diff --git a/.github/workflows/hive-consume.yaml b/.github/workflows/hive-consume.yaml new file mode 100644 index 00000000000..c79cc468c85 --- /dev/null +++ b/.github/workflows/hive-consume.yaml @@ -0,0 +1,123 @@ +name: Hive Consume Tests + +on: + push: + branches: + - 'forks/**' + pull_request: + paths: + - '.github/workflows/hive-consume.yaml' + - 'packages/testing/src/execution_testing/cli/pytest_commands/consume.py' + - 'packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-consume.ini' + - 'packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/**' + - 'packages/testing/src/execution_testing/cli/pytest_commands/plugins/pytest_hive/**' + - 'packages/testing/src/execution_testing/fixtures/consume.py' + - 'packages/testing/src/execution_testing/rpc/**' + workflow_dispatch: + +concurrency: + group: hive-consume-${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + +jobs: + test-hive: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + include: + - name: consume-engine + mode: simulator + simulator: ethereum/eels/consume-engine + - name: consume-rlp + mode: simulator + simulator: ethereum/eels/consume-rlp + - name: consume-sync + mode: simulator + simulator: ethereum/eels/consume-sync + - name: dev-mode + mode: dev + consume_command: engine + steps: + - name: Checkout execution-specs + if: matrix.mode == 'dev' + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + path: execution-specs + + - name: Checkout Hive + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + repository: ethereum/hive + ref: master + path: hive + + - name: Setup go env and cache + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: '>=1.24' + cache-dependency-path: hive/go.sum + + - name: Install uv and python + if: matrix.mode == 'dev' + uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 + with: + enable-cache: true + cache-dependency-glob: "execution-specs/uv.lock" + version: ${{ vars.UV_VERSION }} + python-version: ${{ vars.DEFAULT_PYTHON_VERSION }} + + - name: Pre-pull geth docker image + run: docker pull docker.ethquokkaops.io/dh/ethpandaops/geth:master + + - name: Create clients.yaml + run: | + cat > hive/clients.yaml << 'EOF' + - client: go-ethereum + nametag: default + build_args: + baseimage: docker.ethquokkaops.io/dh/ethpandaops/geth + tag: master + EOF + + - name: Build hive + run: | + cd hive + go build . + + - name: Run simulator tests + if: matrix.mode == 'simulator' + run: | + cd hive + ./hive --sim '${{ matrix.simulator }}' \ + --sim.parallelism=1 \ + --client go-ethereum \ + --client-file clients.yaml \ + --sim.buildarg fixtures=develop@v5.3.0 \ + --sim.limit=".*test_block_at_rlp_limit_with_logs.*Osaka.*" \ + --docker.output + + - name: Start Hive in dev mode + if: matrix.mode == 'dev' + run: | + cd hive + ./hive --dev --client go-ethereum --client-file clients.yaml --docker.output & + echo "Waiting for Hive to be ready..." + for i in {1..30}; do + if curl -s http://127.0.0.1:3000 > /dev/null 2>&1; then + echo "Hive is ready!" + break + fi + echo "Waiting... ($i/30)" + sleep 2 + done + + - name: Run consume in dev mode + if: matrix.mode == 'dev' + working-directory: execution-specs + env: + HIVE_SIMULATOR: http://127.0.0.1:3000 + run: | + uv sync --all-extras + uv run consume ${{ matrix.consume_command }} --input develop@v5.3.0 -k "Osaka and test_block_at_rlp_limit_with_logs" diff --git a/packages/testing/src/execution_testing/cli/eofwrap.py b/packages/testing/src/execution_testing/cli/eofwrap.py index b6446dc2a13..cf8ea297443 100644 --- a/packages/testing/src/execution_testing/cli/eofwrap.py +++ b/packages/testing/src/execution_testing/cli/eofwrap.py @@ -323,6 +323,7 @@ def _wrap_fixture( test = BlockchainTest( genesis_environment=env, + fork=EOFv1, pre=pre.root, post=fixture.post_state.root if fixture.post_state else {}, blocks=[], @@ -373,7 +374,6 @@ def _wrap_fixture( result = test.generate( t8n=t8n, - fork=EOFv1, fixture_format=BlockchainFixture, ) assert isinstance(result, BlockchainFixture) diff --git a/packages/testing/src/execution_testing/cli/fuzzer_bridge/blocktest_builder.py b/packages/testing/src/execution_testing/cli/fuzzer_bridge/blocktest_builder.py index 8d6c375896a..7b6c483a464 100644 --- a/packages/testing/src/execution_testing/cli/fuzzer_bridge/blocktest_builder.py +++ b/packages/testing/src/execution_testing/cli/fuzzer_bridge/blocktest_builder.py @@ -68,7 +68,6 @@ def build_blocktest( # Generate fixture fixture = test.generate( t8n=self.t8n, - fork=fork, fixture_format=BlockchainFixture, ) diff --git a/packages/testing/src/execution_testing/cli/fuzzer_bridge/converter.py b/packages/testing/src/execution_testing/cli/fuzzer_bridge/converter.py index 29971e94a5a..1e62214bbcb 100644 --- a/packages/testing/src/execution_testing/cli/fuzzer_bridge/converter.py +++ b/packages/testing/src/execution_testing/cli/fuzzer_bridge/converter.py @@ -251,6 +251,7 @@ def blockchain_test_from_fuzzer( return BlockchainTest( pre=pre, + fork=fork, blocks=blocks, post={}, # Post-state verification can be added later genesis_environment=genesis_env, diff --git a/packages/testing/src/execution_testing/cli/fuzzer_bridge/production_test.py b/packages/testing/src/execution_testing/cli/fuzzer_bridge/production_test.py index 5adfd26bb94..8f850d8230d 100644 --- a/packages/testing/src/execution_testing/cli/fuzzer_bridge/production_test.py +++ b/packages/testing/src/execution_testing/cli/fuzzer_bridge/production_test.py @@ -201,6 +201,7 @@ def generate_fixture(self, test_params: Dict[str, Any]) -> Dict[str, Any]: # Create test test = BlockchainTest( genesis_environment=test_params["genesis_environment"], + fork=fork, pre=test_params["pre"], post=test_params["post"], blocks=test_params["blocks"], @@ -210,7 +211,6 @@ def generate_fixture(self, test_params: Dict[str, Any]) -> Dict[str, Any]: # Generate fixture fixture = test.generate( t8n=self.t8n, - fork=fork, fixture_format=BlockchainFixture, ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index 19e512827f8..4b083115331 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -407,6 +407,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: kwargs["expected_benchmark_gas_used"] = ( request.getfixturevalue("gas_benchmark_value") ) + kwargs["fork"] = fork kwargs |= { p: request.getfixturevalue(p) for p in cls_fixture_parameters @@ -436,9 +437,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: [str(eoa) for eoa in pre._funded_eoa] ) - execute = self.execute( - fork=fork, execute_format=execute_format - ) + execute = self.execute(execute_format=execute_format) execute.execute( fork=fork, eth_rpc=eth_rpc, diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 50244964d0b..dfde4d13043 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -28,11 +28,11 @@ Alloc, ReferenceSpec, ) -from execution_testing.client_clis import TransitionTool -from execution_testing.client_clis.clis.geth import FixtureConsumerTool from execution_testing.cli.gen_index import ( generate_fixtures_index, ) +from execution_testing.client_clis import TransitionTool +from execution_testing.client_clis.clis.geth import FixtureConsumerTool from execution_testing.fixtures import ( BaseFixture, FixtureCollector, @@ -1350,6 +1350,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: kwargs["pre"] = pre if "expected_benchmark_gas_used" not in kwargs: kwargs["expected_benchmark_gas_used"] = gas_benchmark_value + kwargs["fork"] = fork kwargs |= { p: request.getfixturevalue(p) for p in cls_fixture_parameters @@ -1380,7 +1381,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # Use the original update_pre_alloc_groups method which # returns the groups self.update_pre_alloc_groups( - session.pre_alloc_groups, fork, request.node.nodeid + session.pre_alloc_groups, request.node.nodeid ) return # Skip fixture generation in phase 1 @@ -1391,15 +1392,12 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: FixtureFillingPhase.PRE_ALLOC_GENERATION in fixture_format.format_phases ): - pre_alloc_hash = self.compute_pre_alloc_group_hash( - fork=fork - ) + pre_alloc_hash = self.compute_pre_alloc_group_hash() group = session.get_pre_alloc_group(pre_alloc_hash) self.pre = group.pre try: fixture = self.generate( t8n=t8n, - fork=fork, fixture_format=fixture_format, ) finally: diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py index 8a705dc603a..d576807821c 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py @@ -24,12 +24,14 @@ class MockTest(BaseTest): def __init__( self, pre: Alloc, + fork: Fork, genesis_environment: Environment, request: Mock | None = None, ) -> None: """Initialize mock test.""" super().__init__( # type: ignore pre=pre, + fork=fork, genesis_environment=genesis_environment, ) self._request = request @@ -38,9 +40,9 @@ def generate(self, *args: Any, **kwargs: Any) -> BaseFixture: """Mock generate method.""" raise NotImplementedError("This is a mock test class") - def get_genesis_environment(self, fork: Fork) -> Environment: + def get_genesis_environment(self) -> Environment: """Return the genesis environment.""" - return self.genesis_environment.set_fork_requirements(fork) + return self.genesis_environment.set_fork_requirements(self.fork) def test_pre_alloc_group_separate() -> None: @@ -51,8 +53,8 @@ def test_pre_alloc_group_separate() -> None: fork = Prague # Create test without marker - test1 = MockTest(pre=pre, genesis_environment=env) - hash1 = test1.compute_pre_alloc_group_hash(fork) + test1 = MockTest(pre=pre, genesis_environment=env, fork=fork) + hash1 = test1.compute_pre_alloc_group_hash() # Create test with "separate" marker mock_request = Mock() @@ -62,15 +64,17 @@ def test_pre_alloc_group_separate() -> None: mock_marker.args = ("separate",) mock_request.node.get_closest_marker = Mock(return_value=mock_marker) - test2 = MockTest(pre=pre, genesis_environment=env, request=mock_request) - hash2 = test2.compute_pre_alloc_group_hash(fork) + test2 = MockTest( + pre=pre, genesis_environment=env, request=mock_request, fork=fork + ) + hash2 = test2.compute_pre_alloc_group_hash() # Hashes should be different due to "separate" marker assert hash1 != hash2 # Create another test without marker - should match first test - test3 = MockTest(pre=pre, genesis_environment=env) - hash3 = test3.compute_pre_alloc_group_hash(fork) + test3 = MockTest(pre=pre, genesis_environment=env, fork=fork) + hash3 = test3.compute_pre_alloc_group_hash() assert hash1 == hash3 @@ -89,8 +93,10 @@ def test_pre_alloc_group_custom_salt() -> None: mock_marker1.args = ("eip1234",) mock_request1.node.get_closest_marker = Mock(return_value=mock_marker1) - test1 = MockTest(pre=pre, genesis_environment=env, request=mock_request1) - hash1 = test1.compute_pre_alloc_group_hash(fork) + test1 = MockTest( + pre=pre, genesis_environment=env, request=mock_request1, fork=fork + ) + hash1 = test1.compute_pre_alloc_group_hash() # Create another test with same custom group "eip1234" mock_request2 = Mock() @@ -102,8 +108,10 @@ def test_pre_alloc_group_custom_salt() -> None: mock_marker2.args = ("eip1234",) # Same group mock_request2.node.get_closest_marker = Mock(return_value=mock_marker2) - test2 = MockTest(pre=pre, genesis_environment=env, request=mock_request2) - hash2 = test2.compute_pre_alloc_group_hash(fork) + test2 = MockTest( + pre=pre, genesis_environment=env, request=mock_request2, fork=fork + ) + hash2 = test2.compute_pre_alloc_group_hash() # Hashes should be the same - both in "eip1234" group assert hash1 == hash2 @@ -116,8 +124,10 @@ def test_pre_alloc_group_custom_salt() -> None: mock_marker3.args = ("eip5678",) # Different group mock_request3.node.get_closest_marker = Mock(return_value=mock_marker3) - test3 = MockTest(pre=pre, genesis_environment=env, request=mock_request3) - hash3 = test3.compute_pre_alloc_group_hash(fork) + test3 = MockTest( + pre=pre, genesis_environment=env, request=mock_request3, fork=fork + ) + hash3 = test3.compute_pre_alloc_group_hash() # Hash should be different - different custom group assert hash1 != hash3 @@ -138,8 +148,10 @@ def test_pre_alloc_group_separate_different_nodeids() -> None: mock_marker1.args = ("separate",) mock_request1.node.get_closest_marker = Mock(return_value=mock_marker1) - test1 = MockTest(pre=pre, genesis_environment=env, request=mock_request1) - hash1 = test1.compute_pre_alloc_group_hash(fork) + test1 = MockTest( + pre=pre, genesis_environment=env, request=mock_request1, fork=fork + ) + hash1 = test1.compute_pre_alloc_group_hash() # Create test with "separate" and nodeid2 mock_request2 = Mock() @@ -149,8 +161,10 @@ def test_pre_alloc_group_separate_different_nodeids() -> None: mock_marker2.args = ("separate",) mock_request2.node.get_closest_marker = Mock(return_value=mock_marker2) - test2 = MockTest(pre=pre, genesis_environment=env, request=mock_request2) - hash2 = test2.compute_pre_alloc_group_hash(fork) + test2 = MockTest( + pre=pre, genesis_environment=env, request=mock_request2, fork=fork + ) + hash2 = test2.compute_pre_alloc_group_hash() # Hashes should be different due to different nodeids assert hash1 != hash2 @@ -168,12 +182,14 @@ def test_no_pre_alloc_group_marker() -> None: mock_request.node.nodeid = "test_module.py::test_function" mock_request.node.get_closest_marker = Mock(return_value=None) # No marker - test1 = MockTest(pre=pre, genesis_environment=env, request=mock_request) - hash1 = test1.compute_pre_alloc_group_hash(fork) + test1 = MockTest( + pre=pre, genesis_environment=env, request=mock_request, fork=fork + ) + hash1 = test1.compute_pre_alloc_group_hash() # Create test without any request - test2 = MockTest(pre=pre, genesis_environment=env) - hash2 = test2.compute_pre_alloc_group_hash(fork) + test2 = MockTest(pre=pre, genesis_environment=env, fork=fork) + hash2 = test2.compute_pre_alloc_group_hash() # Hashes should be the same - both have no marker assert hash1 == hash2 @@ -196,8 +212,10 @@ def test_pre_alloc_group_with_reason() -> None: } mock_request1.node.get_closest_marker = Mock(return_value=mock_marker1) - test1 = MockTest(pre=pre, genesis_environment=env, request=mock_request1) - hash1 = test1.compute_pre_alloc_group_hash(fork) + test1 = MockTest( + pre=pre, genesis_environment=env, request=mock_request1, fork=fork + ) + hash1 = test1.compute_pre_alloc_group_hash() # Create another test with same group but different reason mock_request2 = Mock() @@ -208,8 +226,10 @@ def test_pre_alloc_group_with_reason() -> None: mock_marker2.kwargs = {"reason": "Different reason but same group"} mock_request2.node.get_closest_marker = Mock(return_value=mock_marker2) - test2 = MockTest(pre=pre, genesis_environment=env, request=mock_request2) - hash2 = test2.compute_pre_alloc_group_hash(fork) + test2 = MockTest( + pre=pre, genesis_environment=env, request=mock_request2, fork=fork + ) + hash2 = test2.compute_pre_alloc_group_hash() # Hashes should be the same - reason doesn't affect grouping assert hash1 == hash2 diff --git a/packages/testing/src/execution_testing/client_clis/clis/besu.py b/packages/testing/src/execution_testing/client_clis/clis/besu.py index 73a14cee5c2..03ce48ad35a 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/besu.py +++ b/packages/testing/src/execution_testing/client_clis/clis/besu.py @@ -296,26 +296,6 @@ class BesuExceptionMapper(ExceptionMapper): BlockException.RLP_BLOCK_LIMIT_EXCEEDED: ( r"Block size of \d+ bytes exceeds limit of \d+ bytes" ), - BlockException.INVALID_BAL_EXTRA_ACCOUNT: ( - r"Block access list hash mismatch, " - r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" - ), - BlockException.INVALID_BAL_HASH: ( - r"Block access list hash mismatch, " - r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" - ), - BlockException.INVALID_BAL_MISSING_ACCOUNT: ( - r"Block access list hash mismatch, " - r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" - ), - BlockException.INVALID_BLOCK_ACCESS_LIST: ( - r"Block access list hash mismatch, " - r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" - ), - BlockException.INCORRECT_BLOCK_FORMAT: ( - r"Block access list hash mismatch, " - r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" - ), TransactionException.INITCODE_SIZE_EXCEEDED: ( r"transaction invalid Initcode size of \d+ exceeds maximum size of \d+" ), @@ -345,4 +325,25 @@ class BesuExceptionMapper(ExceptionMapper): TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED: ( r"Blob transaction has too many blobs: \d+|Invalid Blob Count: \d+" ), + # BAL Exceptions: TODO - review once all clients completed. + BlockException.INVALID_BAL_EXTRA_ACCOUNT: ( + r"Block access list hash mismatch, " + r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" + ), + BlockException.INVALID_BAL_HASH: ( + r"Block access list hash mismatch, " + r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" + ), + BlockException.INVALID_BAL_MISSING_ACCOUNT: ( + r"Block access list hash mismatch, " + r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" + ), + BlockException.INVALID_BLOCK_ACCESS_LIST: ( + r"Block access list hash mismatch, " + r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" + ), + BlockException.INCORRECT_BLOCK_FORMAT: ( + r"Block access list hash mismatch, " + r"calculated:\s*(0x[a-f0-9]+)\s+header:\s*(0x[a-f0-9]+)" + ), } diff --git a/packages/testing/src/execution_testing/client_clis/clis/geth.py b/packages/testing/src/execution_testing/client_clis/clis/geth.py index 77a6c15d563..f28b3e4ea5f 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/geth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/geth.py @@ -87,9 +87,6 @@ class GethExceptionMapper(ExceptionMapper): BlockException.SYSTEM_CONTRACT_CALL_FAILED: "system call failed to execute:", BlockException.INVALID_BLOCK_HASH: "blockhash mismatch", BlockException.RLP_BLOCK_LIMIT_EXCEEDED: "block RLP-encoded size exceeds maximum", - BlockException.INVALID_BAL_EXTRA_ACCOUNT: "BAL change not reported in computed", - BlockException.INVALID_BAL_MISSING_ACCOUNT: "additional mutations compared to BAL", - BlockException.INVALID_BLOCK_ACCESS_LIST: "unequal", } mapping_regex: ClassVar[Dict[ExceptionBase, str]] = { TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED: ( @@ -116,6 +113,19 @@ class GethExceptionMapper(ExceptionMapper): # # EELS definition for `is_valid_deposit_event_data`: # https://github.com/ethereum/execution-specs/blob/5ddb904fa7ba27daeff423e78466744c51e8cb6a/src/ethereum/forks/prague/requests.py#L51 + # BAL Exceptions: TODO - review once all clients completed. + BlockException.INVALID_BAL_EXTRA_ACCOUNT: ( + r"invalid block access list:" + ), + BlockException.INVALID_BAL_HASH: (r"invalid block access list:"), + BlockException.INVALID_BAL_MISSING_ACCOUNT: ( + r"computed state diff contained mutated accounts which weren't reported in BAL" + ), + BlockException.INVALID_BLOCK_ACCESS_LIST: ( + r"difference between computed state diff and BAL entry for account" + r"|invalid block access list:" + ), + BlockException.INCORRECT_BLOCK_FORMAT: (r"invalid block access list:"), } diff --git a/packages/testing/src/execution_testing/client_clis/clis/nethermind.py b/packages/testing/src/execution_testing/client_clis/clis/nethermind.py index 1c0aabb70f9..242c60c6b91 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/nethermind.py +++ b/packages/testing/src/execution_testing/client_clis/clis/nethermind.py @@ -455,4 +455,19 @@ class NethermindExceptionMapper(ExceptionMapper): BlockException.SYSTEM_CONTRACT_CALL_FAILED: ( r"(Withdrawals|Consolidations)Failed: Contract execution failed\." ), + # BAL Exceptions: TODO - review once all clients completed. + BlockException.INVALID_BAL_EXTRA_ACCOUNT: ( + r"could not be parsed as a block: Could not decode block access list." + ), + BlockException.INVALID_BAL_HASH: (r"InvalidBlockLevelAccessListRoot:"), + BlockException.INVALID_BAL_MISSING_ACCOUNT: ( + r"InvalidBlockLevelAccessListRoot:" + ), + BlockException.INVALID_BLOCK_ACCESS_LIST: ( + r"InvalidBlockLevelAccessListRoot:" + r"|could not be parsed as a block: Could not decode block access list." + ), + BlockException.INCORRECT_BLOCK_FORMAT: ( + r"could not be parsed as a block: Could not decode block access list." + ), } diff --git a/packages/testing/src/execution_testing/client_clis/clis/reth.py b/packages/testing/src/execution_testing/client_clis/clis/reth.py index 951fca5bfc8..dec906350c0 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/reth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/reth.py @@ -74,4 +74,18 @@ class RethExceptionMapper(ExceptionMapper): BlockException.INVALID_GAS_USED_ABOVE_LIMIT: ( r"block used gas \(\d+\) is greater than gas limit \(\d+\)" ), + # BAL Exceptions: TODO - review once all clients completed. + BlockException.INVALID_BAL_EXTRA_ACCOUNT: ( + r"Block BAL contains an account change that is not present in the computed BAL." + ), + BlockException.INVALID_BAL_HASH: (r"Block's access list is invalid."), + BlockException.INVALID_BAL_MISSING_ACCOUNT: ( + r"Block BAL is missing an account change that is present in the computed BAL." + ), + BlockException.INVALID_BLOCK_ACCESS_LIST: ( + r"Block's access list is invalid." + ), + BlockException.INCORRECT_BLOCK_FORMAT: ( + r"Block's access list is invalid." + ), } diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tools_support.py b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tools_support.py index c2a152339af..73f8cf2d4d3 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tools_support.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tools_support.py @@ -265,12 +265,12 @@ def test_t8n_support(fork: Fork, installed_t8n: TransitionTool) -> None: test = BlockchainTest( genesis_environment=env, + fork=fork, pre=pre, post=block_1.expected_post_state, blocks=[block_1, block_2], ) test.generate( t8n=installed_t8n, - fork=fork, fixture_format=BlockchainFixture, ) diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index aa6a9f6b918..ae6e82a793c 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -39,6 +39,7 @@ PreAllocGroups, ) from execution_testing.forks import Fork +from execution_testing.forks.base_fork import BaseFork from execution_testing.test_types import Alloc, Environment, Withdrawal @@ -91,6 +92,11 @@ class BaseTest(BaseModel): model_config = ConfigDict(extra="forbid") tag: str = "" + fork: Fork = ( + BaseFork # type: ignore[type-abstract] + # default to BaseFork to allow the filler to set it, + # instead of each test having to set it + ) _request: pytest.FixtureRequest | None = PrivateAttr(None) _operation_mode: OpMode | None = PrivateAttr(None) @@ -114,6 +120,16 @@ class BaseTest(BaseModel): supported_markers: ClassVar[Dict[str, str]] = {} + def model_post_init(self, __context: Any, /) -> None: + """ + Model post-init to assert that the custom pre-allocation was + provided and the default was not used. + """ + super().model_post_init(__context) + assert self.fork != BaseFork, ( + "Fork was not provided by the filler/executor." + ) + @classmethod def discard_fixture_format_by_marks( cls, @@ -148,6 +164,7 @@ def from_test( """Create a test in a different format from a base test.""" new_instance = cls( tag=base_test.tag, + fork=base_test.fork, t8n_dump_dir=base_test.t8n_dump_dir, expected_benchmark_gas_used=base_test.expected_benchmark_gas_used, skip_gas_used_validation=base_test.skip_gas_used_validation, @@ -177,7 +194,6 @@ def generate( self, *, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat, ) -> BaseFixture: """Generate the list of test fixtures.""" @@ -186,11 +202,9 @@ def generate( def execute( self, *, - fork: Fork, execute_format: ExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" - del fork raise Exception(f"Unsupported execute format: {execute_format}") @classmethod @@ -273,7 +287,7 @@ def check_exception_test( "from the test." ) - def get_genesis_environment(self, fork: Fork) -> Environment: + def get_genesis_environment(self) -> Environment: """ Get the genesis environment for pre-allocation groups. @@ -286,7 +300,7 @@ def get_genesis_environment(self, fork: Fork) -> Environment: ) def update_pre_alloc_groups( - self, pre_alloc_groups: PreAllocGroups, fork: Fork, test_id: str + self, pre_alloc_groups: PreAllocGroups, test_id: str ) -> PreAllocGroups: """ Create or update the pre-allocation group with the pre from the current @@ -297,7 +311,7 @@ def update_pre_alloc_groups( f"{self.__class__.__name__} does not have a 'pre' field. Pre-allocation groups " "are only supported for test types that define pre-allocation." ) - pre_alloc_hash = self.compute_pre_alloc_group_hash(fork=fork) + pre_alloc_hash = self.compute_pre_alloc_group_hash() if pre_alloc_hash in pre_alloc_groups: # Update existing group - just merge pre-allocations @@ -307,36 +321,36 @@ def update_pre_alloc_groups( self.pre, key_collision_mode=Alloc.KeyCollisionMode.ALLOW_IDENTICAL_ACCOUNTS, ) - group.fork = fork + group.fork = self.fork group.test_ids.append(str(test_id)) pre_alloc_groups[pre_alloc_hash] = group else: # Create new group - use Environment instead of expensive genesis # generation - genesis_env = self.get_genesis_environment(fork) + genesis_env = self.get_genesis_environment() pre_alloc = Alloc.merge( - Alloc.model_validate(fork.pre_allocation_blockchain()), + Alloc.model_validate(self.fork.pre_allocation_blockchain()), self.pre, ) group = PreAllocGroup( test_ids=[str(test_id)], - fork=fork, + fork=self.fork, environment=genesis_env, pre=pre_alloc, ) pre_alloc_groups[pre_alloc_hash] = group return pre_alloc_groups - def compute_pre_alloc_group_hash(self, fork: Fork) -> str: + def compute_pre_alloc_group_hash(self) -> str: """Hash (fork, env) in order to group tests by genesis config.""" if not hasattr(self, "pre"): raise AttributeError( f"{self.__class__.__name__} does not have a 'pre' field. Pre-allocation group " "usage is only supported for test types that define pre-allocs." ) - fork_digest = hashlib.sha256(fork.name().encode("utf-8")).digest() + fork_digest = hashlib.sha256(self.fork.name().encode("utf-8")).digest() fork_hash = int.from_bytes(fork_digest[:8], byteorder="big") - genesis_env = self.get_genesis_environment(fork) + genesis_env = self.get_genesis_environment() combined_hash = fork_hash ^ hash(genesis_env) # Check if test has pre_alloc_group marker diff --git a/packages/testing/src/execution_testing/specs/benchmark.py b/packages/testing/src/execution_testing/specs/benchmark.py index e1953db7b79..1b7d022ae28 100644 --- a/packages/testing/src/execution_testing/specs/benchmark.py +++ b/packages/testing/src/execution_testing/specs/benchmark.py @@ -171,6 +171,47 @@ def model_post_init(self, __context: Any, /) -> None: "pre allocation was not provided" ) + set_props = [ + name + for name, val in [ + ("code_generator", self.code_generator), + ("blocks", self.blocks), + ("tx", self.tx), + ] + if val is not None + ] + + if len(set_props) != 1: + raise ValueError( + f"Exactly one must be set, but got {len(set_props)}: {', '.join(set_props)}" + ) + + blocks: List[Block] = self.setup_blocks + + if self.code_generator is not None: + generated_blocks = self.generate_blocks_from_code_generator() + blocks += generated_blocks + + elif self.blocks is not None: + blocks += self.blocks + + elif self.tx is not None: + gas_limit = ( + self.fork.transaction_gas_limit_cap() + or self.gas_benchmark_value + ) + + transactions = self.split_transaction(self.tx, gas_limit) + + blocks.append(Block(txs=transactions)) + + else: + raise ValueError( + "Cannot create BlockchainTest without a code generator, transactions, or blocks" + ) + + self.blocks = blocks + @classmethod def pytest_parameter_name(cls) -> str: """ @@ -198,10 +239,9 @@ def discard_fixture_format_by_marks( return fixture_format != BlockchainEngineFixture return False - def get_genesis_environment(self, fork: Fork) -> Environment: + def get_genesis_environment(self) -> Environment: """Get the genesis environment for this benchmark test.""" - del fork - return self.env + return self.generate_blockchain_test().get_genesis_environment() def split_transaction( self, tx: Transaction, gas_limit_cap: int | None @@ -233,14 +273,13 @@ def split_transaction( return split_transactions - def generate_blocks_from_code_generator(self, fork: Fork) -> List[Block]: + def generate_blocks_from_code_generator(self) -> List[Block]: """Generate blocks using the code generator.""" if self.code_generator is None: raise Exception("Code generator is not set") - - self.code_generator.deploy_contracts(pre=self.pre, fork=fork) + self.code_generator.deploy_contracts(pre=self.pre, fork=self.fork) gas_limit = ( - fork.transaction_gas_limit_cap() or self.gas_benchmark_value + self.fork.transaction_gas_limit_cap() or self.gas_benchmark_value ) benchmark_tx = self.code_generator.generate_transaction( pre=self.pre, gas_benchmark_value=gas_limit @@ -251,58 +290,19 @@ def generate_blocks_from_code_generator(self, fork: Fork) -> List[Block]: return [execution_block] - def generate_blockchain_test(self, fork: Fork) -> BlockchainTest: + def generate_blockchain_test(self) -> BlockchainTest: """Create a BlockchainTest from this BenchmarkTest.""" - set_props = [ - name - for name, val in [ - ("code_generator", self.code_generator), - ("blocks", self.blocks), - ("tx", self.tx), - ] - if val is not None - ] - - if len(set_props) != 1: - raise ValueError( - f"Exactly one must be set, but got {len(set_props)}: {', '.join(set_props)}" - ) - - blocks: List[Block] = self.setup_blocks - - if self.code_generator is not None: - generated_blocks = self.generate_blocks_from_code_generator(fork) - blocks += generated_blocks - - elif self.blocks is not None: - blocks += self.blocks - - elif self.tx is not None: - gas_limit = ( - fork.transaction_gas_limit_cap() or self.gas_benchmark_value - ) - - transactions = self.split_transaction(self.tx, gas_limit) - - blocks.append(Block(txs=transactions)) - - else: - raise ValueError( - "Cannot create BlockchainTest without a code generator, transactions, or blocks" - ) - return BlockchainTest.from_test( base_test=self, genesis_environment=self.env, pre=self.pre, post=self.post, - blocks=blocks, + blocks=self.blocks, ) def generate( self, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat, ) -> BaseFixture: """Generate the blockchain test fixture.""" @@ -310,8 +310,8 @@ def generate( exception=self.tx.error is not None if self.tx else False ) if fixture_format in BlockchainTest.supported_fixture_formats: - return self.generate_blockchain_test(fork=fork).generate( - t8n=t8n, fork=fork, fixture_format=fixture_format + return self.generate_blockchain_test().generate( + t8n=t8n, fixture_format=fixture_format ) else: raise Exception(f"Unsupported fixture format: {fixture_format}") @@ -319,12 +319,9 @@ def generate( def execute( self, *, - fork: Fork, execute_format: ExecuteFormat, ) -> BaseExecute: """Execute the benchmark test by sending it to the live network.""" - del fork - if execute_format == TransactionPost: return TransactionPost( blocks=[[self.tx]], diff --git a/packages/testing/src/execution_testing/specs/blobs.py b/packages/testing/src/execution_testing/specs/blobs.py index 65e1b7c45e1..af9b219a88e 100644 --- a/packages/testing/src/execution_testing/specs/blobs.py +++ b/packages/testing/src/execution_testing/specs/blobs.py @@ -10,7 +10,6 @@ BaseFixture, FixtureFormat, ) -from execution_testing.forks import Fork from execution_testing.test_types import ( NetworkWrappedTransaction, Transaction, @@ -38,22 +37,18 @@ def generate( self, *, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat, ) -> BaseFixture: """Generate the list of test fixtures.""" - del t8n, fork + del t8n raise Exception(f"Unknown fixture format: {fixture_format}") def execute( self, *, - fork: Fork, execute_format: ExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" - del fork - if execute_format == BlobTransaction: return BlobTransaction( txs=self.txs, diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 14745c81769..04f859808a1 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -518,18 +518,18 @@ def discard_fixture_format_by_marks( return True return False - def get_genesis_environment(self, fork: Fork) -> Environment: + def get_genesis_environment(self) -> Environment: """Get the genesis environment for pre-allocation groups.""" modified_values = self.genesis_environment.set_fork_requirements( - fork + self.fork ).model_dump(exclude_unset=True) return Environment(**(GENESIS_ENVIRONMENT_DEFAULTS | modified_values)) def make_genesis( - self, *, fork: Fork, apply_pre_allocation_blockchain: bool + self, *, apply_pre_allocation_blockchain: bool ) -> Tuple[Alloc, FixtureBlock]: """Create a genesis block from the blockchain test definition.""" - env = self.get_genesis_environment(fork) + env = self.get_genesis_environment() assert env.withdrawals is None or len(env.withdrawals) == 0, ( "withdrawals must be empty at genesis" ) @@ -541,13 +541,13 @@ def make_genesis( pre_alloc = self.pre if apply_pre_allocation_blockchain: pre_alloc = Alloc.merge( - Alloc.model_validate(fork.pre_allocation_blockchain()), + Alloc.model_validate(self.fork.pre_allocation_blockchain()), pre_alloc, ) if empty_accounts := pre_alloc.empty_accounts(): raise Exception(f"Empty accounts in pre state: {empty_accounts}") state_root = pre_alloc.state_root() - genesis = FixtureHeader.genesis(fork, env, state_root) + genesis = FixtureHeader.genesis(self.fork, env, state_root) return ( pre_alloc, @@ -560,7 +560,6 @@ def make_genesis( def generate_block_data( self, t8n: TransitionTool, - fork: Fork, block: Block, previous_env: Environment, previous_alloc: Alloc, @@ -570,7 +569,7 @@ def generate_block_data( Generate common block data for both make_fixture and make_hive_fixture. """ env = block.set_environment(previous_env) - env = env.set_fork_requirements(fork) + env = env.set_fork_requirements(self.fork) txs = [tx.with_signature_and_sender() for tx in block.txs] if failing_tx_count := len([tx for tx in txs if tx.error]) > 0: @@ -589,12 +588,12 @@ def generate_block_data( alloc=previous_alloc, txs=txs, env=env, - fork=fork, + fork=self.fork, chain_id=self.chain_id, - reward=fork.get_reward( + reward=self.fork.get_reward( block_number=env.number, timestamp=env.timestamp ), - blob_schedule=fork.blob_schedule(), + blob_schedule=self.fork.blob_schedule(), ), debug_output_path=self.get_next_transition_tool_output_path(), slow_request=self.is_tx_gas_heavy_test(), @@ -615,7 +614,7 @@ def generate_block_data( # the correct value by default. blob_gas_used: int | None = None if ( - blob_gas_per_blob := fork.blob_gas_per_blob( + blob_gas_per_blob := self.fork.blob_gas_per_blob( block_number=env.number, timestamp=env.timestamp ) ) > 0: @@ -634,7 +633,7 @@ def generate_block_data( extra_data=block.extra_data if block.extra_data is not None else b"", - fork=fork, + fork=self.fork, ) if block.header_verify is not None: @@ -661,7 +660,7 @@ def generate_block_data( ) requests_list: List[Bytes] | None = None - if fork.header_requests_required( + if self.fork.header_requests_required( block_number=header.number, timestamp=header.timestamp ): assert transition_tool_output.result.requests is not None, ( @@ -686,7 +685,7 @@ def generate_block_data( ) requests_list = block.requests - if fork.header_bal_hash_required( + if self.fork.header_bal_hash_required( block_number=header.number, timestamp=header.timestamp ): assert ( @@ -708,7 +707,9 @@ def generate_block_data( # Modify any parameter specified in the `rlp_modifier` after # transition tool processing. header = block.rlp_modifier.apply(header) - header.fork = fork # Deleted during `apply` because `exclude=True` + header.fork = ( + self.fork + ) # Deleted during `apply` because `exclude=True` # Process block access list - apply transformer if present for invalid # tests @@ -738,7 +739,7 @@ def generate_block_data( result=transition_tool_output.result, expected_exception=block.exception, engine_api_error_code=block.engine_api_error_code, - fork=fork, + fork=self.fork, block_access_list=bal, ) @@ -806,14 +807,11 @@ def verify_post_state( def make_fixture( self, t8n: TransitionTool, - fork: Fork, ) -> BlockchainFixture: """Create a fixture from the blockchain test definition.""" fixture_blocks: List[FixtureBlock | InvalidFixtureBlock] = [] - pre, genesis = self.make_genesis( - fork=fork, apply_pre_allocation_blockchain=True - ) + pre, genesis = self.make_genesis(apply_pre_allocation_blockchain=True) alloc = pre env = environment_from_parent_header(genesis.header) @@ -825,7 +823,6 @@ def make_fixture( # Set the environment according to the block to execute. built_block = self.generate_block_data( t8n=t8n, - fork=fork, block=block, previous_env=env, previous_alloc=alloc, @@ -856,7 +853,7 @@ def make_fixture( if self._opcode_count is not None: info["opcode_count"] = self._opcode_count.model_dump() return BlockchainFixture( - fork=fork, + fork=self.fork, genesis=genesis.header, genesis_rlp=genesis.rlp, blocks=fixture_blocks, @@ -869,9 +866,9 @@ def make_fixture( if self.exclude_full_post_state_in_output else None, config=FixtureConfig( - fork=fork, + fork=self.fork, blob_schedule=FixtureBlobSchedule.from_blob_schedule( - fork.blob_schedule() + self.fork.blob_schedule() ), chain_id=self.chain_id, ), @@ -881,7 +878,6 @@ def make_fixture( def make_hive_fixture( self, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat = BlockchainEngineFixture, ) -> ( BlockchainEngineFixture @@ -892,7 +888,6 @@ def make_hive_fixture( fixture_payloads: List[FixtureEngineNewPayload] = [] pre, genesis = self.make_genesis( - fork=fork, apply_pre_allocation_blockchain=fixture_format != BlockchainEngineXFixture, ) @@ -903,7 +898,6 @@ def make_hive_fixture( for i, block in enumerate(self.blocks): built_block = self.generate_block_data( t8n=t8n, - fork=fork, block=block, previous_env=env, previous_alloc=alloc, @@ -926,7 +920,7 @@ def make_hive_fixture( expected_state=block.expected_post_state, ) self.check_exception_test(exception=invalid_blocks > 0) - fcu_version = fork.engine_forkchoice_updated_version( + fcu_version = self.fork.engine_forkchoice_updated_version( block_number=built_block.header.number, timestamp=built_block.header.timestamp, ) @@ -942,7 +936,7 @@ def make_hive_fixture( if self._opcode_count is not None: info["opcode_count"] = self._opcode_count.model_dump() fixture_data = { - "fork": fork, + "fork": self.fork, "genesis": genesis.header, "payloads": fixture_payloads, "last_block_hash": head_hash, @@ -950,10 +944,10 @@ def make_hive_fixture( if self.exclude_full_post_state_in_output else None, "config": FixtureConfig( - fork=fork, + fork=self.fork, chain_id=self.chain_id, blob_schedule=FixtureBlobSchedule.from_blob_schedule( - fork.blob_schedule() + self.fork.blob_schedule() ), ), "info": info, @@ -982,7 +976,6 @@ def make_hive_fixture( # send it as new payload and trigger the sync process. sync_built_block = self.generate_block_data( t8n=t8n, - fork=fork, block=Block(), previous_env=env, previous_alloc=alloc, @@ -1013,7 +1006,6 @@ def make_hive_fixture( def generate( self, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat, ) -> BaseFixture: """Generate the BlockchainTest fixture.""" @@ -1023,21 +1015,18 @@ def generate( BlockchainEngineXFixture, BlockchainEngineSyncFixture, ]: - return self.make_hive_fixture(t8n, fork, fixture_format) + return self.make_hive_fixture(t8n, fixture_format) elif fixture_format == BlockchainFixture: - return self.make_fixture(t8n, fork) + return self.make_fixture(t8n) raise Exception(f"Unknown fixture format: {fixture_format}") def execute( self, *, - fork: Fork, execute_format: ExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" - del fork - if execute_format == TransactionPost: blocks: List[List[Transaction]] = [] for block in self.blocks: diff --git a/packages/testing/src/execution_testing/specs/eof.py b/packages/testing/src/execution_testing/specs/eof.py index 32dee55b0e3..f314d08a883 100644 --- a/packages/testing/src/execution_testing/specs/eof.py +++ b/packages/testing/src/execution_testing/specs/eof.py @@ -379,11 +379,7 @@ def model_post_init(self, __context: Any) -> None: if self.post is None: self.post = Alloc() - def make_eof_test_fixture( - self, - *, - fork: Fork, - ) -> EOFFixture: + def make_eof_test_fixture(self) -> EOFFixture: """Generate the EOF test fixture.""" container_bytes = Bytes(self.container) if container_bytes in existing_tests: @@ -397,7 +393,7 @@ def make_eof_test_fixture( code=container_bytes, container_kind=self.container_kind, results={ - fork: Result( + self.fork: Result( exception=self.expect_exception, valid=self.expect_exception is None, ), @@ -415,10 +411,10 @@ def make_eof_test_fixture( return fixture for _, vector in fixture.vectors.items(): - expected_result = vector.results.get(fork) + expected_result = vector.results.get(self.fork) if expected_result is None: raise Exception( - f"EOF Fixture missing vector result for fork: {fork}" + f"EOF Fixture missing vector result for fork: {self.fork}" ) args = [] if vector.container_kind == ContainerKind.INITCODE: @@ -534,9 +530,8 @@ def generate_eof_contract_create_transaction(self) -> Transaction: ) return tx - def generate_state_test(self, fork: Fork) -> StateTest: + def generate_state_test(self) -> StateTest: """Generate the StateTest filler.""" - del fork return StateTest.from_test( base_test=self, pre=self.pre, @@ -549,29 +544,27 @@ def generate( self, *, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat, **_: Any, ) -> BaseFixture: """Generate the BlockchainTest fixture.""" if fixture_format == EOFFixture: - return self.make_eof_test_fixture(fork=fork) + return self.make_eof_test_fixture() elif fixture_format in StateTest.supported_fixture_formats: - return self.generate_state_test(fork).generate( - t8n=t8n, fork=fork, fixture_format=fixture_format + return self.generate_state_test().generate( + t8n=t8n, fixture_format=fixture_format ) raise Exception(f"Unknown fixture format: {fixture_format}") def execute( self, *, - fork: Fork, execute_format: ExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" if execute_format == TransactionPost: - return self.generate_state_test(fork).execute( - fork=fork, execute_format=execute_format + return self.generate_state_test().execute( + execute_format=execute_format ) raise Exception(f"Unsupported execute format: {execute_format}") @@ -693,10 +686,8 @@ def model_post_init(self, __context: Any) -> None: self.post[self.to] = self.container_post - def generate_state_test(self, fork: Fork) -> StateTest: + def generate_state_test(self) -> StateTest: """Generate the StateTest filler.""" - del fork - assert self.pre is not None, "pre must be set to generate a StateTest." assert self.post is not None, ( "post must be set to generate a StateTest." @@ -714,7 +705,6 @@ def generate( self, *, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat, **_: Any, ) -> BaseFixture: @@ -726,10 +716,10 @@ def generate( pytest.skip( f"Duplicate EOF container on EOFStateTest: {self.node_id()}" ) - return self.make_eof_test_fixture(fork=fork) + return self.make_eof_test_fixture() elif fixture_format in StateTest.supported_fixture_formats: - return self.generate_state_test(fork).generate( - t8n=t8n, fork=fork, fixture_format=fixture_format + return self.generate_state_test().generate( + t8n=t8n, fixture_format=fixture_format ) raise Exception(f"Unknown fixture format: {fixture_format}") diff --git a/packages/testing/src/execution_testing/specs/state.py b/packages/testing/src/execution_testing/specs/state.py index 2fe8946d5f0..6a6d37a7664 100644 --- a/packages/testing/src/execution_testing/specs/state.py +++ b/packages/testing/src/execution_testing/specs/state.py @@ -17,13 +17,13 @@ from pydantic import Field from execution_testing.base_types import HexNumber +from execution_testing.cli.pytest_commands.plugins.custom_logging import ( + get_logger, +) from execution_testing.client_clis import ( TransitionTool, TransitionToolOutput, ) -from execution_testing.cli.pytest_commands.plugins.custom_logging import ( - get_logger, -) from execution_testing.exceptions import ( BlockException, EngineAPIError, @@ -245,9 +245,7 @@ def discard_fixture_format_by_marks( return fixture_format != StateFixture return False - def _generate_blockchain_genesis_environment( - self, *, fork: Fork - ) -> Environment: + def _generate_blockchain_genesis_environment(self) -> Environment: """ Generate the genesis environment for the BlockchainTest formatted test. """ @@ -289,12 +287,13 @@ def _generate_blockchain_genesis_environment( # will be subtracted from the excess blob gas when the first block # is mined. kwargs["excess_blob_gas"] = self.env.excess_blob_gas + ( - fork.target_blobs_per_block() * fork.blob_gas_per_blob() + self.fork.target_blobs_per_block() + * self.fork.blob_gas_per_blob() ) return Environment(**kwargs) - def _generate_blockchain_blocks(self, *, fork: Fork) -> List[Block]: + def _generate_blockchain_blocks(self) -> List[Block]: """ Generate the single block that represents this state test in a BlockchainTest format. @@ -314,7 +313,7 @@ def _generate_blockchain_blocks(self, *, fork: Fork) -> List[Block]: "rlp_modifier": self.blockchain_test_rlp_modifier, "expected_block_access_list": self.expected_block_access_list, } - if not fork.header_prev_randao_required(): + if not self.fork.header_prev_randao_required(): kwargs["difficulty"] = self.env.difficulty if "block_exception" in self.model_fields_set: kwargs["exception"] = self.block_exception # type: ignore @@ -322,28 +321,25 @@ def _generate_blockchain_blocks(self, *, fork: Fork) -> List[Block]: kwargs["exception"] = self.tx.error # type: ignore return [Block(**kwargs)] - def generate_blockchain_test(self, *, fork: Fork) -> BlockchainTest: + def generate_blockchain_test(self) -> BlockchainTest: """Generate a BlockchainTest fixture from this StateTest fixture.""" return BlockchainTest.from_test( base_test=self, - genesis_environment=self._generate_blockchain_genesis_environment( - fork=fork - ), + genesis_environment=self._generate_blockchain_genesis_environment(), pre=self.pre, post=self.post, - blocks=self._generate_blockchain_blocks(fork=fork), + blocks=self._generate_blockchain_blocks(), ) def make_state_test_fixture( self, t8n: TransitionTool, - fork: Fork, ) -> StateFixture: """Create a fixture from the state test definition.""" # We can't generate a state test fixture that names a transition fork, # so we get the fork at the block number and timestamp of the state # test - fork = fork.fork_at( + fork = self.fork.fork_at( block_number=self.env.number, timestamp=self.env.timestamp ) @@ -491,38 +487,32 @@ def make_state_test_fixture( ), ) - def get_genesis_environment(self, fork: Fork) -> Environment: + def get_genesis_environment(self) -> Environment: """Get the genesis environment for pre-allocation groups.""" - return self.generate_blockchain_test( - fork=fork - ).get_genesis_environment(fork=fork) + return self.generate_blockchain_test().get_genesis_environment() def generate( self, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat, ) -> BaseFixture: """Generate the BlockchainTest fixture.""" self.check_exception_test(exception=self.tx.error is not None) if fixture_format in BlockchainTest.supported_fixture_formats: - return self.generate_blockchain_test(fork=fork).generate( - t8n=t8n, fork=fork, fixture_format=fixture_format + return self.generate_blockchain_test().generate( + t8n=t8n, fixture_format=fixture_format ) elif fixture_format == StateFixture: - return self.make_state_test_fixture(t8n, fork) + return self.make_state_test_fixture(t8n) raise Exception(f"Unknown fixture format: {fixture_format}") def execute( self, *, - fork: Fork, execute_format: ExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" - del fork - if execute_format == TransactionPost: # Pass gas validation params for benchmark tests # If not benchmark mode, skip gas used validation diff --git a/packages/testing/src/execution_testing/specs/tests/test_benchmark.py b/packages/testing/src/execution_testing/specs/tests/test_benchmark.py index 897d3b17af4..215ec36de68 100644 --- a/packages/testing/src/execution_testing/specs/tests/test_benchmark.py +++ b/packages/testing/src/execution_testing/specs/tests/test_benchmark.py @@ -6,6 +6,7 @@ import pytest from execution_testing.base_types import HexNumber +from execution_testing.forks import Osaka from execution_testing.specs.benchmark import BenchmarkTest from execution_testing.test_types import Alloc, Environment, Transaction @@ -34,6 +35,7 @@ def test_split_transaction( # Create a minimal BenchmarkTest instance benchmark_test = BenchmarkTest( + fork=Osaka, pre=Alloc(), post=Alloc(), tx=Transaction(sender=HexNumber(0), to=HexNumber(0), nonce=0), @@ -96,7 +98,9 @@ def test_split_transaction_edge_cases( gas_benchmark_value: int, gas_limit_cap: int | None ) -> None: """Test edge cases for transaction splitting.""" + fork = Osaka benchmark_test = BenchmarkTest( + fork=fork, pre=Alloc(), post=Alloc(), tx=Transaction( diff --git a/packages/testing/src/execution_testing/specs/tests/test_expect.py b/packages/testing/src/execution_testing/specs/tests/test_expect.py index b6967bee59c..a73478d8fc7 100644 --- a/packages/testing/src/execution_testing/specs/tests/test_expect.py +++ b/packages/testing/src/execution_testing/specs/tests/test_expect.py @@ -70,13 +70,17 @@ def fork() -> Fork: # noqa: D103 @pytest.fixture def state_test( # noqa: D103 - pre: Mapping[Any, Any], post: Mapping[Any, Any], tx: Transaction + pre: Mapping[Any, Any], + post: Mapping[Any, Any], + tx: Transaction, + fork: Fork, ) -> StateTest: return StateTest( env=Environment(), pre=pre, post=post, tx=tx, + fork=fork, ) @@ -173,9 +177,7 @@ def test_post_storage_value_mismatch( generation. """ with pytest.raises(Storage.KeyValueMismatchError) as e_info: - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) assert e_info.value == expected_exception @@ -216,14 +218,10 @@ def test_post_nonce_value_mismatch( pre_nonce = pre_account.nonce post_nonce = post_account.nonce if "nonce" not in post_account.model_fields_set: # no exception - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) return with pytest.raises(Account.NonceMismatchError) as e_info: - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) assert e_info.value == Account.NonceMismatchError( address=ADDRESS_UNDER_TEST, want=post_nonce, got=pre_nonce ) @@ -266,14 +264,10 @@ def test_post_code_value_mismatch( pre_code = pre_account.code post_code = post_account.code if "code" not in post_account.model_fields_set: # no exception - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) return with pytest.raises(Account.CodeMismatchError) as e_info: - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) assert e_info.value == Account.CodeMismatchError( address=ADDRESS_UNDER_TEST, want=post_code, got=pre_code ) @@ -316,14 +310,10 @@ def test_post_balance_value_mismatch( pre_balance = pre_account.balance post_balance = post_account.balance if "balance" not in post_account.model_fields_set: # no exception - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) return with pytest.raises(Account.BalanceMismatchError) as e_info: - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) assert e_info.value == Account.BalanceMismatchError( address=ADDRESS_UNDER_TEST, want=post_balance, got=pre_balance ) @@ -370,14 +360,10 @@ def test_post_account_mismatch( fixture generation. """ if exception_type is None: - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) return with pytest.raises(exception_type) as _: - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=StateFixture - ) + state_test.generate(t8n=default_t8n, fixture_format=StateFixture) # Transaction result mismatch tests @@ -466,14 +452,10 @@ def test_transaction_expectation( f"({default_t8n.__class__.__name__})." ) if exception_type is None: - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=fixture_format - ) + state_test.generate(t8n=default_t8n, fixture_format=fixture_format) else: with pytest.raises(exception_type) as _: - state_test.generate( - t8n=default_t8n, fork=fork, fixture_format=fixture_format - ) + state_test.generate(t8n=default_t8n, fixture_format=fixture_format) @pytest.mark.parametrize( @@ -552,18 +534,18 @@ def test_block_intermediate_state( if expected_exception: with pytest.raises(expected_exception) as _: BlockchainTest( + fork=fork, genesis_environment=env, pre=pre, post=block_3.expected_post_state, blocks=[block_1, block_2, block_3], - ).generate( - t8n=default_t8n, fork=fork, fixture_format=fixture_format - ) + ).generate(t8n=default_t8n, fixture_format=fixture_format) return else: BlockchainTest( + fork=fork, genesis_environment=env, pre=pre, post=block_3.expected_post_state, blocks=[block_1, block_2, block_3], - ).generate(t8n=default_t8n, fork=fork, fixture_format=fixture_format) + ).generate(t8n=default_t8n, fixture_format=fixture_format) diff --git a/packages/testing/src/execution_testing/specs/tests/test_fixtures.py b/packages/testing/src/execution_testing/specs/tests/test_fixtures.py index 1b65c296cd4..aec091a0aba 100644 --- a/packages/testing/src/execution_testing/specs/tests/test_fixtures.py +++ b/packages/testing/src/execution_testing/specs/tests/test_fixtures.py @@ -109,12 +109,13 @@ def test_make_genesis( ) fixture = BlockchainTest( + fork=fork, genesis_environment=env, pre=pre, post={}, blocks=[], tag="some_state_test", - ).generate(t8n=default_t8n, fork=fork, fixture_format=BlockchainFixture) + ).generate(t8n=default_t8n, fixture_format=BlockchainFixture) assert isinstance(fixture, BlockchainFixture) assert fixture.genesis is not None @@ -193,12 +194,13 @@ def test_fill_state_test( } generated_fixture = StateTest( + fork=fork, env=env, pre=pre, post=post, tx=tx, tag="my_chain_id_test", - ).generate(t8n=default_t8n, fork=fork, fixture_format=fixture_format) + ).generate(t8n=default_t8n, fixture_format=fixture_format) assert generated_fixture.__class__ == fixture_format fixture = { f"000/my_chain_id_test/{fork}/tx_type_{tx_type}": generated_fixture.json_dict_with_info( @@ -521,12 +523,13 @@ def blockchain_test_fixture( # noqa: D102 default_t8n: TransitionTool, ) -> BaseFixture: return BlockchainTest( + fork=fork, pre=pre, post=post, blocks=blocks, genesis_environment=genesis_environment, tag="my_blockchain_test_valid_txs", - ).generate(t8n=default_t8n, fork=fork, fixture_format=fixture_format) + ).generate(t8n=default_t8n, fixture_format=fixture_format) @pytest.mark.parametrize("fork", [London, Shanghai], indirect=True) def test_fill_blockchain_valid_txs( # noqa: D102 @@ -914,11 +917,12 @@ def test_fill_blockchain_invalid_txs( BlockchainEngineFixture if check_hive else BlockchainFixture ) generated_fixture = BlockchainTest( + fork=fork, pre=pre, post=post, blocks=blocks, genesis_environment=genesis_environment, - ).generate(t8n=default_t8n, fork=fork, fixture_format=fixture_format) + ).generate(t8n=default_t8n, fixture_format=fixture_format) assert generated_fixture.__class__ == fixture_format # BlockchainEngineFixture inherits from BlockchainEngineFixtureCommon # (not BlockchainFixtureCommon) diff --git a/packages/testing/src/execution_testing/specs/tests/test_transaction.py b/packages/testing/src/execution_testing/specs/tests/test_transaction.py index 3a76e5050e0..0114e38029d 100644 --- a/packages/testing/src/execution_testing/specs/tests/test_transaction.py +++ b/packages/testing/src/execution_testing/specs/tests/test_transaction.py @@ -28,10 +28,10 @@ def test_transaction_test_filling( ) -> None: """Test the transaction test filling.""" generated_fixture = TransactionTest( - tx=tx.with_signature_and_sender() + tx=tx.with_signature_and_sender(), + fork=fork, ).generate( t8n=None, # type: ignore - fork=fork, fixture_format=TransactionFixture, ) assert generated_fixture.__class__ == TransactionFixture diff --git a/packages/testing/src/execution_testing/specs/transaction.py b/packages/testing/src/execution_testing/specs/transaction.py index 072cbfbb3e1..b1e108cdb36 100644 --- a/packages/testing/src/execution_testing/specs/transaction.py +++ b/packages/testing/src/execution_testing/specs/transaction.py @@ -16,7 +16,6 @@ TransactionFixture, ) from execution_testing.fixtures.transaction import FixtureResult -from execution_testing.forks import Fork from execution_testing.test_types import Alloc, Transaction from .base import BaseTest @@ -45,7 +44,6 @@ class TransactionTest(BaseTest): def make_transaction_test_fixture( self, - fork: Fork, ) -> TransactionFixture: """Create a fixture from the transaction test definition.""" if self.tx.error is not None: @@ -57,7 +55,7 @@ def make_transaction_test_fixture( ) else: intrinsic_gas_cost_calculator = ( - fork.transaction_intrinsic_cost_calculator() + self.fork.transaction_intrinsic_cost_calculator() ) intrinsic_gas = intrinsic_gas_cost_calculator( calldata=self.tx.data, @@ -74,7 +72,7 @@ def make_transaction_test_fixture( return TransactionFixture( result={ - fork: result, + self.fork: result, }, transaction=self.tx.with_signature_and_sender().rlp(), ) @@ -82,7 +80,6 @@ def make_transaction_test_fixture( def generate( self, t8n: TransitionTool, - fork: Fork, fixture_format: FixtureFormat, ) -> BaseFixture: """Generate the TransactionTest fixture.""" @@ -90,19 +87,16 @@ def generate( self.check_exception_test(exception=self.tx.error is not None) if fixture_format == TransactionFixture: - return self.make_transaction_test_fixture(fork) + return self.make_transaction_test_fixture() raise Exception(f"Unknown fixture format: {fixture_format}") def execute( self, *, - fork: Fork, execute_format: ExecuteFormat, ) -> BaseExecute: """Execute the transaction test by sending it to the live network.""" - del fork - if execute_format == TransactionPost: return TransactionPost( blocks=[[self.tx]], diff --git a/packages/testing/src/execution_testing/tools/tests/test_code.py b/packages/testing/src/execution_testing/tools/tests/test_code.py index 542cc41c8be..80f9e2b41a4 100644 --- a/packages/testing/src/execution_testing/tools/tests/test_code.py +++ b/packages/testing/src/execution_testing/tools/tests/test_code.py @@ -690,13 +690,13 @@ def test_switch( } state_test = StateTest( env=Environment(), + fork=Cancun, pre=pre, tx=tx, post=post, ) state_test.generate( t8n=default_t8n, - fork=Cancun, fixture_format=BlockchainFixture, ) diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index a5fcfaeb1be..4d71e380632 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -109,11 +109,6 @@ def state_transition(self) -> Any: """state_transition function of the fork.""" return self._module("fork").state_transition - @property - def pay_rewards(self) -> Any: - """pay_rewards function of the fork.""" - return self._module("fork").pay_rewards - @property def signing_hash(self) -> Any: """signing_hash function of the fork.""" @@ -159,11 +154,6 @@ def process_transaction(self) -> Any: """process_transaction function of the fork.""" return self._module("fork").process_transaction - @property - def MAX_BLOB_GAS_PER_BLOCK(self) -> Any: - """MAX_BLOB_GAS_PER_BLOCK parameter of the fork.""" - return self._module("fork").MAX_BLOB_GAS_PER_BLOCK - @property def Block(self) -> Any: """Block class of the fork.""" @@ -239,11 +229,6 @@ def Withdrawal(self) -> Any: """Withdrawal class of the fork.""" return self._module("blocks").Withdrawal - @property - def encode_transaction(self) -> Any: - """encode_transaction function of the fork.""" - return self._module("transactions").encode_transaction - @property def decode_transaction(self) -> Any: """decode_transaction function of the fork.""" @@ -320,29 +305,9 @@ def Authorization(self) -> Any: return self._module("fork_types").Authorization @property - def TARGET_BLOB_GAS_PER_BLOCK(self) -> Any: - """TARGET_BLOB_GAS_PER_BLOCK of the fork.""" - return self._module("vm.gas").TARGET_BLOB_GAS_PER_BLOCK - - @property - def GAS_PER_BLOB(self) -> Any: - """GAS_PER_BLOB of the fork.""" - return self._module("vm.gas").GAS_PER_BLOB - - @property - def BLOB_BASE_COST(self) -> Any: - """BLOB_BASE_COST of the fork.""" - return self._module("vm.gas").BLOB_BASE_COST - - @property - def BLOB_SCHEDULE_MAX(self) -> Any: - """BLOB_SCHEDULE_MAX of the fork.""" - return self._module("vm.gas").BLOB_SCHEDULE_MAX - - @property - def BLOB_SCHEDULE_TARGET(self) -> Any: - """BLOB_SCHEDULE_TARGET of the fork.""" - return self._module("vm.gas").BLOB_SCHEDULE_TARGET + def calculate_excess_blob_gas(self) -> Any: + """calculate_excess_blob_gas of the fork.""" + return self._module("vm.gas").calculate_excess_blob_gas @property def calculate_blob_gas_price(self) -> Any: diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index 614e1f57c89..75fb83bcfaf 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes32 +from ethereum_types.bytes import Bytes8, Bytes20, Bytes32, Bytes256 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -99,11 +99,6 @@ def read_excess_blob_gas(self, data: Any, t8n: "T8N") -> None: if not t8n.fork.is_after_fork("cancun"): return - if "currentExcessBlobGas" in data: - self.excess_blob_gas = parse_hex_or_int( - data["currentExcessBlobGas"], U64 - ) - if "parentExcessBlobGas" in data: self.parent_excess_blob_gas = parse_hex_or_int( data["parentExcessBlobGas"], U64 @@ -114,52 +109,49 @@ def read_excess_blob_gas(self, data: Any, t8n: "T8N") -> None: data["parentBlobGasUsed"], U64 ) - if self.excess_blob_gas is not None: + if "currentExcessBlobGas" in data: + self.excess_blob_gas = parse_hex_or_int( + data["currentExcessBlobGas"], U64 + ) return assert self.parent_excess_blob_gas is not None assert self.parent_blob_gas_used is not None - parent_blob_gas = ( - self.parent_excess_blob_gas + self.parent_blob_gas_used + arguments = { + # Useless as far as calculate_excess_blob_gas is concerned. + "parent_hash": Hash32(b"\0" * 32), + "ommers_hash": Hash32(b"\0" * 32), + "coinbase": Bytes20(b"\0" * 20), + "state_root": Hash32(b"\0" * 32), + "transactions_root": Hash32(b"\0" * 32), + "receipt_root": Hash32(b"\0" * 32), + "bloom": Bytes256(b"\0" * 256), + "difficulty": Uint(0), + "number": Uint(0), + "gas_limit": Uint(0), + "gas_used": Uint(0), + "timestamp": U256(0), + "extra_data": b"", + "prev_randao": Bytes32(b"\0" * 32), + "nonce": Bytes8(b"\0" * 8), + "withdrawals_root": Hash32(b"\0" * 32), + "parent_beacon_block_root": Hash32(b"\0" * 32), + # Used for calculating excess_blob_gas. + "base_fee_per_gas": self.parent_base_fee_per_gas, + "blob_gas_used": self.parent_blob_gas_used, + "excess_blob_gas": self.parent_excess_blob_gas, + } + + if t8n.fork.is_after_fork("prague"): + arguments["requests_hash"] = Hash32(b"\0" * 32) + + parent_header = t8n.fork.Header(**arguments) + + self.excess_blob_gas = t8n.fork.calculate_excess_blob_gas( + parent_header ) - target_blob_gas_per_block = t8n.fork.TARGET_BLOB_GAS_PER_BLOCK - - if parent_blob_gas < target_blob_gas_per_block: - self.excess_blob_gas = U64(0) - else: - self.excess_blob_gas = parent_blob_gas - target_blob_gas_per_block - - if t8n.fork.is_after_fork("osaka"): - # Under certain conditions specified in EIP-7918, the - # the excess_blob_gas is calculated differently in osaka - assert self.parent_base_fee_per_gas is not None - - GAS_PER_BLOB = t8n.fork.GAS_PER_BLOB # noqa N806 - BLOB_BASE_COST = t8n.fork.BLOB_BASE_COST # noqa N806 - BLOB_SCHEDULE_MAX = t8n.fork.BLOB_SCHEDULE_MAX # noqa N806 - BLOB_SCHEDULE_TARGET = t8n.fork.BLOB_SCHEDULE_TARGET # noqa N806 - - target_blob_gas_price = Uint(GAS_PER_BLOB) - target_blob_gas_price *= t8n.fork.calculate_blob_gas_price( - self.parent_excess_blob_gas - ) - - base_blob_tx_price = ( - BLOB_BASE_COST * self.parent_base_fee_per_gas - ) - if base_blob_tx_price > target_blob_gas_price: - blob_schedule_delta = ( - BLOB_SCHEDULE_MAX - BLOB_SCHEDULE_TARGET - ) - self.excess_blob_gas = ( - self.parent_excess_blob_gas - + self.parent_blob_gas_used - * blob_schedule_delta - // BLOB_SCHEDULE_MAX - ) - def read_base_fee_per_gas(self, data: Any, t8n: "T8N") -> None: """ Read the base_fee_per_gas from the data. If the base fee is