diff --git a/.gitignore b/.gitignore index 6b56fdb..80f06e3 100644 --- a/.gitignore +++ b/.gitignore @@ -164,4 +164,7 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -scratch/ \ No newline at end of file +scratch/ + +# protosol +protosol/ diff --git a/README.md b/README.md index c4a03f4..718c738 100644 --- a/README.md +++ b/README.md @@ -14,69 +14,69 @@ Clone this repository and run: source install.sh ``` -### (HIGHLY Recommended) Install auto-completion +### Install auto-completion ```sh solana-test-suite --install-completion ``` You will need to reload your shell + the `test_suite_env` venv to see the changes. +## Currently Supported Harness Types +`list-harness-types` will provide the most updated list. +``` +$ solana-test-suite list-harness-types + +Available harness types: +- ElfLoaderHarness +- InstrHarness +- SyscallHarness +- CpiHarness +- VmInterpHarness +- VmValidateHarness +- TxnHarness +``` + ## Protobuf -Each target must contain a `sol_compat_instr_execute_v1` function that takes in a `InstrContext` message and outputs a `InstrEffects` message (see [`proto/invoke.proto`](https://github.com/firedancer-io/protosol/blob/main/proto/invoke.proto)). See `utils.py:process_instruction` to see how the program interacts with shared libraries. +Each target must contain a function entrypoint that takes in a Context input message and outputs a Effects message (see [`proto/invoke.proto`](https://github.com/firedancer-io/protosol/blob/main/proto/invoke.proto) as an example). + +``` +Function Entrypoints: +- ElfLoaderHarness: sol_compat_elf_loader_v1 +- InstrHarness: sol_compat_instr_execute_v1 +- SyscallHarness: sol_compat_vm_syscall_execute_v1 +- CpiHarness: sol_compat_vm_cpi_syscall_v1 +- VmInterpHarness: sol_compat_vm_interp_v1 +- VmValidateHarness: sol_compat_vm_validate_v1 +- TxnHarness: sol_compat_txn_execute_v1 +``` ### Updating definitions -All message definitions are defined in [Protosol](https://github.com/firedancer-io/protosol/). You may need to periodically update these definitions with the following command: +All message definitions are defined in [protosol](https://github.com/firedancer-io/protosol/). Anytime, protofuf definitions are updated in protosol, you will need to run the following command: ```sh ./fetch_and_generate.sh ``` +## Setting up Environment +To setup the `solana-conformance` environment, run the following command and you will be all set: +``` +source test_suite_env/bin/activate +``` ## Usage -Run `solana-test-suite --help` to see the available commands. -Or, refer to the [commands.md](commands.md) for a list of available commands. - -### Note on [commands.md](commands.md) -`commands.md` is automatically generated from the `typer utils docs` module. -Help messages are dynamically generated based on the [currently set harness type](#selecting-the-correct-harness). Thus, descriptions specific to harness type (typically the Context, Effects, and Fixtures names) in `commands.md` refer to the harness type set during time of generation. - -When the desired [harness type is set](#selecting-the-correct-harness), the `--help` output in the CLI will reflect the correct names. Try it! - -### Selecting the correct harness -The harness type should be specified by an environment variable `HARNESS_TYPE` and supports the following values (default is `InstrHarness` if not provided): -- `InstrHarness` -- `TxnHarness` -- `SyscallHarness` -- `CpiHarness` -- `VmInterpHarness` -- `VmValidateHarness` -- `ElfLoaderHarness` - -`solana-test-suite list-harness-types` will provide the most updated list. - - -### Data Preparation - -Before running tests, Context messages may be converted into Protobuf's text format, with all `bytes` fields base58-encoded (for human readability). This can be done with [decode-protobuf](commands.md#solana-test-suite-decode-protobuf) command. - -Optionally, context messages may also be left in the original Protobuf binary-encoded format. - - -### Debugging - -For failing test cases, it may be useful to analyze what could have differed between Solana and Firedancer. You can execute a Protobuf message (human-readable or binary) through the desired client with the [`debug-instr`](commands.md#solana-test-suite-debug-instr) command. - - -#### Alternative (and preferred) debugging solution +Run the following to view all supported commands or refer to [commands.md](commands.md): +``` +solana-test-suite --help +``` -Use the following command instead if you want the ability to directly restart the program without having to restart GDB: -```sh +### Preferred Debugging +Use the following command instead if you want the ability to debug in GDB: +``` --args python3.11 -m test_suite.test_suite exec-instr --input --target ``` - Refer to [`exec-instr`](commands.md#solana-test-suite-exec-instr) command for more information. -Recommended usage is opening two terminals side by side, and running the above command on both with one having `--target` for Solana (`impl/lib/libsolfuzz_agave_v2.0.so`) and another for Firedancer (`impl/lib/libsolfuzz_firedancer.so`), and then stepping through the debugger for each corresponding test case. +Recommended usage is opening two terminals side by side, and running the above command on the same output while changing the target parameter to the two desired targets and stepping through the debugger for each corresponding test case. ### Uninstalling diff --git a/commands.md b/commands.md index cdbb838..973b0cf 100644 --- a/commands.md +++ b/commands.md @@ -1,6 +1,6 @@ # `solana-test-suite` -Validate effects from clients using InstrContext Protobuf messages. +Validate effects from clients using Protobuf messages. **Usage**: @@ -17,20 +17,19 @@ $ solana-test-suite [OPTIONS] COMMAND [ARGS]... **Commands**: * `create-fixtures`: Create test fixtures from a directory of... -* `debug-instr` * `debug-mismatches`: Run tests on a set of targets with a list... * `debug-non-repros`: Run tests on a set of targets with a list... -* `decode-protobuf`: Convert InstrContext messages to... -* `exec-instr`: Execute InstrContext message(s) and print... -* `instr-from-fixtures`: Extract InstrContext messages from fixtures. +* `decode-protobufs`: Convert Context and/or Fixture messages to... +* `execute`: Execute Context or Fixture message(s) and... +* `fix-to-ctx`: Extract Context messages from Fixtures. * `list-harness-types`: List harness types available for use. * `regenerate-all-fixtures`: Regenerate all fixtures in provided... -* `regenerate-fixtures`: Regenerate InstrFixture messages by... +* `regenerate-fixtures`: Regenerate Fixture messages by checking... * `run-tests`: Run tests on a set of targets with a... ## `solana-test-suite create-fixtures` -Create test fixtures from a directory of InstrContext messages. +Create test fixtures from a directory of Context and/or Fixture messages. Effects are generated by the target passed in with --solana-target or -s. You can also pass in additional targets with --target or -t and use --keep-passing or -k to only generate effects for test cases that match. @@ -43,9 +42,10 @@ $ solana-test-suite create-fixtures [OPTIONS] **Options**: -* `-i, --input-dir PATH`: Either a file or directory containing InstrContext messages [default: corpus8] +* `-i, --input PATH`: Input protobuf file or directory of protobuf files [default: corpus8] +* `-h, --default-harness-type TEXT`: Harness type to use for Context protobufs [default: InstrHarness] * `-s, --solana-target PATH`: Solana (or ground truth) shared object (.so) target file path [default: impl/lib/libsolfuzz_agave_v2.0.so] -* `-t, --target PATH`: Shared object (.so) target file paths (pairs with --keep-passing). Targets must have sol_compat_instr_execute_v1 defined +* `-t, --target PATH`: Shared object (.so) target file paths (pairs with --keep-passing). Targets must have required function entrypoints defined * `-o, --output-dir PATH`: Output directory for fixtures [default: test_fixtures] * `-p, --num-processes INTEGER`: Number of processes to use [default: 4] * `-r, --readable`: Output fixtures in human-readable format @@ -54,21 +54,6 @@ $ solana-test-suite create-fixtures [OPTIONS] * `-l, --log-level INTEGER`: FD logging level [default: 5] * `--help`: Show this message and exit. -## `solana-test-suite debug-instr` - -**Usage**: - -```console -$ solana-test-suite debug-instr [OPTIONS] -``` - -**Options**: - -* `-i, --input PATH`: Input file -* `-t, --target PATH`: Shared object (.so) target file path to debug [default: impl/lib/libsolfuzz_firedancer.so] -* `-d, --debugger TEXT`: Debugger to use (gdb, rust-gdb) [default: gdb] -* `--help`: Show this message and exit. - ## `solana-test-suite debug-mismatches` Run tests on a set of targets with a list of FuzzCorp mismatch links. @@ -84,11 +69,11 @@ $ solana-test-suite debug-mismatches [OPTIONS] **Options**: * `-s, --solana-target PATH`: Solana (or ground truth) shared object (.so) target file path [default: impl/lib/libsolfuzz_agave_v2.0.so] -* `-t, --target PATH`: Shared object (.so) target file paths (pairs with --keep-passing). Targets must have sol_compat_instr_execute_v1 defined [default: impl/lib/libsolfuzz_firedancer.so] -* `-o, --output-dir PATH`: Output directory for InstrContext messages [default: debug_mismatch] +* `-t, --target PATH`: Shared object (.so) target file paths (pairs with --keep-passing). Targets must have required function entrypoints defined [default: impl/lib/libsolfuzz_firedancer.so] +* `-o, --output-dir PATH`: Output directory for messages [default: debug_mismatch] * `-u, --repro-urls TEXT`: Comma-delimited list of FuzzCorp mismatch links * `-s, --section-names TEXT`: Comma-delimited list of FuzzCorp section names -* `-f, --fuzzcorp-url TEXT`: Comma-delimited list of FuzzCorp section names +* `-f, --fuzzcorp-url TEXT`: Comma-delimited list of FuzzCorp section names [default: https://api.dev.fuzzcorp.asymmetric.re/uglyweb/firedancer-io/solfuzz/bugs/] * `--help`: Show this message and exit. ## `solana-test-suite debug-non-repros` @@ -106,62 +91,64 @@ $ solana-test-suite debug-non-repros [OPTIONS] **Options**: * `-s, --solana-target PATH`: Solana (or ground truth) shared object (.so) target file path [default: impl/lib/libsolfuzz_agave_v2.0.so] -* `-t, --target PATH`: Shared object (.so) target file paths (pairs with --keep-passing). Targets must have sol_compat_instr_execute_v1 defined [default: impl/lib/libsolfuzz_firedancer.so] -* `-o, --output-dir PATH`: Output directory for InstrContext messages [default: debug_mismatch] +* `-t, --target PATH`: Shared object (.so) target file paths (pairs with --keep-passing). Targets must have required function entrypoints defined [default: impl/lib/libsolfuzz_firedancer.so] +* `-o, --output-dir PATH`: Output directory for messages [default: debug_mismatch] * `-u, --repro-urls TEXT`: Comma-delimited list of FuzzCorp mismatch links * `-s, --section-names TEXT`: Comma-delimited list of FuzzCorp section names -* `-f, --fuzzcorp-url TEXT`: Comma-delimited list of FuzzCorp section names +* `-f, --fuzzcorp-url TEXT`: Comma-delimited list of FuzzCorp section names [default: https://api.dev.fuzzcorp.asymmetric.re/uglyweb/firedancer-io/solfuzz/bugs/] * `--help`: Show this message and exit. -## `solana-test-suite decode-protobuf` +## `solana-test-suite decode-protobufs` -Convert InstrContext messages to human-readable format. +Convert Context and/or Fixture messages to human-readable format. **Usage**: ```console -$ solana-test-suite decode-protobuf [OPTIONS] +$ solana-test-suite decode-protobufs [OPTIONS] ``` **Options**: -* `-i, --input PATH`: Either a InstrContext message or directory of messages [default: raw_context] -* `-o, --output-dir PATH`: Output directory for base58-encoded, human-readable InstrContext messages [default: readable_context] +* `-i, --input PATH`: Input protobuf file or directory of protobuf files [default: raw_context] +* `-o, --output-dir PATH`: Output directory for base58-encoded, Context and/or Fixture human-readable messages [default: readable_context] * `-p, --num-processes INTEGER`: Number of processes to use [default: 4] +* `-h, --default-harness-type TEXT`: Harness type to use for Context protobufs [default: InstrHarness] * `--help`: Show this message and exit. -## `solana-test-suite exec-instr` +## `solana-test-suite execute` -Execute InstrContext message(s) and print the effects. +Execute Context or Fixture message(s) and print the Effects. **Usage**: ```console -$ solana-test-suite exec-instr [OPTIONS] +$ solana-test-suite execute [OPTIONS] ``` **Options**: -* `-i, --input PATH`: Input InstrContext file or directory of files +* `-i, --input PATH`: Input protobuf file or directory of protobuf files +* `-h, --default-harness-type TEXT`: Harness type to use for Context protobufs [default: InstrHarness] * `-t, --target PATH`: Shared object (.so) target file path to execute [default: impl/firedancer/build/native/clang/lib/libfd_exec_sol_compat.so] * `-r, --randomize-output-buffer`: Randomizes bytes in output buffer before shared library execution * `-l, --log-level INTEGER`: FD logging level [default: 2] * `--help`: Show this message and exit. -## `solana-test-suite instr-from-fixtures` +## `solana-test-suite fix-to-ctx` -Extract InstrContext messages from fixtures. +Extract Context messages from Fixtures. **Usage**: ```console -$ solana-test-suite instr-from-fixtures [OPTIONS] +$ solana-test-suite fix-to-ctx [OPTIONS] ``` **Options**: -* `-i, --input-dir PATH`: Input directory containing InstrFixture messages [default: fixtures] -* `-o, --output-dir PATH`: Output directory for InstrContext messages [default: instr] +* `-i, --input PATH`: Input Fixture file or directory of Fixture files [default: fixtures] +* `-o, --output-dir PATH`: Output directory for messages [default: instr] * `-p, --num-processes INTEGER`: Number of processes to use [default: 4] * `--help`: Show this message and exit. @@ -191,7 +178,7 @@ $ solana-test-suite regenerate-all-fixtures [OPTIONS] **Options**: -* `-i, --input-dir PATH`: Input test-vectors directory [default: corpus8] +* `-i, --input PATH`: Input test-vectors directory [default: corpus8] * `-o, --output-dir PATH`: Output directory for regenerated fixtures [default: /tmp/regenerated_fixtures] * `-t, --target PATH`: Shared object (.so) target file path to execute [default: impl/lib/libsolfuzz_agave_v2.0.so] * `-s, --stubbed-target PATH`: Stubbed shared object (.so) target file path to execute [default: impl/lib/libsolfuzz_firedancer.so] @@ -199,8 +186,7 @@ $ solana-test-suite regenerate-all-fixtures [OPTIONS] ## `solana-test-suite regenerate-fixtures` -Regenerate InstrFixture messages by -checking FeatureSet compatibility with the target shared library. +Regenerate Fixture messages by checking FeatureSet compatibility with the target shared library. **Usage**: @@ -210,7 +196,7 @@ $ solana-test-suite regenerate-fixtures [OPTIONS] **Options**: -* `-i, --input-dir PATH`: Either a file or directory containing InstrFixture messages [default: corpus8] +* `-i, --input PATH`: Either a file or directory containing messages [default: corpus8] * `-t, --target PATH`: Shared object (.so) target file path to execute [default: impl/lib/libsolfuzz_agave_v2.0.so] * `-o, --output-dir PATH`: Output directory for regenerated fixtures [default: regenerated_fixtures] * `-d, --dry-run`: Only print the fixtures that would be regenerated @@ -220,8 +206,7 @@ $ solana-test-suite regenerate-fixtures [OPTIONS] ## `solana-test-suite run-tests` -Run tests on a set of targets with a directory of InstrContext -or InstrFixture messages. +Run tests on a set of targets with a directory of Context and/or Fixture messages. Note: each `.so` target filename must be unique. @@ -233,7 +218,8 @@ $ solana-test-suite run-tests [OPTIONS] **Options**: -* `-i, --input PATH`: Single input file or input directory containing InstrContext or InstrFixture messages [default: corpus8] +* `-i, --input PATH`: Input protobuf file or directory of protobuf files [default: corpus8] +* `-h, --default-harness-type TEXT`: Harness type to use for Context protobufs [default: InstrHarness] * `-s, --solana-target PATH`: Solana (or ground truth) shared object (.so) target file path [default: impl/lib/libsolfuzz_agave_v2.0.so] * `-t, --target PATH`: Shared object (.so) target file paths [default: impl/lib/libsolfuzz_firedancer.so] * `-o, --output-dir PATH`: Output directory for test results [default: test_results] diff --git a/src/test_suite/debugger.py b/src/test_suite/debugger.py deleted file mode 100644 index 629c127..0000000 --- a/src/test_suite/debugger.py +++ /dev/null @@ -1,79 +0,0 @@ -import ctypes -import multiprocessing -from multiprocessing import Pipe -import signal -import subprocess -import os -from test_suite.multiprocessing_utils import ( - initialize_process_output_buffers, - process_target, -) -import test_suite.globals as globals - - -def debug_target(shared_library, test_input, pipe): - initialize_process_output_buffers() - - # Signal to parent that we are ready for the debugger - pipe.send("started") - - # Suspend self so the debugger has time to attach - os.kill(os.getpid(), signal.SIGSTOP) - # ... at this point, the debugger has sent us a SIGCONT signal, - # is watching any dlopen() call and has set breakpoints - - lib = ctypes.CDLL(shared_library) - lib.sol_compat_init() - process_target(lib, test_input) - lib.sol_compat_fini() - - -def debug_host(shared_library, instruction_context, gdb): - # Sets up the following debug environment: - # - # +-------------------------------+ - # | Main Python Process | - # | (this function) | - # | | - # | +-----------------------+ | - # | | Child Python Process | | - # | | (With sol_compat lib) | | - # | +-----------------------+ | - # | /\ Attached to | - # | +-----------------------+ | - # | | Debugger Process | | - # | +-----------------------+ | - # | | - # +-------------------------------+ - - # Spawn the Python interpreter - pipe, child_pipe = Pipe() - target = multiprocessing.Process( - target=debug_target, args=(shared_library, instruction_context, child_pipe) - ) - target.start() - # Wait for a signal that the child process is ready - assert pipe.recv() == "started" - - commands = [ - # Skip loading Python interpreter libraries, as those are not interesting - "set auto-solib-add off", - # Attach to the debug target Python process - f"attach {target.pid}", - # As soon as the target library gets loaded, set a breakpoint - # for the newly appeared executor function - "set breakpoint pending on", - f"break {globals.harness_ctx.fuzz_fn_name}", - # GDB stops the process when attaching, let it continue - "continue", - # ... At this point, the child process has SIGSTOP'ed itself - "set auto-solib-add on", - # Continue it - "signal SIGCONT", - # ... At this point, the child process has dlopen()ed the - # target library and the breakpoint was hit - "layout src", - ] - invoke = [gdb, "-q"] + ["--eval-command=" + cmd for cmd in commands] - print(f"Running {' '.join(invoke)}") - subprocess.run(invoke) diff --git a/src/test_suite/elf_pb2.py b/src/test_suite/elf_pb2.py index 60f03b4..5b0c733 100644 --- a/src/test_suite/elf_pb2.py +++ b/src/test_suite/elf_pb2.py @@ -9,15 +9,16 @@ _sym_db = _symbol_database.Default() from . import context_pb2 as context__pb2 +from . import metadata_pb2 as metadata__pb2 DESCRIPTOR = _descriptor.FileDescriptor( name="elf.proto", package="org.solana.sealevel.v1", syntax="proto3", serialized_pb=_b( - '\n\telf.proto\x12\x16org.solana.sealevel.v1\x1a\rcontext.proto"\x19\n\tELFBinary\x12\x0c\n\x04data\x18\x01 \x01(\x0c"\x9b\x01\n\x0cELFLoaderCtx\x12.\n\x03elf\x18\x01 \x01(\x0b2!.org.solana.sealevel.v1.ELFBinary\x124\n\x08features\x18\x02 \x01(\x0b2".org.solana.sealevel.v1.FeatureSet\x12\x0e\n\x06elf_sz\x18\x03 \x01(\x04\x12\x15\n\rdeploy_checks\x18\x04 \x01(\x08"~\n\x10ELFLoaderEffects\x12\x0e\n\x06rodata\x18\x01 \x01(\x0c\x12\x11\n\trodata_sz\x18\x02 \x01(\x04\x12\x10\n\x08text_cnt\x18\x04 \x01(\x04\x12\x10\n\x08text_off\x18\x05 \x01(\x04\x12\x10\n\x08entry_pc\x18\x06 \x01(\x04\x12\x11\n\tcalldests\x18\x07 \x03(\x04"\x81\x01\n\x10ELFLoaderFixture\x123\n\x05input\x18\x01 \x01(\x0b2$.org.solana.sealevel.v1.ELFLoaderCtx\x128\n\x06output\x18\x02 \x01(\x0b2(.org.solana.sealevel.v1.ELFLoaderEffectsb\x06proto3' + '\n\telf.proto\x12\x16org.solana.sealevel.v1\x1a\rcontext.proto\x1a\x0emetadata.proto"\x19\n\tELFBinary\x12\x0c\n\x04data\x18\x01 \x01(\x0c"\x9b\x01\n\x0cELFLoaderCtx\x12.\n\x03elf\x18\x01 \x01(\x0b2!.org.solana.sealevel.v1.ELFBinary\x124\n\x08features\x18\x02 \x01(\x0b2".org.solana.sealevel.v1.FeatureSet\x12\x0e\n\x06elf_sz\x18\x03 \x01(\x04\x12\x15\n\rdeploy_checks\x18\x04 \x01(\x08"~\n\x10ELFLoaderEffects\x12\x0e\n\x06rodata\x18\x01 \x01(\x0c\x12\x11\n\trodata_sz\x18\x02 \x01(\x04\x12\x10\n\x08text_cnt\x18\x04 \x01(\x04\x12\x10\n\x08text_off\x18\x05 \x01(\x04\x12\x10\n\x08entry_pc\x18\x06 \x01(\x04\x12\x11\n\tcalldests\x18\x07 \x03(\x04"¼\x01\n\x10ELFLoaderFixture\x129\n\x08metadata\x18\x01 \x01(\x0b2\'.org.solana.sealevel.v1.FixtureMetadata\x123\n\x05input\x18\x02 \x01(\x0b2$.org.solana.sealevel.v1.ELFLoaderCtx\x128\n\x06output\x18\x03 \x01(\x0b2(.org.solana.sealevel.v1.ELFLoaderEffectsb\x06proto3' ), - dependencies=[context__pb2.DESCRIPTOR], + dependencies=[context__pb2.DESCRIPTOR, metadata__pb2.DESCRIPTOR], ) _ELFBINARY = _descriptor.Descriptor( name="ELFBinary", @@ -53,8 +54,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=52, - serialized_end=77, + serialized_start=68, + serialized_end=93, ) _ELFLOADERCTX = _descriptor.Descriptor( name="ELFLoaderCtx", @@ -144,8 +145,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=80, - serialized_end=235, + serialized_start=96, + serialized_end=251, ) _ELFLOADEREFFECTS = _descriptor.Descriptor( name="ELFLoaderEffects", @@ -271,8 +272,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=237, - serialized_end=363, + serialized_start=253, + serialized_end=379, ) _ELFLOADERFIXTURE = _descriptor.Descriptor( name="ELFLoaderFixture", @@ -282,8 +283,8 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="input", - full_name="org.solana.sealevel.v1.ELFLoaderFixture.input", + name="metadata", + full_name="org.solana.sealevel.v1.ELFLoaderFixture.metadata", index=0, number=1, type=11, @@ -300,8 +301,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="output", - full_name="org.solana.sealevel.v1.ELFLoaderFixture.output", + name="input", + full_name="org.solana.sealevel.v1.ELFLoaderFixture.input", index=1, number=2, type=11, @@ -317,6 +318,24 @@ options=None, file=DESCRIPTOR, ), + _descriptor.FieldDescriptor( + name="output", + full_name="org.solana.sealevel.v1.ELFLoaderFixture.output", + index=2, + number=3, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), ], extensions=[], nested_types=[], @@ -326,11 +345,14 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=366, - serialized_end=495, + serialized_start=382, + serialized_end=570, ) _ELFLOADERCTX.fields_by_name["elf"].message_type = _ELFBINARY _ELFLOADERCTX.fields_by_name["features"].message_type = context__pb2._FEATURESET +_ELFLOADERFIXTURE.fields_by_name["metadata"].message_type = ( + metadata__pb2._FIXTUREMETADATA +) _ELFLOADERFIXTURE.fields_by_name["input"].message_type = _ELFLOADERCTX _ELFLOADERFIXTURE.fields_by_name["output"].message_type = _ELFLOADEREFFECTS DESCRIPTOR.message_types_by_name["ELFBinary"] = _ELFBINARY diff --git a/src/test_suite/fixture_utils.py b/src/test_suite/fixture_utils.py index 92a8c12..5f9aa02 100644 --- a/src/test_suite/fixture_utils.py +++ b/src/test_suite/fixture_utils.py @@ -1,12 +1,17 @@ import fd58 +import inspect from test_suite.constants import NATIVE_PROGRAM_MAPPING +from test_suite.fuzz_context import HARNESS_ENTRYPOINT_MAP, HarnessCtx from test_suite.multiprocessing_utils import ( build_test_results, + extract_metadata, read_context, + read_fixture, process_single_test_case, ) import test_suite.globals as globals import test_suite.invoke_pb2 as invoke_pb +import test_suite.metadata_pb2 as metadata_pb from google.protobuf import text_format from pathlib import Path from test_suite.fuzz_interface import ContextType, FixtureType @@ -22,22 +27,47 @@ def create_fixture(test_file: Path) -> int: Returns: - int: 1 on success, 0 on failure """ - fixture = create_fixture_from_context(read_context(test_file)) + + harness_ctx = globals.default_harness_ctx + if test_file.suffix == ".fix": + fixture = read_fixture(test_file) + harness_ctx = HARNESS_ENTRYPOINT_MAP[fixture.metadata.fn_entrypoint] + fixture = create_fixture_from_fixture( + harness_ctx, + read_fixture(test_file), + ) + else: + fixture = create_fixture_from_context( + harness_ctx, + read_context(globals.default_harness_ctx, test_file), + ) + if fixture is None: return 0 return write_fixture_to_disk( - test_file.stem, fixture.SerializeToString(deterministic=True) + harness_ctx, test_file.stem, fixture.SerializeToString(deterministic=True) ) -def create_fixture_from_context(context: ContextType) -> FixtureType | None: +def create_fixture_from_fixture( + harness_ctx: HarnessCtx, fixture: FixtureType +) -> FixtureType | None: + fixture = create_fixture_from_context(harness_ctx, fixture.input) + return fixture + + +def create_fixture_from_context( + harness_ctx: HarnessCtx, context: ContextType +) -> FixtureType | None: context.DiscardUnknownFields() context_serialized = context.SerializeToString(deterministic=True) # Execute the test case - results = process_single_test_case(context_serialized) - pruned_results = globals.harness_ctx.prune_effects_fn(context_serialized, results) + results = process_single_test_case(harness_ctx, context_serialized) + pruned_results = harness_ctx.prune_effects_fn( + harness_ctx, context_serialized, results + ) # This is only relevant when you gather results for multiple targets if globals.only_keep_passing: @@ -53,17 +83,22 @@ def create_fixture_from_context(context: ContextType) -> FixtureType | None: if context_serialized is None or effects_serialized is None: return None # Create instruction fixture - effects = globals.harness_ctx.effects_type() + effects = harness_ctx.effects_type() effects.ParseFromString(effects_serialized) - fixture = globals.harness_ctx.fixture_type() + metadata = metadata_pb.FixtureMetadata() + metadata.fn_entrypoint = harness_ctx.fuzz_fn_name + fixture = harness_ctx.fixture_type() fixture.input.MergeFrom(context) fixture.output.MergeFrom(effects) + fixture.metadata.MergeFrom(metadata) return fixture -def write_fixture_to_disk(file_stem: str, serialized_fixture: str) -> int: +def write_fixture_to_disk( + harness_ctx: HarnessCtx, file_stem: str, serialized_fixture: str +) -> int: """ Writes instruction fixtures to disk. This function outputs in binary format unless specified otherwise with the --readable flag. @@ -80,7 +115,7 @@ def write_fixture_to_disk(file_stem: str, serialized_fixture: str) -> int: output_dir = globals.output_dir if globals.organize_fixture_dir: - fixture = globals.harness_ctx.fixture_type() + fixture = harness_ctx.fixture_type() fixture.ParseFromString(serialized_fixture) program_type = get_program_type(fixture) output_dir = output_dir / program_type @@ -92,14 +127,14 @@ def write_fixture_to_disk(file_stem: str, serialized_fixture: str) -> int: fixture.ParseFromString(serialized_fixture) # Encode fields for instruction context and effects - context = globals.harness_ctx.context_type() + context = harness_ctx.context_type() context.CopyFrom(fixture.input) # encode_input(context) - globals.harness_ctx.context_human_encode_fn(context) + harness_ctx.context_human_encode_fn(context) - instr_effects = globals.harness_ctx.effects_type() + instr_effects = harness_ctx.effects_type() instr_effects.CopyFrom(fixture.output) - globals.harness_ctx.effects_human_encode_fn(instr_effects) + harness_ctx.effects_human_encode_fn(instr_effects) fixture.input.CopyFrom(context) fixture.output.CopyFrom(instr_effects) @@ -124,7 +159,9 @@ def extract_context_from_fixture(fixture_file: Path): - int: 1 on success, 0 on failure """ try: - fixture = globals.harness_ctx.fixture_type() + fn_entrypoint = extract_metadata(fixture_file).fn_entrypoint + harness_ctx = HARNESS_ENTRYPOINT_MAP[fn_entrypoint] + fixture = harness_ctx.fixture_type() with open(fixture_file, "rb") as f: fixture.ParseFromString(f.read()) diff --git a/src/test_suite/fuzz_context.py b/src/test_suite/fuzz_context.py index 00aa029..ac28070 100644 --- a/src/test_suite/fuzz_context.py +++ b/src/test_suite/fuzz_context.py @@ -62,3 +62,8 @@ ) HARNESS_LIST = [name for name, obj in globals().items() if isinstance(obj, HarnessCtx)] +HARNESS_ENTRYPOINT_MAP = { + obj.fuzz_fn_name: obj + for name, obj in globals().items() + if isinstance(obj, HarnessCtx) +} diff --git a/src/test_suite/fuzz_interface.py b/src/test_suite/fuzz_interface.py index b1a3cdb..21aba7b 100644 --- a/src/test_suite/fuzz_interface.py +++ b/src/test_suite/fuzz_interface.py @@ -44,7 +44,7 @@ def encode_hex_compact(buf): def generic_effects_prune( - ctx: str | None, effects: dict[str, str | None] + harness_ctx: "HarnessCtx", ctx: str | None, effects: dict[str, str | None] ) -> dict[str, str | None] | None: if ctx is None: return None diff --git a/src/test_suite/globals.py b/src/test_suite/globals.py index cc9d2b3..54d5229 100644 --- a/src/test_suite/globals.py +++ b/src/test_suite/globals.py @@ -30,5 +30,8 @@ # (For fixtures) Whether to only keep passing tests only_keep_passing = False -# Harness context -harness_ctx: HarnessCtx = None +# Default harness context +default_harness_ctx: HarnessCtx = None + +# Whether to run in consensus mode +consensus_mode: bool = False diff --git a/src/test_suite/instr/prune_utils.py b/src/test_suite/instr/prune_utils.py index 5e9cb43..7702a05 100644 --- a/src/test_suite/instr/prune_utils.py +++ b/src/test_suite/instr/prune_utils.py @@ -1,3 +1,4 @@ +from test_suite.fuzz_context import HarnessCtx import test_suite.globals as globals import test_suite.invoke_pb2 as invoke_pb import test_suite.context_pb2 as context_pb @@ -5,6 +6,7 @@ def prune_execution_result( + harness_ctx: HarnessCtx, serialized_context: str | None, targets_to_serialized_effects: dict[str, str | None], ) -> dict[str, str | None] | None: @@ -21,7 +23,7 @@ def prune_execution_result( if serialized_context is None: return None - context = globals.harness_ctx.context_type() + context = harness_ctx.context_type() context.ParseFromString(serialized_context) targets_to_serialized_pruned_instruction_effects = {} diff --git a/src/test_suite/invoke_pb2.py b/src/test_suite/invoke_pb2.py index a09c754..8c807ba 100644 --- a/src/test_suite/invoke_pb2.py +++ b/src/test_suite/invoke_pb2.py @@ -9,15 +9,16 @@ _sym_db = _symbol_database.Default() from . import context_pb2 as context__pb2 +from . import metadata_pb2 as metadata__pb2 DESCRIPTOR = _descriptor.FileDescriptor( name="invoke.proto", package="org.solana.sealevel.v1", syntax="proto3", serialized_pb=_b( - '\n\x0cinvoke.proto\x12\x16org.solana.sealevel.v1\x1a\rcontext.proto"B\n\tInstrAcct\x12\r\n\x05index\x18\x01 \x01(\r\x12\x13\n\x0bis_writable\x18\x02 \x01(\x08\x12\x11\n\tis_signer\x18\x03 \x01(\x08"ª\x02\n\x0cInstrContext\x12\x12\n\nprogram_id\x18\x01 \x01(\x0c\x123\n\x08accounts\x18\x03 \x03(\x0b2!.org.solana.sealevel.v1.AcctState\x129\n\x0einstr_accounts\x18\x04 \x03(\x0b2!.org.solana.sealevel.v1.InstrAcct\x12\x0c\n\x04data\x18\x05 \x01(\x0c\x12\x10\n\x08cu_avail\x18\x06 \x01(\x04\x129\n\x0cslot_context\x18\x08 \x01(\x0b2#.org.solana.sealevel.v1.SlotContext\x12;\n\repoch_context\x18\t \x01(\x0b2$.org.solana.sealevel.v1.EpochContext"\x97\x01\n\x0cInstrEffects\x12\x0e\n\x06result\x18\x01 \x01(\x05\x12\x12\n\ncustom_err\x18\x02 \x01(\r\x12<\n\x11modified_accounts\x18\x03 \x03(\x0b2!.org.solana.sealevel.v1.AcctState\x12\x10\n\x08cu_avail\x18\x04 \x01(\x04\x12\x13\n\x0breturn_data\x18\x05 \x01(\x0c"y\n\x0cInstrFixture\x123\n\x05input\x18\x01 \x01(\x0b2$.org.solana.sealevel.v1.InstrContext\x124\n\x06output\x18\x02 \x01(\x0b2$.org.solana.sealevel.v1.InstrEffectsb\x06proto3' + '\n\x0cinvoke.proto\x12\x16org.solana.sealevel.v1\x1a\rcontext.proto\x1a\x0emetadata.proto"B\n\tInstrAcct\x12\r\n\x05index\x18\x01 \x01(\r\x12\x13\n\x0bis_writable\x18\x02 \x01(\x08\x12\x11\n\tis_signer\x18\x03 \x01(\x08"ª\x02\n\x0cInstrContext\x12\x12\n\nprogram_id\x18\x01 \x01(\x0c\x123\n\x08accounts\x18\x03 \x03(\x0b2!.org.solana.sealevel.v1.AcctState\x129\n\x0einstr_accounts\x18\x04 \x03(\x0b2!.org.solana.sealevel.v1.InstrAcct\x12\x0c\n\x04data\x18\x05 \x01(\x0c\x12\x10\n\x08cu_avail\x18\x06 \x01(\x04\x129\n\x0cslot_context\x18\x08 \x01(\x0b2#.org.solana.sealevel.v1.SlotContext\x12;\n\repoch_context\x18\t \x01(\x0b2$.org.solana.sealevel.v1.EpochContext"\x97\x01\n\x0cInstrEffects\x12\x0e\n\x06result\x18\x01 \x01(\x05\x12\x12\n\ncustom_err\x18\x02 \x01(\r\x12<\n\x11modified_accounts\x18\x03 \x03(\x0b2!.org.solana.sealevel.v1.AcctState\x12\x10\n\x08cu_avail\x18\x04 \x01(\x04\x12\x13\n\x0breturn_data\x18\x05 \x01(\x0c"´\x01\n\x0cInstrFixture\x129\n\x08metadata\x18\x01 \x01(\x0b2\'.org.solana.sealevel.v1.FixtureMetadata\x123\n\x05input\x18\x02 \x01(\x0b2$.org.solana.sealevel.v1.InstrContext\x124\n\x06output\x18\x03 \x01(\x0b2$.org.solana.sealevel.v1.InstrEffectsb\x06proto3' ), - dependencies=[context__pb2.DESCRIPTOR], + dependencies=[context__pb2.DESCRIPTOR, metadata__pb2.DESCRIPTOR], ) _INSTRACCT = _descriptor.Descriptor( name="InstrAcct", @@ -89,8 +90,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=55, - serialized_end=121, + serialized_start=71, + serialized_end=137, ) _INSTRCONTEXT = _descriptor.Descriptor( name="InstrContext", @@ -234,8 +235,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=124, - serialized_end=422, + serialized_start=140, + serialized_end=438, ) _INSTREFFECTS = _descriptor.Descriptor( name="InstrEffects", @@ -343,8 +344,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=425, - serialized_end=576, + serialized_start=441, + serialized_end=592, ) _INSTRFIXTURE = _descriptor.Descriptor( name="InstrFixture", @@ -354,8 +355,8 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="input", - full_name="org.solana.sealevel.v1.InstrFixture.input", + name="metadata", + full_name="org.solana.sealevel.v1.InstrFixture.metadata", index=0, number=1, type=11, @@ -372,8 +373,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="output", - full_name="org.solana.sealevel.v1.InstrFixture.output", + name="input", + full_name="org.solana.sealevel.v1.InstrFixture.input", index=1, number=2, type=11, @@ -389,6 +390,24 @@ options=None, file=DESCRIPTOR, ), + _descriptor.FieldDescriptor( + name="output", + full_name="org.solana.sealevel.v1.InstrFixture.output", + index=2, + number=3, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), ], extensions=[], nested_types=[], @@ -398,14 +417,15 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=578, - serialized_end=699, + serialized_start=595, + serialized_end=775, ) _INSTRCONTEXT.fields_by_name["accounts"].message_type = context__pb2._ACCTSTATE _INSTRCONTEXT.fields_by_name["instr_accounts"].message_type = _INSTRACCT _INSTRCONTEXT.fields_by_name["slot_context"].message_type = context__pb2._SLOTCONTEXT _INSTRCONTEXT.fields_by_name["epoch_context"].message_type = context__pb2._EPOCHCONTEXT _INSTREFFECTS.fields_by_name["modified_accounts"].message_type = context__pb2._ACCTSTATE +_INSTRFIXTURE.fields_by_name["metadata"].message_type = metadata__pb2._FIXTUREMETADATA _INSTRFIXTURE.fields_by_name["input"].message_type = _INSTRCONTEXT _INSTRFIXTURE.fields_by_name["output"].message_type = _INSTREFFECTS DESCRIPTOR.message_types_by_name["InstrAcct"] = _INSTRACCT diff --git a/src/test_suite/metadata_pb2.py b/src/test_suite/metadata_pb2.py new file mode 100644 index 0000000..3c32030 --- /dev/null +++ b/src/test_suite/metadata_pb2.py @@ -0,0 +1,63 @@ +import sys + +_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 + +_sym_db = _symbol_database.Default() +DESCRIPTOR = _descriptor.FileDescriptor( + name="metadata.proto", + package="org.solana.sealevel.v1", + syntax="proto3", + serialized_pb=_b( + '\n\x0emetadata.proto\x12\x16org.solana.sealevel.v1"(\n\x0fFixtureMetadata\x12\x15\n\rfn_entrypoint\x18\x01 \x01(\tb\x06proto3' + ), +) +_FIXTUREMETADATA = _descriptor.Descriptor( + name="FixtureMetadata", + full_name="org.solana.sealevel.v1.FixtureMetadata", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="fn_entrypoint", + full_name="org.solana.sealevel.v1.FixtureMetadata.fn_entrypoint", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ) + ], + extensions=[], + nested_types=[], + enum_types=[], + options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=42, + serialized_end=82, +) +DESCRIPTOR.message_types_by_name["FixtureMetadata"] = _FIXTUREMETADATA +_sym_db.RegisterFileDescriptor(DESCRIPTOR) +FixtureMetadata = _reflection.GeneratedProtocolMessageType( + "FixtureMetadata", + (_message.Message,), + dict(DESCRIPTOR=_FIXTUREMETADATA, __module__="metadata_pb2"), +) +_sym_db.RegisterMessage(FixtureMetadata) diff --git a/src/test_suite/multiprocessing_utils.py b/src/test_suite/multiprocessing_utils.py index 290e1b5..0a7a21f 100644 --- a/src/test_suite/multiprocessing_utils.py +++ b/src/test_suite/multiprocessing_utils.py @@ -1,16 +1,19 @@ from dataclasses import dataclass, field from test_suite.constants import OUTPUT_BUFFER_SIZE +from test_suite.fuzz_context import HARNESS_ENTRYPOINT_MAP, HarnessCtx import test_suite.invoke_pb2 as invoke_pb +import test_suite.metadata_pb2 as metadata_pb2 import ctypes from ctypes import c_uint64, c_int, POINTER from pathlib import Path import test_suite.globals as globals from google.protobuf import text_format, message +from google.protobuf.internal.decoder import _DecodeVarint import os def process_target( - library: ctypes.CDLL, serialized_instruction_context: str + harness_ctx: HarnessCtx, library: ctypes.CDLL, serialized_instruction_context: str ) -> invoke_pb.InstrEffects | None: """ Process an instruction through a provided shared library and return the result. @@ -29,7 +32,7 @@ def process_target( out_sz = ctypes.c_uint64(OUTPUT_BUFFER_SIZE) # Get the function to call - sol_compat_fn = getattr(library, globals.harness_ctx.fuzz_fn_name) + sol_compat_fn = getattr(library, harness_ctx.fuzz_fn_name) # Define argument and return types sol_compat_fn.argtypes = [ @@ -50,15 +53,15 @@ def process_target( # Process the output output_data = bytearray(globals.output_buffer_pointer[: out_sz.value]) - output_object = globals.harness_ctx.effects_type() + output_object = harness_ctx.effects_type() output_object.ParseFromString(output_data) return output_object -def read_context_serialized(test_file: Path) -> str | None: +def read_context_serialized(harness_ctx: HarnessCtx, test_file: Path) -> str | None: """ - Reads in test files and generates an InstrContext Protobuf object for a test case. + Reads in test files and generates an Context Protobuf object for a test case. Args: - test_file (Path): Path to the instruction context message. @@ -68,11 +71,42 @@ def read_context_serialized(test_file: Path) -> str | None: """ # Serialize instruction context to string (pickleable) - ctx = read_context(test_file) + ctx = read_context(harness_ctx, test_file) return ctx.SerializeToString(deterministic=True) if ctx else None -def read_context(test_file: Path) -> message.Message | None: +def extract_metadata(fixture_file: Path) -> str | None: + """ + Extracts metadata from a fixture file. + + Args: + - fixture_file (Path): Path to the fixture message. + + Returns: + - str | None: Metadata from the fixture file. + """ + + with open(fixture_file, "rb") as f: + proto_bytes = f.read() + try: + metadata = metadata_pb2.FixtureMetadata() + pos = 0 + while pos < len(proto_bytes): + tag, pos = _DecodeVarint(proto_bytes, pos) + if (tag >> 3) == 1: + length, pos = _DecodeVarint(proto_bytes, pos) + metadata.ParseFromString(proto_bytes[pos : pos + length]) + return metadata + pos += _DecodeVarint(proto_bytes, pos)[0] + raise message.DecodeError("No 'metadata' found") + except message.DecodeError as e: + print(f"Failed to parse 'metadata': {e}") + return None + + return getattr(metadata, "fn_entrypoint", None) + + +def read_context(harness_ctx: HarnessCtx, test_file: Path) -> message.Message | None: """ Reads in test files and generates an Context Protobuf object for a test case. @@ -86,19 +120,17 @@ def read_context(test_file: Path) -> message.Message | None: try: # Read in binary Protobuf messages with open(test_file, "rb") as f: - context = globals.harness_ctx.context_type() + context = harness_ctx.context_type() context.ParseFromString(f.read()) except: try: # Maybe it's in human-readable Protobuf format? with open(test_file) as f: - context = text_format.Parse( - f.read(), globals.harness_ctx.context_type() - ) + context = text_format.Parse(f.read(), harness_ctx.context_type()) # Decode into digestable fields # decode_input(instruction_context) - globals.harness_ctx.context_human_decode_fn(context) + harness_ctx.context_human_decode_fn(context) except: # Unable to read message, skip and continue context = None @@ -147,8 +179,10 @@ def read_fixture(fixture_file: Path) -> message.Message | None: # Try to read in first as binary-encoded Protobuf messages try: # Read in binary Protobuf messages + fn_entrypoint = extract_metadata(fixture_file).fn_entrypoint + harness_ctx = HARNESS_ENTRYPOINT_MAP[fn_entrypoint] with open(fixture_file, "rb") as f: - fixture = globals.harness_ctx.fixture_type() + fixture = harness_ctx.fixture_type() fixture.ParseFromString(f.read()) except: # Unable to read message, skip and continue @@ -173,25 +207,39 @@ def decode_single_test_case(test_file: Path) -> int: Returns: - int: 1 if successfully decoded and written, 0 if skipped. """ - serialized_instruction_context = read_context_serialized(test_file) + if test_file.suffix == ".fix": + fn_entrypoint = extract_metadata(test_file).fn_entrypoint + harness_ctx = HARNESS_ENTRYPOINT_MAP[fn_entrypoint] + serialized_protobuf = read_fixture_serialized(test_file) + else: + harness_ctx = globals.default_harness_ctx + serialized_protobuf = read_context_serialized(harness_ctx, test_file) # Skip if input is invalid - if serialized_instruction_context is None: + if serialized_protobuf is None: return 0 # Encode the input fields to be human readable - instruction_context = globals.harness_ctx.context_type() - instruction_context.ParseFromString(serialized_instruction_context) - globals.harness_ctx.context_human_encode_fn(instruction_context) + if test_file.suffix == ".fix": + output = harness_ctx.fixture_type() + else: + output = harness_ctx.context_type() + + output.ParseFromString(serialized_protobuf) + + if test_file.suffix == ".fix": + harness_ctx.context_human_encode_fn(output.input) + harness_ctx.effects_human_encode_fn(output.output) + else: + harness_ctx.context_human_encode_fn(output) with open(globals.output_dir / (test_file.stem + ".txt"), "w") as f: - f.write( - text_format.MessageToString(instruction_context, print_unknown_fields=False) - ) + f.write(text_format.MessageToString(output, print_unknown_fields=False)) return 1 def process_single_test_case( + harness_ctx: HarnessCtx, serialized_instruction_context: str | None, ) -> dict[str, str | None] | None: """ @@ -212,7 +260,9 @@ def process_single_test_case( results = {} for target in globals.target_libraries: instruction_effects = process_target( - globals.target_libraries[target], serialized_instruction_context + harness_ctx, + globals.target_libraries[target], + serialized_instruction_context, ) result = ( instruction_effects.SerializeToString(deterministic=True) @@ -252,7 +302,9 @@ def merge_results_over_iterations(results: tuple) -> tuple[str, dict]: return file, merged_results -def build_test_results(results: dict[str, str | None]) -> tuple[int, dict | None]: +def build_test_results( + harness_ctx: HarnessCtx, results: dict[str, str | None] +) -> tuple[int, dict | None]: """ Build a single result of single test execution and returns whether the test passed or failed. @@ -277,7 +329,7 @@ def build_test_results(results: dict[str, str | None]) -> tuple[int, dict | None print("Skipping test case due to Agave rejection") return 0, None - ref_effects = globals.harness_ctx.effects_type() + ref_effects = harness_ctx.effects_type() ref_effects.ParseFromString(ref_result) # Log execution results @@ -289,18 +341,21 @@ def build_test_results(results: dict[str, str | None]) -> tuple[int, dict | None effects = None if result is not None: # Turn bytes into human readable fields - effects = globals.harness_ctx.effects_type() + effects = harness_ctx.effects_type() effects.ParseFromString(result) + if globals.consensus_mode: + harness_ctx.diff_effect_fn = harness_ctx.consensus_diff_effect_fn + # Note: diff_effect_fn may modify effects in-place - all_passed &= globals.harness_ctx.diff_effect_fn(ref_effects, effects) + all_passed &= harness_ctx.diff_effect_fn(ref_effects, effects) - globals.harness_ctx.effects_human_encode_fn(effects) + harness_ctx.effects_human_encode_fn(effects) outputs[target] = text_format.MessageToString(effects) else: all_passed = False - globals.harness_ctx.effects_human_encode_fn(ref_effects) + harness_ctx.effects_human_encode_fn(ref_effects) outputs[globals.reference_shared_library] = text_format.MessageToString(ref_effects) # 1 = passed, -1 = failed @@ -323,13 +378,13 @@ def initialize_process_output_buffers(randomize_output_buffer=False): ) -def serialize_context(file: Path) -> str | None: +def serialize_context(harness_ctx: HarnessCtx, file: Path) -> str | None: if file.suffix == ".fix": - fixture = globals.harness_ctx.fixture_type() + fixture = harness_ctx.fixture_type() fixture.ParseFromString(file.open("rb").read()) serialized_instr_context = fixture.input.SerializeToString(deterministic=True) else: - serialized_instr_context = read_context_serialized(file) + serialized_instr_context = read_context_serialized(harness_ctx, file) assert serialized_instr_context is not None, f"Unable to read {file.name}" return serialized_instr_context @@ -349,7 +404,12 @@ def run_test(test_file: Path) -> tuple[str, int, dict | None]: - Dictionary of target library names and file-dumpable serialized instruction effects """ # Process fixtures through this entrypoint as well - context = serialize_context(test_file) - results = process_single_test_case(context) - pruned_results = globals.harness_ctx.prune_effects_fn(context, results) - return test_file.stem, *build_test_results(pruned_results) + if test_file.suffix == ".fix": + fn_entrypoint = extract_metadata(test_file).fn_entrypoint + harness_ctx = HARNESS_ENTRYPOINT_MAP[fn_entrypoint] + else: + harness_ctx = globals.default_harness_ctx + context = serialize_context(harness_ctx, test_file) + results = process_single_test_case(harness_ctx, context) + pruned_results = harness_ctx.prune_effects_fn(harness_ctx, context, results) + return test_file.stem, *build_test_results(harness_ctx, pruned_results) diff --git a/src/test_suite/shred_pb2.py b/src/test_suite/shred_pb2.py new file mode 100644 index 0000000..cae617e --- /dev/null +++ b/src/test_suite/shred_pb2.py @@ -0,0 +1,459 @@ +import sys + +_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 + +_sym_db = _symbol_database.Default() +DESCRIPTOR = _descriptor.FileDescriptor( + name="shred.proto", + package="org.solana.sealevel.v1", + syntax="proto3", + serialized_pb=_b( + '\n\x0bshred.proto\x12\x16org.solana.sealevel.v1"\x1b\n\x0bShredBinary\x12\x0c\n\x04data\x18\x01 \x01(\x0c"=\n\nDataHeader\x12\x12\n\nparent_off\x18\x01 \x01(\r\x12\r\n\x05flags\x18\x02 \x01(\r\x12\x0c\n\x04size\x18\x03 \x01(\r"=\n\nCodeHeader\x12\x10\n\x08data_cnt\x18\x01 \x01(\r\x12\x10\n\x08code_cnt\x18\x02 \x01(\r\x12\x0b\n\x03idx\x18\x03 \x01(\r"è\x01\n\x0bParsedShred\x12\x11\n\tsignature\x18\x01 \x01(\t\x12\x0f\n\x07variant\x18\x02 \x01(\r\x12\x0c\n\x04slot\x18\x03 \x01(\x04\x12\x0b\n\x03idx\x18\x04 \x01(\r\x12\x0f\n\x07version\x18\x05 \x01(\r\x12\x13\n\x0bfec_set_idx\x18\x06 \x01(\r\x122\n\x04data\x18\x07 \x01(\x0b2".org.solana.sealevel.v1.DataHeaderH\x00\x122\n\x04code\x18\x08 \x01(\x0b2".org.solana.sealevel.v1.CodeHeaderH\x00B\x0c\n\nshred_type"\x1d\n\x0cAcceptsShred\x12\r\n\x05valid\x18\x01 \x01(\x08b\x06proto3' + ), +) +_SHREDBINARY = _descriptor.Descriptor( + name="ShredBinary", + full_name="org.solana.sealevel.v1.ShredBinary", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="data", + full_name="org.solana.sealevel.v1.ShredBinary.data", + index=0, + number=1, + type=12, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b(""), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ) + ], + extensions=[], + nested_types=[], + enum_types=[], + options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=39, + serialized_end=66, +) +_DATAHEADER = _descriptor.Descriptor( + name="DataHeader", + full_name="org.solana.sealevel.v1.DataHeader", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="parent_off", + full_name="org.solana.sealevel.v1.DataHeader.parent_off", + index=0, + number=1, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="flags", + full_name="org.solana.sealevel.v1.DataHeader.flags", + index=1, + number=2, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="size", + full_name="org.solana.sealevel.v1.DataHeader.size", + index=2, + number=3, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=68, + serialized_end=129, +) +_CODEHEADER = _descriptor.Descriptor( + name="CodeHeader", + full_name="org.solana.sealevel.v1.CodeHeader", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="data_cnt", + full_name="org.solana.sealevel.v1.CodeHeader.data_cnt", + index=0, + number=1, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="code_cnt", + full_name="org.solana.sealevel.v1.CodeHeader.code_cnt", + index=1, + number=2, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="idx", + full_name="org.solana.sealevel.v1.CodeHeader.idx", + index=2, + number=3, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=131, + serialized_end=192, +) +_PARSEDSHRED = _descriptor.Descriptor( + name="ParsedShred", + full_name="org.solana.sealevel.v1.ParsedShred", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="signature", + full_name="org.solana.sealevel.v1.ParsedShred.signature", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="variant", + full_name="org.solana.sealevel.v1.ParsedShred.variant", + index=1, + number=2, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="slot", + full_name="org.solana.sealevel.v1.ParsedShred.slot", + index=2, + number=3, + type=4, + cpp_type=4, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="idx", + full_name="org.solana.sealevel.v1.ParsedShred.idx", + index=3, + number=4, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="version", + full_name="org.solana.sealevel.v1.ParsedShred.version", + index=4, + number=5, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="fec_set_idx", + full_name="org.solana.sealevel.v1.ParsedShred.fec_set_idx", + index=5, + number=6, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="data", + full_name="org.solana.sealevel.v1.ParsedShred.data", + index=6, + number=7, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="code", + full_name="org.solana.sealevel.v1.ParsedShred.code", + index=7, + number=8, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name="shred_type", + full_name="org.solana.sealevel.v1.ParsedShred.shred_type", + index=0, + containing_type=None, + fields=[], + ) + ], + serialized_start=195, + serialized_end=427, +) +_ACCEPTSSHRED = _descriptor.Descriptor( + name="AcceptsShred", + full_name="org.solana.sealevel.v1.AcceptsShred", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="valid", + full_name="org.solana.sealevel.v1.AcceptsShred.valid", + index=0, + number=1, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ) + ], + extensions=[], + nested_types=[], + enum_types=[], + options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=429, + serialized_end=458, +) +_PARSEDSHRED.fields_by_name["data"].message_type = _DATAHEADER +_PARSEDSHRED.fields_by_name["code"].message_type = _CODEHEADER +_PARSEDSHRED.oneofs_by_name["shred_type"].fields.append( + _PARSEDSHRED.fields_by_name["data"] +) +_PARSEDSHRED.fields_by_name["data"].containing_oneof = _PARSEDSHRED.oneofs_by_name[ + "shred_type" +] +_PARSEDSHRED.oneofs_by_name["shred_type"].fields.append( + _PARSEDSHRED.fields_by_name["code"] +) +_PARSEDSHRED.fields_by_name["code"].containing_oneof = _PARSEDSHRED.oneofs_by_name[ + "shred_type" +] +DESCRIPTOR.message_types_by_name["ShredBinary"] = _SHREDBINARY +DESCRIPTOR.message_types_by_name["DataHeader"] = _DATAHEADER +DESCRIPTOR.message_types_by_name["CodeHeader"] = _CODEHEADER +DESCRIPTOR.message_types_by_name["ParsedShred"] = _PARSEDSHRED +DESCRIPTOR.message_types_by_name["AcceptsShred"] = _ACCEPTSSHRED +_sym_db.RegisterFileDescriptor(DESCRIPTOR) +ShredBinary = _reflection.GeneratedProtocolMessageType( + "ShredBinary", + (_message.Message,), + dict(DESCRIPTOR=_SHREDBINARY, __module__="shred_pb2"), +) +_sym_db.RegisterMessage(ShredBinary) +DataHeader = _reflection.GeneratedProtocolMessageType( + "DataHeader", + (_message.Message,), + dict(DESCRIPTOR=_DATAHEADER, __module__="shred_pb2"), +) +_sym_db.RegisterMessage(DataHeader) +CodeHeader = _reflection.GeneratedProtocolMessageType( + "CodeHeader", + (_message.Message,), + dict(DESCRIPTOR=_CODEHEADER, __module__="shred_pb2"), +) +_sym_db.RegisterMessage(CodeHeader) +ParsedShred = _reflection.GeneratedProtocolMessageType( + "ParsedShred", + (_message.Message,), + dict(DESCRIPTOR=_PARSEDSHRED, __module__="shred_pb2"), +) +_sym_db.RegisterMessage(ParsedShred) +AcceptsShred = _reflection.GeneratedProtocolMessageType( + "AcceptsShred", + (_message.Message,), + dict(DESCRIPTOR=_ACCEPTSSHRED, __module__="shred_pb2"), +) +_sym_db.RegisterMessage(AcceptsShred) diff --git a/src/test_suite/test_suite.py b/src/test_suite/test_suite.py index 5eb5bef..4e6f8d4 100644 --- a/src/test_suite/test_suite.py +++ b/src/test_suite/test_suite.py @@ -3,6 +3,7 @@ import typer from collections import defaultdict import ctypes +from itertools import repeat from glob import glob from multiprocessing import Pool from pathlib import Path @@ -16,14 +17,14 @@ ) from test_suite.multiprocessing_utils import ( decode_single_test_case, - read_context_serialized, + extract_metadata, + read_fixture, initialize_process_output_buffers, process_target, run_test, serialize_context, ) import test_suite.globals as globals -from test_suite.debugger import debug_host from test_suite.util import set_ld_preload_asan import resource import tqdm @@ -44,27 +45,23 @@ - ValidateVM - ElfHarness """ -harness_type = os.getenv("HARNESS_TYPE") -if harness_type: - globals.harness_ctx = eval(harness_type) -else: - globals.harness_ctx = InstrHarness - harness_type = "InstrHarness" - -app = typer.Typer( - help=f"Validate effects from clients using {globals.harness_ctx.context_type.__name__} Protobuf messages." -) +app = typer.Typer(help=f"Validate effects from clients using Protobuf messages.") -@app.command( - help=f"Execute {globals.harness_ctx.context_type.__name__} message(s) and print the effects." -) -def exec_instr( - file_or_dir: Path = typer.Option( + +@app.command(help=f"Execute Context or Fixture message(s) and print the Effects.") +def execute( + input: Path = typer.Option( None, "--input", "-i", - help=f"Input {globals.harness_ctx.context_type.__name__} file or directory of files", + help=f"Input protobuf file or directory of protobuf files", + ), + default_harness_ctx: str = typer.Option( + "InstrHarness", + "--default-harness-type", + "-h", + help=f"Harness type to use for Context protobufs", ), shared_library: Path = typer.Option( Path("impl/firedancer/build/native/clang/lib/libfd_exec_sol_compat.so"), @@ -89,83 +86,68 @@ def exec_instr( initialize_process_output_buffers(randomize_output_buffer=randomize_output_buffer) try: lib = ctypes.CDLL(shared_library) + lib.sol_compat_init() + globals.target_libraries[shared_library] = lib + globals.reference_shared_library = shared_library except: set_ld_preload_asan() lib.sol_compat_init(log_level) - files_to_exec = file_or_dir.iterdir() if file_or_dir.is_dir() else [file_or_dir] + files_to_exec = input.iterdir() if input.is_dir() else [input] for file in files_to_exec: print(f"Handling {file}...") + if file.suffix == ".fix": + fn_entrypoint = extract_metadata(file).fn_entrypoint + harness_ctx = HARNESS_ENTRYPOINT_MAP[fn_entrypoint] + else: + harness_ctx = eval(default_harness_ctx) # Execute and cleanup - context = serialize_context(file) + context = serialize_context(harness_ctx, file) start = time.time() - effects = process_target(lib, context) + effects = process_target(harness_ctx, lib, context) end = time.time() print(f"Total time taken for {file}: {(end - start) * 1000} ms\n------------") if not effects: - print(f"No {globals.harness_ctx.effects_type.__name__} returned") + print(f"No {harness_ctx.effects_type.__name__} returned") continue serialized_effects = effects.SerializeToString(deterministic=True) # Prune execution results - serialized_effects = globals.harness_ctx.prune_effects_fn( + serialized_effects = harness_ctx.prune_effects_fn( + harness_ctx, context, {shared_library: serialized_effects}, )[shared_library] - parsed_instruction_effects = globals.harness_ctx.effects_type() + parsed_instruction_effects = harness_ctx.effects_type() parsed_instruction_effects.ParseFromString(serialized_effects) # Print human-readable output if parsed_instruction_effects: - globals.harness_ctx.effects_human_encode_fn(parsed_instruction_effects) + harness_ctx.effects_human_encode_fn(parsed_instruction_effects) print(parsed_instruction_effects) lib.sol_compat_fini() -@app.command() -def debug_instr( - file: Path = typer.Option(None, "--input", "-i", help="Input file"), - shared_library: Path = typer.Option( - Path("impl/lib/libsolfuzz_firedancer.so"), - "--target", - "-t", - help="Shared object (.so) target file path to debug", - ), - debugger: str = typer.Option( - "gdb", "--debugger", "-d", help="Debugger to use (gdb, rust-gdb)" - ), -): - print("-" * LOG_FILE_SEPARATOR_LENGTH) - print(f"Processing {file.name}...") - - # Decode the file and pass it into GDB - instruction_context = read_context_serialized(file) - assert instruction_context is not None, f"Unable to read {file.name}" - debug_host(shared_library, instruction_context, gdb=debugger) - - -@app.command( - help=f"Extract {globals.harness_ctx.context_type.__name__} messages from fixtures." -) -def instr_from_fixtures( - input_dir: Path = typer.Option( +@app.command(help=f"Extract Context messages from Fixtures.") +def fix_to_ctx( + input: Path = typer.Option( Path("fixtures"), - "--input-dir", + "--input", "-i", - help=f"Input directory containing {globals.harness_ctx.fixture_type.__name__} messages", + help=f"Input Fixture file or directory of Fixture files", ), output_dir: Path = typer.Option( Path("instr"), "--output-dir", "-o", - help=f"Output directory for {globals.harness_ctx.context_type.__name__} messages", + help=f"Output directory for messages", ), num_processes: int = typer.Option( 4, "--num-processes", "-p", help="Number of processes to use" @@ -179,10 +161,10 @@ def instr_from_fixtures( shutil.rmtree(globals.output_dir) globals.output_dir.mkdir(parents=True, exist_ok=True) - test_cases = list(input_dir.iterdir()) + test_cases = input.iterdir() if input.is_dir() else [input] num_test_cases = len(test_cases) - print(f"Converting to {globals.harness_ctx.context_type.__name__}...") + print(f"Converting to Fixture messages...") results = [] with Pool(processes=num_processes) as pool: for result in tqdm.tqdm( @@ -198,18 +180,24 @@ def instr_from_fixtures( @app.command( help=f""" - Create test fixtures from a directory of {globals.harness_ctx.context_type.__name__} messages. + Create test fixtures from a directory of Context and/or Fixture messages. Effects are generated by the target passed in with --solana-target or -s. You can also pass in additional targets with --target or -t and use --keep-passing or -k to only generate effects for test cases that match. """ ) def create_fixtures( - input_path: Path = typer.Option( + input: Path = typer.Option( Path("corpus8"), - "--input-dir", + "--input", "-i", - help=f"Either a file or directory containing {globals.harness_ctx.context_type.__name__} messages", + help=f"Input protobuf file or directory of protobuf files", + ), + default_harness_ctx: str = typer.Option( + "InstrHarness", + "--default-harness-type", + "-h", + help=f"Harness type to use for Context protobufs", ), reference_shared_library: Path = typer.Option( Path(os.getenv("SOLFUZZ_TARGET", "impl/lib/libsolfuzz_agave_v2.0.so")), @@ -222,7 +210,7 @@ def create_fixtures( "--target", "-t", help="Shared object (.so) target file paths (pairs with --keep-passing)." - f" Targets must have {globals.harness_ctx.fuzz_fn_name} defined", + f" Targets must have required function entrypoints defined", ), output_dir: Path = typer.Option( Path("test_fixtures"), @@ -271,8 +259,9 @@ def create_fixtures( lib.sol_compat_init(log_level) globals.target_libraries[target] = lib - test_cases = [input_path] if input_path.is_file() else list(input_path.iterdir()) + test_cases = [input] if input.is_file() else list(input.iterdir()) num_test_cases = len(test_cases) + globals.default_harness_ctx = eval(default_harness_ctx) # Generate the test cases in parallel from files on disk print("Creating fixtures...") @@ -301,19 +290,23 @@ def create_fixtures( @app.command( help=f""" - Run tests on a set of targets with a directory of {globals.harness_ctx.context_type.__name__} - or {globals.harness_ctx.fixture_type.__name__} messages. + Run tests on a set of targets with a directory of Context and/or Fixture messages. Note: each `.so` target filename must be unique. """ ) def run_tests( - file_or_dir: Path = typer.Option( + input: Path = typer.Option( Path("corpus8"), "--input", "-i", - help=f"Single input file or input directory containing {globals.harness_ctx.context_type.__name__}" - f" or { globals.harness_ctx.fixture_type.__name__ } messages", + help=f"Input protobuf file or directory of protobuf files", + ), + default_harness_ctx: str = typer.Option( + "InstrHarness", + "--default-harness-type", + "-h", + help=f"Harness type to use for Context protobufs", ), reference_shared_library: Path = typer.Option( Path(os.getenv("SOLFUZZ_TARGET", "impl/lib/libsolfuzz_agave_v2.0.so")), @@ -382,12 +375,10 @@ def run_tests( # Specify globals globals.output_dir = output_dir globals.reference_shared_library = reference_shared_library + globals.default_harness_ctx = eval(default_harness_ctx) # Set diff mode to consensus if specified - if consensus_mode: - globals.harness_ctx.diff_effect_fn = ( - globals.harness_ctx.consensus_diff_effect_fn - ) + globals.consensus_mode = consensus_mode # Create the output directory, if necessary if globals.output_dir.exists(): @@ -410,7 +401,7 @@ def run_tests( failed_protobufs_dir = globals.output_dir / "failed_protobufs" failed_protobufs_dir.mkdir(parents=True, exist_ok=True) - test_cases = list(file_or_dir.iterdir()) if file_or_dir.is_dir() else [file_or_dir] + test_cases = list(input.iterdir()) if input.is_dir() else [input] num_test_cases = len(test_cases) # Process the test results in parallel @@ -486,25 +477,29 @@ def run_tests( print("Failures tests are in: ", globals.output_dir / "failed_protobufs") -@app.command( - help=f"Convert {globals.harness_ctx.context_type.__name__} messages to human-readable format." -) -def decode_protobuf( - input_path: Path = typer.Option( +@app.command(help=f"Convert Context and/or Fixture messages to human-readable format.") +def decode_protobufs( + input: Path = typer.Option( Path("raw_context"), "--input", "-i", - help=f"Either a {globals.harness_ctx.context_type.__name__} message or directory of messages", + help=f"Input protobuf file or directory of protobuf files", ), output_dir: Path = typer.Option( Path("readable_context"), "--output-dir", "-o", - help=f"Output directory for base58-encoded, human-readable {globals.harness_ctx.context_type.__name__} messages", + help=f"Output directory for base58-encoded, Context and/or Fixture human-readable messages", ), num_processes: int = typer.Option( 4, "--num-processes", "-p", help="Number of processes to use" ), + default_harness_ctx: str = typer.Option( + "InstrHarness", + "--default-harness-type", + "-h", + help=f"Harness type to use for Context protobufs", + ), ): globals.output_dir = output_dir @@ -512,19 +507,15 @@ def decode_protobuf( if globals.output_dir.exists(): shutil.rmtree(globals.output_dir) globals.output_dir.mkdir(parents=True, exist_ok=True) + globals.default_harness_ctx = eval(default_harness_ctx) - if not input_path.is_dir(): - ok = decode_single_test_case(input_path) - if not ok: - print(f"Error decoding {input_path}") - return - - num_test_cases = len(list(input_path.iterdir())) + test_cases = list(input.iterdir()) if input.is_dir() else [input] + num_test_cases = len(test_cases) write_results = [] with Pool(processes=num_processes) as pool: for result in tqdm.tqdm( - pool.imap(decode_single_test_case, input_path.iterdir()), + pool.imap(decode_single_test_case, test_cases), total=num_test_cases, ): write_results.append(result) @@ -537,13 +528,9 @@ def decode_protobuf( @app.command(help=f"List harness types available for use.") def list_harness_types(): # pretty print harness types - print(f"Currently set harness type: {harness_type}\n") - print("Available harness types:") for name in HARNESS_LIST: print(f"- {name}") - print("\nTo use, export the harness type to HARNESS_TYPE env var. Example:") - print(f"export HARNESS_TYPE={HARNESS_LIST[0]}") @app.command( @@ -565,13 +552,13 @@ def debug_mismatches( "--target", "-t", help="Shared object (.so) target file paths (pairs with --keep-passing)." - f" Targets must have {globals.harness_ctx.fuzz_fn_name} defined", + f" Targets must have required function entrypoints defined", ), output_dir: Path = typer.Option( Path("debug_mismatch"), "--output-dir", "-o", - help=f"Output directory for {globals.harness_ctx.context_type.__name__} messages", + help=f"Output directory for messages", ), repro_urls: str = typer.Option( "", "--repro-urls", "-u", help="Comma-delimited list of FuzzCorp mismatch links" @@ -703,13 +690,13 @@ def debug_non_repros( "--target", "-t", help="Shared object (.so) target file paths (pairs with --keep-passing)." - f" Targets must have {globals.harness_ctx.fuzz_fn_name} defined", + f" Targets must have required function entrypoints defined", ), output_dir: Path = typer.Option( Path("debug_mismatch"), "--output-dir", "-o", - help=f"Output directory for {globals.harness_ctx.context_type.__name__} messages", + help=f"Output directory for messages", ), repro_urls: str = typer.Option( "", "--repro-urls", "-u", help="Comma-delimited list of FuzzCorp mismatch links" @@ -789,10 +776,6 @@ def debug_non_repros( text=True, ) - import pdb - - pdb.set_trace - run_tests( file_or_dir=globals.inputs_dir, reference_shared_library=reference_shared_library, @@ -810,16 +793,15 @@ def debug_non_repros( @app.command( help=f""" - Regenerate {globals.harness_ctx.fixture_type.__name__} messages by - checking FeatureSet compatibility with the target shared library. + Regenerate Fixture messages by checking FeatureSet compatibility with the target shared library. """ ) def regenerate_fixtures( input_path: Path = typer.Option( Path("corpus8"), - "--input-dir", + "--input", "-i", - help=f"Either a file or directory containing {globals.harness_ctx.fixture_type.__name__} messages", + help=f"Either a file or directory containing messages", ), shared_library: Path = typer.Option( Path(os.getenv("SOLFUZZ_TARGET", "impl/lib/libsolfuzz_agave_v2.0.so")), @@ -864,22 +846,21 @@ def regenerate_fixtures( globals.target_libraries[shared_library] = lib initialize_process_output_buffers() - target_features = features_utils.get_sol_compat_features_t(lib) - features_path = pb_utils.find_field_with_type( - globals.harness_ctx.context_type.DESCRIPTOR, context_pb.FeatureSet.DESCRIPTOR - ) - - # TODO: support multiple FeatureSet fields - assert len(features_path) == 1, "Only one FeatureSet field is supported" - features_path = features_path[0] - test_cases = list(input_path.iterdir()) if input_path.is_dir() else [input_path] num_regenerated = 0 for file in test_cases: - fixture = globals.harness_ctx.fixture_type() - with open(file, "rb") as f: - fixture.ParseFromString(f.read()) + fixture = read_fixture(file) + harness_ctx = HARNESS_ENTRYPOINT_MAP[fixture.metadata.fn_entrypoint] + + target_features = features_utils.get_sol_compat_features_t(lib) + features_path = pb_utils.find_field_with_type( + harness_ctx.context_type.DESCRIPTOR, context_pb.FeatureSet.DESCRIPTOR + ) + + # TODO: support multiple FeatureSet fields + assert len(features_path) == 1, "Only one FeatureSet field is supported" + features_path = features_path[0] features = pb_utils.access_nested_field_safe(fixture.input, features_path) feature_set = set(features.features) if features else set() @@ -901,9 +882,13 @@ def regenerate_fixtures( features.features[:] = features_utils.min_compatible_featureset( target_features, feature_set ) - regenerated_fixture = create_fixture_from_context(fixture.input) + regenerated_fixture = create_fixture_from_context( + harness_ctx, fixture.input + ) write_fixture_to_disk( - file.stem, regenerated_fixture.SerializeToString() + harness_ctx, + file.stem, + regenerated_fixture.SerializeToString(), ) lib.sol_compat_fini() @@ -918,7 +903,7 @@ def regenerate_fixtures( def regenerate_all_fixtures( test_vectors: Path = typer.Option( Path("corpus8"), - "--input-dir", + "--input", "-i", help=f"Input test-vectors directory", ), @@ -983,8 +968,6 @@ def get_harness_type_for_folder(src, regenerate_folder): output_dir, os.path.relpath(source_folder, test_vectors) ) folder_harness_type = get_harness_type_for_folder(test_vectors, source_folder) - os.environ["HARNESS_TYPE"] = folder_harness_type - globals.harness_ctx = eval(folder_harness_type) print( f"Regenerating fixtures for {source_folder} with harness type {folder_harness_type}" ) diff --git a/src/test_suite/txn_pb2.py b/src/test_suite/txn_pb2.py index 50b62a8..6400c6c 100644 --- a/src/test_suite/txn_pb2.py +++ b/src/test_suite/txn_pb2.py @@ -9,15 +9,16 @@ _sym_db = _symbol_database.Default() from . import context_pb2 as context__pb2 +from . import metadata_pb2 as metadata__pb2 DESCRIPTOR = _descriptor.FileDescriptor( name="txn.proto", package="org.solana.sealevel.v1", syntax="proto3", serialized_pb=_b( - '\n\ttxn.proto\x12\x16org.solana.sealevel.v1\x1a\rcontext.proto"~\n\rMessageHeader\x12\x1f\n\x17num_required_signatures\x18\x01 \x01(\r\x12$\n\x1cnum_readonly_signed_accounts\x18\x02 \x01(\r\x12&\n\x1enum_readonly_unsigned_accounts\x18\x03 \x01(\r"O\n\x13CompiledInstruction\x12\x18\n\x10program_id_index\x18\x01 \x01(\r\x12\x10\n\x08accounts\x18\x02 \x03(\r\x12\x0c\n\x04data\x18\x03 \x01(\x0c"d\n\x19MessageAddressTableLookup\x12\x13\n\x0baccount_key\x18\x01 \x01(\x0c\x12\x18\n\x10writable_indexes\x18\x02 \x03(\r\x12\x18\n\x10readonly_indexes\x18\x03 \x03(\r"ã\x02\n\x12TransactionMessage\x12\x11\n\tis_legacy\x18\x01 \x01(\x08\x125\n\x06header\x18\x02 \x01(\x0b2%.org.solana.sealevel.v1.MessageHeader\x12\x14\n\x0caccount_keys\x18\x03 \x03(\x0c\x12>\n\x13account_shared_data\x18\x04 \x03(\x0b2!.org.solana.sealevel.v1.AcctState\x12\x18\n\x10recent_blockhash\x18\x05 \x01(\x0c\x12A\n\x0cinstructions\x18\x06 \x03(\x0b2+.org.solana.sealevel.v1.CompiledInstruction\x12P\n\x15address_table_lookups\x18\x07 \x03(\x0b21.org.solana.sealevel.v1.MessageAddressTableLookup"\x98\x01\n\x14SanitizedTransaction\x12;\n\x07message\x18\x01 \x01(\x0b2*.org.solana.sealevel.v1.TransactionMessage\x12\x14\n\x0cmessage_hash\x18\x02 \x01(\x0c\x12\x19\n\x11is_simple_vote_tx\x18\x03 \x01(\x08\x12\x12\n\nsignatures\x18\x04 \x03(\x0c"à\x01\n\nTxnContext\x128\n\x02tx\x18\x01 \x01(\x0b2,.org.solana.sealevel.v1.SanitizedTransaction\x12\x0f\n\x07max_age\x18\x02 \x01(\x04\x12\x17\n\x0fblockhash_queue\x18\x03 \x03(\x0c\x127\n\tepoch_ctx\x18\x04 \x01(\x0b2$.org.solana.sealevel.v1.EpochContext\x125\n\x08slot_ctx\x18\x05 \x01(\x0b2#.org.solana.sealevel.v1.SlotContext"\x9b\x01\n\x0eResultingState\x126\n\x0bacct_states\x18\x01 \x03(\x0b2!.org.solana.sealevel.v1.AcctState\x127\n\x0brent_debits\x18\x02 \x03(\x0b2".org.solana.sealevel.v1.RentDebits\x12\x18\n\x10transaction_rent\x18\x03 \x01(\x04"4\n\nRentDebits\x12\x0e\n\x06pubkey\x18\x01 \x01(\x0c\x12\x16\n\x0erent_collected\x18\x02 \x01(\x03"A\n\nFeeDetails\x12\x17\n\x0ftransaction_fee\x18\x01 \x01(\x04\x12\x1a\n\x12prioritization_fee\x18\x02 \x01(\x04"ß\x02\n\tTxnResult\x12\x10\n\x08executed\x18\x01 \x01(\x08\x12\x1a\n\x12sanitization_error\x18\x02 \x01(\x08\x12?\n\x0fresulting_state\x18\x03 \x01(\x0b2&.org.solana.sealevel.v1.ResultingState\x12\x0c\n\x04rent\x18\x04 \x01(\x04\x12\r\n\x05is_ok\x18\x05 \x01(\x08\x12\x0e\n\x06status\x18\x06 \x01(\r\x12\x19\n\x11instruction_error\x18\x07 \x01(\r\x12\x1f\n\x17instruction_error_index\x18\x08 \x01(\r\x12\x14\n\x0ccustom_error\x18\t \x01(\r\x12\x13\n\x0breturn_data\x18\n \x01(\x0c\x12\x16\n\x0eexecuted_units\x18\x0b \x01(\x04\x127\n\x0bfee_details\x18\x0c \x01(\x0b2".org.solana.sealevel.v1.FeeDetails"r\n\nTxnFixture\x121\n\x05input\x18\x01 \x01(\x0b2".org.solana.sealevel.v1.TxnContext\x121\n\x06output\x18\x02 \x01(\x0b2!.org.solana.sealevel.v1.TxnResultb\x06proto3' + '\n\ttxn.proto\x12\x16org.solana.sealevel.v1\x1a\rcontext.proto\x1a\x0emetadata.proto"~\n\rMessageHeader\x12\x1f\n\x17num_required_signatures\x18\x01 \x01(\r\x12$\n\x1cnum_readonly_signed_accounts\x18\x02 \x01(\r\x12&\n\x1enum_readonly_unsigned_accounts\x18\x03 \x01(\r"O\n\x13CompiledInstruction\x12\x18\n\x10program_id_index\x18\x01 \x01(\r\x12\x10\n\x08accounts\x18\x02 \x03(\r\x12\x0c\n\x04data\x18\x03 \x01(\x0c"d\n\x19MessageAddressTableLookup\x12\x13\n\x0baccount_key\x18\x01 \x01(\x0c\x12\x18\n\x10writable_indexes\x18\x02 \x03(\r\x12\x18\n\x10readonly_indexes\x18\x03 \x03(\r"ã\x02\n\x12TransactionMessage\x12\x11\n\tis_legacy\x18\x01 \x01(\x08\x125\n\x06header\x18\x02 \x01(\x0b2%.org.solana.sealevel.v1.MessageHeader\x12\x14\n\x0caccount_keys\x18\x03 \x03(\x0c\x12>\n\x13account_shared_data\x18\x04 \x03(\x0b2!.org.solana.sealevel.v1.AcctState\x12\x18\n\x10recent_blockhash\x18\x05 \x01(\x0c\x12A\n\x0cinstructions\x18\x06 \x03(\x0b2+.org.solana.sealevel.v1.CompiledInstruction\x12P\n\x15address_table_lookups\x18\x07 \x03(\x0b21.org.solana.sealevel.v1.MessageAddressTableLookup"\x98\x01\n\x14SanitizedTransaction\x12;\n\x07message\x18\x01 \x01(\x0b2*.org.solana.sealevel.v1.TransactionMessage\x12\x14\n\x0cmessage_hash\x18\x02 \x01(\x0c\x12\x19\n\x11is_simple_vote_tx\x18\x03 \x01(\x08\x12\x12\n\nsignatures\x18\x04 \x03(\x0c"à\x01\n\nTxnContext\x128\n\x02tx\x18\x01 \x01(\x0b2,.org.solana.sealevel.v1.SanitizedTransaction\x12\x0f\n\x07max_age\x18\x02 \x01(\x04\x12\x17\n\x0fblockhash_queue\x18\x03 \x03(\x0c\x127\n\tepoch_ctx\x18\x04 \x01(\x0b2$.org.solana.sealevel.v1.EpochContext\x125\n\x08slot_ctx\x18\x05 \x01(\x0b2#.org.solana.sealevel.v1.SlotContext"\x9b\x01\n\x0eResultingState\x126\n\x0bacct_states\x18\x01 \x03(\x0b2!.org.solana.sealevel.v1.AcctState\x127\n\x0brent_debits\x18\x02 \x03(\x0b2".org.solana.sealevel.v1.RentDebits\x12\x18\n\x10transaction_rent\x18\x03 \x01(\x04"4\n\nRentDebits\x12\x0e\n\x06pubkey\x18\x01 \x01(\x0c\x12\x16\n\x0erent_collected\x18\x02 \x01(\x03"A\n\nFeeDetails\x12\x17\n\x0ftransaction_fee\x18\x01 \x01(\x04\x12\x1a\n\x12prioritization_fee\x18\x02 \x01(\x04"ß\x02\n\tTxnResult\x12\x10\n\x08executed\x18\x01 \x01(\x08\x12\x1a\n\x12sanitization_error\x18\x02 \x01(\x08\x12?\n\x0fresulting_state\x18\x03 \x01(\x0b2&.org.solana.sealevel.v1.ResultingState\x12\x0c\n\x04rent\x18\x04 \x01(\x04\x12\r\n\x05is_ok\x18\x05 \x01(\x08\x12\x0e\n\x06status\x18\x06 \x01(\r\x12\x19\n\x11instruction_error\x18\x07 \x01(\r\x12\x1f\n\x17instruction_error_index\x18\x08 \x01(\r\x12\x14\n\x0ccustom_error\x18\t \x01(\r\x12\x13\n\x0breturn_data\x18\n \x01(\x0c\x12\x16\n\x0eexecuted_units\x18\x0b \x01(\x04\x127\n\x0bfee_details\x18\x0c \x01(\x0b2".org.solana.sealevel.v1.FeeDetails"\xad\x01\n\nTxnFixture\x129\n\x08metadata\x18\x01 \x01(\x0b2\'.org.solana.sealevel.v1.FixtureMetadata\x121\n\x05input\x18\x02 \x01(\x0b2".org.solana.sealevel.v1.TxnContext\x121\n\x06output\x18\x03 \x01(\x0b2!.org.solana.sealevel.v1.TxnResultb\x06proto3' ), - dependencies=[context__pb2.DESCRIPTOR], + dependencies=[context__pb2.DESCRIPTOR, metadata__pb2.DESCRIPTOR], ) _MESSAGEHEADER = _descriptor.Descriptor( name="MessageHeader", @@ -89,8 +90,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=52, - serialized_end=178, + serialized_start=68, + serialized_end=194, ) _COMPILEDINSTRUCTION = _descriptor.Descriptor( name="CompiledInstruction", @@ -162,8 +163,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=180, - serialized_end=259, + serialized_start=196, + serialized_end=275, ) _MESSAGEADDRESSTABLELOOKUP = _descriptor.Descriptor( name="MessageAddressTableLookup", @@ -235,8 +236,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=261, - serialized_end=361, + serialized_start=277, + serialized_end=377, ) _TRANSACTIONMESSAGE = _descriptor.Descriptor( name="TransactionMessage", @@ -380,8 +381,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=364, - serialized_end=719, + serialized_start=380, + serialized_end=735, ) _SANITIZEDTRANSACTION = _descriptor.Descriptor( name="SanitizedTransaction", @@ -471,8 +472,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=722, - serialized_end=874, + serialized_start=738, + serialized_end=890, ) _TXNCONTEXT = _descriptor.Descriptor( name="TxnContext", @@ -580,8 +581,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=877, - serialized_end=1101, + serialized_start=893, + serialized_end=1117, ) _RESULTINGSTATE = _descriptor.Descriptor( name="ResultingState", @@ -653,8 +654,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1104, - serialized_end=1259, + serialized_start=1120, + serialized_end=1275, ) _RENTDEBITS = _descriptor.Descriptor( name="RentDebits", @@ -708,8 +709,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1261, - serialized_end=1313, + serialized_start=1277, + serialized_end=1329, ) _FEEDETAILS = _descriptor.Descriptor( name="FeeDetails", @@ -763,8 +764,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1315, - serialized_end=1380, + serialized_start=1331, + serialized_end=1396, ) _TXNRESULT = _descriptor.Descriptor( name="TxnResult", @@ -998,8 +999,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1383, - serialized_end=1734, + serialized_start=1399, + serialized_end=1750, ) _TXNFIXTURE = _descriptor.Descriptor( name="TxnFixture", @@ -1009,8 +1010,8 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="input", - full_name="org.solana.sealevel.v1.TxnFixture.input", + name="metadata", + full_name="org.solana.sealevel.v1.TxnFixture.metadata", index=0, number=1, type=11, @@ -1027,8 +1028,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="output", - full_name="org.solana.sealevel.v1.TxnFixture.output", + name="input", + full_name="org.solana.sealevel.v1.TxnFixture.input", index=1, number=2, type=11, @@ -1044,6 +1045,24 @@ options=None, file=DESCRIPTOR, ), + _descriptor.FieldDescriptor( + name="output", + full_name="org.solana.sealevel.v1.TxnFixture.output", + index=2, + number=3, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), ], extensions=[], nested_types=[], @@ -1053,8 +1072,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1736, - serialized_end=1850, + serialized_start=1753, + serialized_end=1926, ) _TRANSACTIONMESSAGE.fields_by_name["header"].message_type = _MESSAGEHEADER _TRANSACTIONMESSAGE.fields_by_name["account_shared_data"].message_type = ( @@ -1072,6 +1091,7 @@ _RESULTINGSTATE.fields_by_name["rent_debits"].message_type = _RENTDEBITS _TXNRESULT.fields_by_name["resulting_state"].message_type = _RESULTINGSTATE _TXNRESULT.fields_by_name["fee_details"].message_type = _FEEDETAILS +_TXNFIXTURE.fields_by_name["metadata"].message_type = metadata__pb2._FIXTUREMETADATA _TXNFIXTURE.fields_by_name["input"].message_type = _TXNCONTEXT _TXNFIXTURE.fields_by_name["output"].message_type = _TXNRESULT DESCRIPTOR.message_types_by_name["MessageHeader"] = _MESSAGEHEADER diff --git a/src/test_suite/vm_pb2.py b/src/test_suite/vm_pb2.py index dea4b86..5db92a1 100644 --- a/src/test_suite/vm_pb2.py +++ b/src/test_suite/vm_pb2.py @@ -1,6 +1,7 @@ import sys _b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) +from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection @@ -10,16 +11,51 @@ _sym_db = _symbol_database.Default() from . import invoke_pb2 as invoke__pb2 from . import context_pb2 as context__pb2 +from . import metadata_pb2 as metadata__pb2 DESCRIPTOR = _descriptor.FileDescriptor( name="vm.proto", package="org.solana.sealevel.v1", syntax="proto3", serialized_pb=_b( - '\n\x08vm.proto\x12\x16org.solana.sealevel.v1\x1a\x0cinvoke.proto\x1a\rcontext.proto"G\n\x0fInputDataRegion\x12\x0e\n\x06offset\x18\x01 \x01(\x04\x12\x0f\n\x07content\x18\x02 \x01(\x0c\x12\x13\n\x0bis_writable\x18\x03 \x01(\x08"¸\x03\n\tVmContext\x12\x10\n\x08heap_max\x18\x01 \x01(\x04\x12\x0e\n\x06rodata\x18\x02 \x01(\x0c\x12"\n\x1arodata_text_section_offset\x18\x03 \x01(\x04\x12"\n\x1arodata_text_section_length\x18\x04 \x01(\x04\x12C\n\x12input_data_regions\x18\x05 \x03(\x0b2\'.org.solana.sealevel.v1.InputDataRegion\x12\n\n\x02r0\x18\x06 \x01(\x04\x12\n\n\x02r1\x18\x07 \x01(\x04\x12\n\n\x02r2\x18\x08 \x01(\x04\x12\n\n\x02r3\x18\t \x01(\x04\x12\n\n\x02r4\x18\n \x01(\x04\x12\n\n\x02r5\x18\x0b \x01(\x04\x12\n\n\x02r6\x18\x0c \x01(\x04\x12\n\n\x02r7\x18\r \x01(\x04\x12\n\n\x02r8\x18\x0e \x01(\x04\x12\n\n\x02r9\x18\x0f \x01(\x04\x12\x0b\n\x03r10\x18\x10 \x01(\x04\x12\x0b\n\x03r11\x18\x11 \x01(\x04\x12\x13\n\x0bcheck_align\x18\x12 \x01(\x08\x12\x12\n\ncheck_size\x18\x13 \x01(\x08\x12\x10\n\x08entry_pc\x18\x14 \x01(\x04\x12\x16\n\x0ecall_whitelist\x18\x15 \x01(\x0c\x12\x17\n\x0ftracing_enabled\x18\x16 \x01(\x08"U\n\x11SyscallInvocation\x12\x15\n\rfunction_name\x18\x01 \x01(\x0c\x12\x13\n\x0bheap_prefix\x18\x02 \x01(\x0c\x12\x14\n\x0cstack_prefix\x18\x03 \x01(\x0c"ÿ\x01\n\x0eSyscallContext\x121\n\x06vm_ctx\x18\x01 \x01(\x0b2!.org.solana.sealevel.v1.VmContext\x127\n\tinstr_ctx\x18\x02 \x01(\x0b2$.org.solana.sealevel.v1.InstrContext\x12E\n\x12syscall_invocation\x18\x03 \x01(\x0b2).org.solana.sealevel.v1.SyscallInvocation\x12:\n\x0cexec_effects\x18\x04 \x01(\x0b2$.org.solana.sealevel.v1.InstrEffects"ð\x01\n\x0eSyscallEffects\x12\r\n\x05error\x18\x01 \x01(\x03\x12\n\n\x02r0\x18\x02 \x01(\x04\x12\x10\n\x08cu_avail\x18\x03 \x01(\x04\x12\x0c\n\x04heap\x18\x04 \x01(\x0c\x12\r\n\x05stack\x18\x05 \x01(\x0c\x12\x11\n\tinputdata\x18\x06 \x01(\x0c\x12C\n\x12input_data_regions\x18\x0b \x03(\x0b2\'.org.solana.sealevel.v1.InputDataRegion\x12\x13\n\x0bframe_count\x18\x07 \x01(\x04\x12\x0b\n\x03log\x18\x08 \x01(\x0c\x12\x0e\n\x06rodata\x18\t \x01(\x0c\x12\n\n\x02pc\x18\n \x01(\x04"\x7f\n\x0eSyscallFixture\x125\n\x05input\x18\x01 \x01(\x0b2&.org.solana.sealevel.v1.SyscallContext\x126\n\x06output\x18\x02 \x01(\x0b2&.org.solana.sealevel.v1.SyscallEffects"x\n\rFullVmContext\x121\n\x06vm_ctx\x18\x01 \x01(\x0b2!.org.solana.sealevel.v1.VmContext\x124\n\x08features\x18\x03 \x01(\x0b2".org.solana.sealevel.v1.FeatureSet"4\n\x11ValidateVmEffects\x12\x0e\n\x06result\x18\x01 \x01(\x05\x12\x0f\n\x07success\x18\x02 \x01(\x08"\x84\x01\n\x11ValidateVmFixture\x124\n\x05input\x18\x01 \x01(\x0b2%.org.solana.sealevel.v1.FullVmContext\x129\n\x06output\x18\x02 \x01(\x0b2).org.solana.sealevel.v1.ValidateVmEffectsb\x06proto3' + '\n\x08vm.proto\x12\x16org.solana.sealevel.v1\x1a\x0cinvoke.proto\x1a\rcontext.proto\x1a\x0emetadata.proto"G\n\x0fInputDataRegion\x12\x0e\n\x06offset\x18\x01 \x01(\x04\x12\x0f\n\x07content\x18\x02 \x01(\x0c\x12\x13\n\x0bis_writable\x18\x03 \x01(\x08"ñ\x03\n\tVmContext\x12\x10\n\x08heap_max\x18\x01 \x01(\x04\x12\x0e\n\x06rodata\x18\x02 \x01(\x0c\x12"\n\x1arodata_text_section_offset\x18\x03 \x01(\x04\x12"\n\x1arodata_text_section_length\x18\x04 \x01(\x04\x12C\n\x12input_data_regions\x18\x05 \x03(\x0b2\'.org.solana.sealevel.v1.InputDataRegion\x12\n\n\x02r0\x18\x06 \x01(\x04\x12\n\n\x02r1\x18\x07 \x01(\x04\x12\n\n\x02r2\x18\x08 \x01(\x04\x12\n\n\x02r3\x18\t \x01(\x04\x12\n\n\x02r4\x18\n \x01(\x04\x12\n\n\x02r5\x18\x0b \x01(\x04\x12\n\n\x02r6\x18\x0c \x01(\x04\x12\n\n\x02r7\x18\r \x01(\x04\x12\n\n\x02r8\x18\x0e \x01(\x04\x12\n\n\x02r9\x18\x0f \x01(\x04\x12\x0b\n\x03r10\x18\x10 \x01(\x04\x12\x0b\n\x03r11\x18\x11 \x01(\x04\x12\x13\n\x0bcheck_align\x18\x12 \x01(\x08\x12\x12\n\ncheck_size\x18\x13 \x01(\x08\x12\x10\n\x08entry_pc\x18\x14 \x01(\x04\x12\x16\n\x0ecall_whitelist\x18\x15 \x01(\x0c\x12\x17\n\x0ftracing_enabled\x18\x16 \x01(\x08\x127\n\x0breturn_data\x18\x17 \x01(\x0b2".org.solana.sealevel.v1.ReturnData"U\n\x11SyscallInvocation\x12\x15\n\rfunction_name\x18\x01 \x01(\x0c\x12\x13\n\x0bheap_prefix\x18\x02 \x01(\x0c\x12\x14\n\x0cstack_prefix\x18\x03 \x01(\x0c"ÿ\x01\n\x0eSyscallContext\x121\n\x06vm_ctx\x18\x01 \x01(\x0b2!.org.solana.sealevel.v1.VmContext\x127\n\tinstr_ctx\x18\x02 \x01(\x0b2$.org.solana.sealevel.v1.InstrContext\x12E\n\x12syscall_invocation\x18\x03 \x01(\x0b2).org.solana.sealevel.v1.SyscallInvocation\x12:\n\x0cexec_effects\x18\x04 \x01(\x0b2$.org.solana.sealevel.v1.InstrEffects"¥\x02\n\x0eSyscallEffects\x12\r\n\x05error\x18\x01 \x01(\x03\x123\n\nerror_kind\x18\x0c \x01(\x0e2\x1f.org.solana.sealevel.v1.ErrKind\x12\n\n\x02r0\x18\x02 \x01(\x04\x12\x10\n\x08cu_avail\x18\x03 \x01(\x04\x12\x0c\n\x04heap\x18\x04 \x01(\x0c\x12\r\n\x05stack\x18\x05 \x01(\x0c\x12\x11\n\tinputdata\x18\x06 \x01(\x0c\x12C\n\x12input_data_regions\x18\x0b \x03(\x0b2\'.org.solana.sealevel.v1.InputDataRegion\x12\x13\n\x0bframe_count\x18\x07 \x01(\x04\x12\x0b\n\x03log\x18\x08 \x01(\x0c\x12\x0e\n\x06rodata\x18\t \x01(\x0c\x12\n\n\x02pc\x18\n \x01(\x04"º\x01\n\x0eSyscallFixture\x129\n\x08metadata\x18\x01 \x01(\x0b2\'.org.solana.sealevel.v1.FixtureMetadata\x125\n\x05input\x18\x02 \x01(\x0b2&.org.solana.sealevel.v1.SyscallContext\x126\n\x06output\x18\x03 \x01(\x0b2&.org.solana.sealevel.v1.SyscallEffects"x\n\rFullVmContext\x121\n\x06vm_ctx\x18\x01 \x01(\x0b2!.org.solana.sealevel.v1.VmContext\x124\n\x08features\x18\x03 \x01(\x0b2".org.solana.sealevel.v1.FeatureSet"4\n\x11ValidateVmEffects\x12\x0e\n\x06result\x18\x01 \x01(\x05\x12\x0f\n\x07success\x18\x02 \x01(\x08"¿\x01\n\x11ValidateVmFixture\x129\n\x08metadata\x18\x01 \x01(\x0b2\'.org.solana.sealevel.v1.FixtureMetadata\x124\n\x05input\x18\x02 \x01(\x0b2%.org.solana.sealevel.v1.FullVmContext\x129\n\x06output\x18\x03 \x01(\x0b2).org.solana.sealevel.v1.ValidateVmEffects".\n\nReturnData\x12\x12\n\nprogram_id\x18\x01 \x01(\x0c\x12\x0c\n\x04data\x18\x02 \x01(\x0c*B\n\x07ErrKind\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x08\n\x04EBPF\x10\x01\x12\x0b\n\x07SYSCALL\x10\x02\x12\x0f\n\x0bINSTRUCTION\x10\x03b\x06proto3' ), - dependencies=[invoke__pb2.DESCRIPTOR, context__pb2.DESCRIPTOR], + dependencies=[ + invoke__pb2.DESCRIPTOR, + context__pb2.DESCRIPTOR, + metadata__pb2.DESCRIPTOR, + ], +) +_ERRKIND = _descriptor.EnumDescriptor( + name="ErrKind", + full_name="org.solana.sealevel.v1.ErrKind", + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name="UNSPECIFIED", index=0, number=0, options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="EBPF", index=1, number=1, options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="SYSCALL", index=2, number=2, options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="INSTRUCTION", index=3, number=3, options=None, type=None + ), + ], + containing_type=None, + options=None, + serialized_start=1902, + serialized_end=1968, ) +_sym_db.RegisterEnumDescriptor(_ERRKIND) +ErrKind = enum_type_wrapper.EnumTypeWrapper(_ERRKIND) +UNSPECIFIED = 0 +EBPF = 1 +SYSCALL = 2 +INSTRUCTION = 3 _INPUTDATAREGION = _descriptor.Descriptor( name="InputDataRegion", full_name="org.solana.sealevel.v1.InputDataRegion", @@ -90,8 +126,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=65, - serialized_end=136, + serialized_start=81, + serialized_end=152, ) _VMCONTEXT = _descriptor.Descriptor( name="VmContext", @@ -496,6 +532,24 @@ options=None, file=DESCRIPTOR, ), + _descriptor.FieldDescriptor( + name="return_data", + full_name="org.solana.sealevel.v1.VmContext.return_data", + index=22, + number=23, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), ], extensions=[], nested_types=[], @@ -505,8 +559,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=139, - serialized_end=579, + serialized_start=155, + serialized_end=652, ) _SYSCALLINVOCATION = _descriptor.Descriptor( name="SyscallInvocation", @@ -578,8 +632,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=581, - serialized_end=666, + serialized_start=654, + serialized_end=739, ) _SYSCALLCONTEXT = _descriptor.Descriptor( name="SyscallContext", @@ -669,8 +723,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=669, - serialized_end=924, + serialized_start=742, + serialized_end=997, ) _SYSCALLEFFECTS = _descriptor.Descriptor( name="SyscallEffects", @@ -697,10 +751,28 @@ options=None, file=DESCRIPTOR, ), + _descriptor.FieldDescriptor( + name="error_kind", + full_name="org.solana.sealevel.v1.SyscallEffects.error_kind", + index=1, + number=12, + type=14, + cpp_type=8, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), _descriptor.FieldDescriptor( name="r0", full_name="org.solana.sealevel.v1.SyscallEffects.r0", - index=1, + index=2, number=2, type=4, cpp_type=4, @@ -718,7 +790,7 @@ _descriptor.FieldDescriptor( name="cu_avail", full_name="org.solana.sealevel.v1.SyscallEffects.cu_avail", - index=2, + index=3, number=3, type=4, cpp_type=4, @@ -736,7 +808,7 @@ _descriptor.FieldDescriptor( name="heap", full_name="org.solana.sealevel.v1.SyscallEffects.heap", - index=3, + index=4, number=4, type=12, cpp_type=9, @@ -754,7 +826,7 @@ _descriptor.FieldDescriptor( name="stack", full_name="org.solana.sealevel.v1.SyscallEffects.stack", - index=4, + index=5, number=5, type=12, cpp_type=9, @@ -772,7 +844,7 @@ _descriptor.FieldDescriptor( name="inputdata", full_name="org.solana.sealevel.v1.SyscallEffects.inputdata", - index=5, + index=6, number=6, type=12, cpp_type=9, @@ -790,7 +862,7 @@ _descriptor.FieldDescriptor( name="input_data_regions", full_name="org.solana.sealevel.v1.SyscallEffects.input_data_regions", - index=6, + index=7, number=11, type=11, cpp_type=10, @@ -808,7 +880,7 @@ _descriptor.FieldDescriptor( name="frame_count", full_name="org.solana.sealevel.v1.SyscallEffects.frame_count", - index=7, + index=8, number=7, type=4, cpp_type=4, @@ -826,7 +898,7 @@ _descriptor.FieldDescriptor( name="log", full_name="org.solana.sealevel.v1.SyscallEffects.log", - index=8, + index=9, number=8, type=12, cpp_type=9, @@ -844,7 +916,7 @@ _descriptor.FieldDescriptor( name="rodata", full_name="org.solana.sealevel.v1.SyscallEffects.rodata", - index=9, + index=10, number=9, type=12, cpp_type=9, @@ -862,7 +934,7 @@ _descriptor.FieldDescriptor( name="pc", full_name="org.solana.sealevel.v1.SyscallEffects.pc", - index=10, + index=11, number=10, type=4, cpp_type=4, @@ -886,8 +958,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=927, - serialized_end=1167, + serialized_start=1000, + serialized_end=1293, ) _SYSCALLFIXTURE = _descriptor.Descriptor( name="SyscallFixture", @@ -897,8 +969,8 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="input", - full_name="org.solana.sealevel.v1.SyscallFixture.input", + name="metadata", + full_name="org.solana.sealevel.v1.SyscallFixture.metadata", index=0, number=1, type=11, @@ -915,8 +987,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="output", - full_name="org.solana.sealevel.v1.SyscallFixture.output", + name="input", + full_name="org.solana.sealevel.v1.SyscallFixture.input", index=1, number=2, type=11, @@ -932,6 +1004,24 @@ options=None, file=DESCRIPTOR, ), + _descriptor.FieldDescriptor( + name="output", + full_name="org.solana.sealevel.v1.SyscallFixture.output", + index=2, + number=3, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), ], extensions=[], nested_types=[], @@ -941,8 +1031,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1169, - serialized_end=1296, + serialized_start=1296, + serialized_end=1482, ) _FULLVMCONTEXT = _descriptor.Descriptor( name="FullVmContext", @@ -996,8 +1086,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1298, - serialized_end=1418, + serialized_start=1484, + serialized_end=1604, ) _VALIDATEVMEFFECTS = _descriptor.Descriptor( name="ValidateVmEffects", @@ -1051,8 +1141,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1420, - serialized_end=1472, + serialized_start=1606, + serialized_end=1658, ) _VALIDATEVMFIXTURE = _descriptor.Descriptor( name="ValidateVmFixture", @@ -1062,8 +1152,8 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="input", - full_name="org.solana.sealevel.v1.ValidateVmFixture.input", + name="metadata", + full_name="org.solana.sealevel.v1.ValidateVmFixture.metadata", index=0, number=1, type=11, @@ -1080,8 +1170,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="output", - full_name="org.solana.sealevel.v1.ValidateVmFixture.output", + name="input", + full_name="org.solana.sealevel.v1.ValidateVmFixture.input", index=1, number=2, type=11, @@ -1097,6 +1187,24 @@ options=None, file=DESCRIPTOR, ), + _descriptor.FieldDescriptor( + name="output", + full_name="org.solana.sealevel.v1.ValidateVmFixture.output", + index=2, + number=3, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), ], extensions=[], nested_types=[], @@ -1106,19 +1214,80 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1475, - serialized_end=1607, + serialized_start=1661, + serialized_end=1852, +) +_RETURNDATA = _descriptor.Descriptor( + name="ReturnData", + full_name="org.solana.sealevel.v1.ReturnData", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="program_id", + full_name="org.solana.sealevel.v1.ReturnData.program_id", + index=0, + number=1, + type=12, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b(""), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="data", + full_name="org.solana.sealevel.v1.ReturnData.data", + index=1, + number=2, + type=12, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b(""), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1854, + serialized_end=1900, ) _VMCONTEXT.fields_by_name["input_data_regions"].message_type = _INPUTDATAREGION +_VMCONTEXT.fields_by_name["return_data"].message_type = _RETURNDATA _SYSCALLCONTEXT.fields_by_name["vm_ctx"].message_type = _VMCONTEXT _SYSCALLCONTEXT.fields_by_name["instr_ctx"].message_type = invoke__pb2._INSTRCONTEXT _SYSCALLCONTEXT.fields_by_name["syscall_invocation"].message_type = _SYSCALLINVOCATION _SYSCALLCONTEXT.fields_by_name["exec_effects"].message_type = invoke__pb2._INSTREFFECTS +_SYSCALLEFFECTS.fields_by_name["error_kind"].enum_type = _ERRKIND _SYSCALLEFFECTS.fields_by_name["input_data_regions"].message_type = _INPUTDATAREGION +_SYSCALLFIXTURE.fields_by_name["metadata"].message_type = metadata__pb2._FIXTUREMETADATA _SYSCALLFIXTURE.fields_by_name["input"].message_type = _SYSCALLCONTEXT _SYSCALLFIXTURE.fields_by_name["output"].message_type = _SYSCALLEFFECTS _FULLVMCONTEXT.fields_by_name["vm_ctx"].message_type = _VMCONTEXT _FULLVMCONTEXT.fields_by_name["features"].message_type = context__pb2._FEATURESET +_VALIDATEVMFIXTURE.fields_by_name["metadata"].message_type = ( + metadata__pb2._FIXTUREMETADATA +) _VALIDATEVMFIXTURE.fields_by_name["input"].message_type = _FULLVMCONTEXT _VALIDATEVMFIXTURE.fields_by_name["output"].message_type = _VALIDATEVMEFFECTS DESCRIPTOR.message_types_by_name["InputDataRegion"] = _INPUTDATAREGION @@ -1130,6 +1299,8 @@ DESCRIPTOR.message_types_by_name["FullVmContext"] = _FULLVMCONTEXT DESCRIPTOR.message_types_by_name["ValidateVmEffects"] = _VALIDATEVMEFFECTS DESCRIPTOR.message_types_by_name["ValidateVmFixture"] = _VALIDATEVMFIXTURE +DESCRIPTOR.message_types_by_name["ReturnData"] = _RETURNDATA +DESCRIPTOR.enum_types_by_name["ErrKind"] = _ERRKIND _sym_db.RegisterFileDescriptor(DESCRIPTOR) InputDataRegion = _reflection.GeneratedProtocolMessageType( "InputDataRegion", @@ -1183,3 +1354,7 @@ dict(DESCRIPTOR=_VALIDATEVMFIXTURE, __module__="vm_pb2"), ) _sym_db.RegisterMessage(ValidateVmFixture) +ReturnData = _reflection.GeneratedProtocolMessageType( + "ReturnData", (_message.Message,), dict(DESCRIPTOR=_RETURNDATA, __module__="vm_pb2") +) +_sym_db.RegisterMessage(ReturnData)