From 42ed8cd394f9dab4bc100b096f5c3ff34faa16fb Mon Sep 17 00:00:00 2001 From: felix Date: Mon, 30 Mar 2026 12:40:16 +0200 Subject: [PATCH 1/2] fix: path the receipt parser so a non-empty geth JSON root is treated as pre-Byzantium post_state and takes precedence over status (#2576) --- .../test_types/receipt_types.py | 9 +++++++ .../test_types/tests/test_types.py | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/packages/testing/src/execution_testing/test_types/receipt_types.py b/packages/testing/src/execution_testing/test_types/receipt_types.py index 9fe5428feff..40266d6e8a4 100644 --- a/packages/testing/src/execution_testing/test_types/receipt_types.py +++ b/packages/testing/src/execution_testing/test_types/receipt_types.py @@ -45,9 +45,18 @@ class TransactionReceipt(CamelModel): def strip_extra_fields(cls, data: Any) -> Any: """Strip extra fields from t8n tool output not part of model.""" if isinstance(data, dict): + data = dict(data) # geth (1.16+) returns extra fields in receipts data.pop("type", None) data.pop("blockNumber", None) + root = data.get("root") + root_is_empty = root in (None, "", "0x", b"", bytearray()) + if not root_is_empty: + # geth's t8n JSON uses `root` for pre-Byzantium receipts while + # also populating `status`. For fixture re-encoding, a + # non-empty root must take precedence over status. + data.setdefault("post_state", root) + data.pop("status", None) return data transaction_hash: Hash | None = None diff --git a/packages/testing/src/execution_testing/test_types/tests/test_types.py b/packages/testing/src/execution_testing/test_types/tests/test_types.py index 41000515298..bf01f9af30f 100644 --- a/packages/testing/src/execution_testing/test_types/tests/test_types.py +++ b/packages/testing/src/execution_testing/test_types/tests/test_types.py @@ -21,6 +21,7 @@ Environment, Withdrawal, ) +from ..receipt_types import TransactionReceipt from ..transaction_types import ( AuthorizationTuple, Transaction, @@ -93,6 +94,31 @@ def test_storage() -> None: } +def test_transaction_receipt_maps_non_empty_root_to_post_state() -> None: + """Non-empty `root` from geth should be treated as pre-Byzantium state.""" + receipt = TransactionReceipt.model_validate( + { + "root": "0x" + "11" * 32, + "status": "0x1", + } + ) + assert str(receipt.post_state) == "0x" + "11" * 32 + assert receipt.status is None + + +def test_transaction_receipt_keeps_status_when_root_is_empty() -> None: + """Empty `root` should not override Byzantium-style receipt status.""" + receipt = TransactionReceipt.model_validate( + { + "root": "0x", + "status": "0x1", + } + ) + assert receipt.post_state is None + assert receipt.status is not None + assert int(receipt.status) == 1 + + @pytest.mark.parametrize( ["account"], [ From fd1dd634e8ba0a6009eb70f4705d705b147d5055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:32:39 +0800 Subject: [PATCH 2/2] feat(test-fixture): add `metadata` subfield to `_info` with `target_opcode` (#2520) --- .../src/execution_testing/cli/diff_opcode_counts.py | 13 ++++++------- .../cli/pytest_commands/plugins/filler/filler.py | 10 +++++++++- .../testing/src/execution_testing/fixtures/base.py | 7 +++---- .../execution_testing/fixtures/tests/test_base.py | 1 - .../fixtures/tests/test_collector.py | 1 - .../testing/src/execution_testing/specs/base.py | 3 ++- .../src/execution_testing/specs/benchmark.py | 4 ++++ 7 files changed, 24 insertions(+), 15 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/diff_opcode_counts.py b/packages/testing/src/execution_testing/cli/diff_opcode_counts.py index 8922f5ff079..41f62b528ca 100644 --- a/packages/testing/src/execution_testing/cli/diff_opcode_counts.py +++ b/packages/testing/src/execution_testing/cli/diff_opcode_counts.py @@ -55,17 +55,16 @@ def load_fixtures_from_file( def extract_opcode_counts_from_fixtures( fixtures: Fixtures, ) -> Dict[str, OpcodeCount]: - """Extract opcode_count from info field for each fixture.""" + """Extract opcode_count from the metadata field for each fixture.""" opcode_counts = {} for fixture_name, fixture in fixtures.items(): - if ( - hasattr(fixture, "info") - and fixture.info - and "opcode_count" in fixture.info - ): + if not (hasattr(fixture, "info") and fixture.info): + continue + metadata = fixture.info.get("metadata") + if isinstance(metadata, dict) and "opcode_count" in metadata: try: opcode_count = OpcodeCount.model_validate( - fixture.info["opcode_count"] + metadata["opcode_count"] ) opcode_counts[fixture_name] = opcode_count except Exception as e: 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 3ba89261097..5a733c09ad5 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 @@ -1812,13 +1812,21 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: fixture.post_state, group.pre ) + fill_metadata: Dict[str, Any] = {} + if t8n.opcode_count is not None: + fill_metadata["opcode_count"] = ( + t8n.opcode_count.model_dump() + ) + if fill_result.metadata: + fill_metadata.update(fill_result.metadata) + fixture.fill_info( t8n.version(), test_case_description, fixture_source_url=fixture_source_url, - opcode_count=t8n.opcode_count, ref_spec=reference_spec, _info_metadata=t8n._info_metadata, + metadata=fill_metadata, ) output_subdir = resolve_fixture_subfolder( diff --git a/packages/testing/src/execution_testing/fixtures/base.py b/packages/testing/src/execution_testing/fixtures/base.py index ec70377140e..d2bc45c31bb 100644 --- a/packages/testing/src/execution_testing/fixtures/base.py +++ b/packages/testing/src/execution_testing/fixtures/base.py @@ -29,7 +29,6 @@ from pydantic_core.core_schema import ValidatorFunctionWrapHandler from execution_testing.base_types import CamelModel, ReferenceSpec -from execution_testing.client_clis.cli_types import OpcodeCount from execution_testing.fixtures.post_verifications import PostVerifications from execution_testing.forks import Fork, TransitionFork @@ -170,9 +169,9 @@ def fill_info( t8n_version: str, test_case_description: str, fixture_source_url: str, - opcode_count: OpcodeCount | None, ref_spec: ReferenceSpec | None, _info_metadata: Dict[str, Any] | None, + metadata: Dict[str, Any] | None = None, ) -> None: """Fill the info field for this fixture.""" if "comment" not in self.info: @@ -180,8 +179,8 @@ def fill_info( self.info["filling-transition-tool"] = t8n_version self.info["description"] = test_case_description self.info["url"] = fixture_source_url - if opcode_count is not None: - self.info["opcode_count"] = opcode_count.model_dump() + if metadata: + self.info["metadata"] = metadata if ref_spec is not None: ref_spec.write_info(self.info) if _info_metadata: diff --git a/packages/testing/src/execution_testing/fixtures/tests/test_base.py b/packages/testing/src/execution_testing/fixtures/tests/test_base.py index 892eb5ac8b0..045c137da55 100644 --- a/packages/testing/src/execution_testing/fixtures/tests/test_base.py +++ b/packages/testing/src/execution_testing/fixtures/tests/test_base.py @@ -149,7 +149,6 @@ def test_base_fixtures_parsing(fixture: BaseFixture) -> None: "t8n-version", "test_case_description", fixture_source_url="fixture_source_url", - opcode_count=None, ref_spec=None, _info_metadata={}, ) diff --git a/packages/testing/src/execution_testing/fixtures/tests/test_collector.py b/packages/testing/src/execution_testing/fixtures/tests/test_collector.py index 93b6e3794f1..4b41f1c6d4f 100644 --- a/packages/testing/src/execution_testing/fixtures/tests/test_collector.py +++ b/packages/testing/src/execution_testing/fixtures/tests/test_collector.py @@ -22,7 +22,6 @@ def _make_fixture(nonce: int = 0) -> TransactionFixture: f"test description {nonce}", fixture_source_url="http://example.com", ref_spec=None, - opcode_count=None, _info_metadata={}, ) return fixture diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index 468df77d452..8aee027f097 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -17,7 +17,7 @@ ) import pytest -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from typing_extensions import Self from execution_testing.base_types import to_hex @@ -96,6 +96,7 @@ class FillResult(BaseModel): benchmark_gas_used: int | None = None benchmark_opcode_count: OpcodeCount | None = None post_verifications: PostVerifications | None = None + metadata: Dict[str, Any] = Field(default_factory=dict) class BaseTest(BaseModel): diff --git a/packages/testing/src/execution_testing/specs/benchmark.py b/packages/testing/src/execution_testing/specs/benchmark.py index f4a30746ccf..81ec46dcb90 100644 --- a/packages/testing/src/execution_testing/specs/benchmark.py +++ b/packages/testing/src/execution_testing/specs/benchmark.py @@ -554,6 +554,10 @@ def generate( self._verify_target_opcode_count( fill_result.benchmark_opcode_count ) + + if self.target_opcode is not None: + fill_result.metadata["target_opcode"] = str(self.target_opcode) + return fill_result else: raise Exception(f"Unsupported fixture format: {fixture_format}")