From 1e16f55a14623d5389c31dfe77f7504686c57aee Mon Sep 17 00:00:00 2001 From: qu0b Date: Tue, 17 Mar 2026 21:28:47 +0000 Subject: [PATCH] feat: add EIP-7843 slot_number support and fix genesis compatibility with geth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes to make EELS-generated Amsterdam fixtures consumable by geth: 1. Add `slot_number` (EIP-7843) to Amsterdam block header - `src/ethereum/forks/amsterdam/blocks.py`: Add `slot_number: U64` field - `src/ethereum/genesis.py`: Default `slot_number = U64(0)` for genesis - `src/ethereum_spec_tools/evm_tools/t8n/env.py`: Include in t8n parent header - Testing framework: Add to FixtureHeader, FixtureExecutionPayload, fork requirements, and genesis construction 2. Fix genesis nonce parsing for hex values - `src/ethereum/genesis.py`: `int(nonce)` → `hex_to_uint(nonce)` to handle hex nonces like "0x01" in alloc data 3. Fix genesis requests_hash to match EIP-7685 spec - `src/ethereum/genesis.py`: Use SHA-256("") instead of zero hash for empty requests_hash, matching geth's `EmptyRequestsHash` Without these fixes, EELS and geth compute different genesis block hashes from the same alloc, causing all EELS-generated Amsterdam fixtures to be rejected by geth with "missing parent" errors. Tested: 44/44 BAL scenario benchmarks pass in benchmarkoor with geth across all 3 BAL execution modes (full, sequential, nobatchio). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../execution_testing/client_clis/cli_types.py | 3 +++ .../src/execution_testing/fixtures/blockchain.py | 11 +++++++++++ .../src/execution_testing/forks/base_fork.py | 8 ++++++++ .../src/execution_testing/forks/forks/forks.py | 16 ++++++++++++++++ src/ethereum/forks/amsterdam/blocks.py | 8 ++++++++ src/ethereum/genesis.py | 12 ++++++++++-- src/ethereum_spec_tools/evm_tools/t8n/env.py | 4 ++++ 7 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/testing/src/execution_testing/client_clis/cli_types.py b/packages/testing/src/execution_testing/client_clis/cli_types.py index 333e1103582..0e76e5f0b44 100644 --- a/packages/testing/src/execution_testing/client_clis/cli_types.py +++ b/packages/testing/src/execution_testing/client_clis/cli_types.py @@ -288,6 +288,9 @@ class Result(CamelModel): requests: List[Bytes] | None = None block_access_list: BlockAccessList | None = None block_access_list_hash: Hash | None = None + slot_number: HexNumber | None = Field( + None, alias="currentSlotNumber" + ) block_exception: Annotated[ BlockExceptionWithMessage | UndefinedException | None, ExceptionMapperValidator, diff --git a/packages/testing/src/execution_testing/fixtures/blockchain.py b/packages/testing/src/execution_testing/fixtures/blockchain.py index 6b15039515a..4447c9bf19a 100644 --- a/packages/testing/src/execution_testing/fixtures/blockchain.py +++ b/packages/testing/src/execution_testing/fixtures/blockchain.py @@ -214,6 +214,9 @@ class FixtureHeader(CamelModel): block_access_list_hash: ( Annotated[Hash, HeaderForkRequirement("bal_hash")] | None ) = Field(None, alias="blockAccessListHash") + slot_number: ZeroPaddedHexNumber | None = Field( + None, alias="slotNumber" + ) fork: Fork | None = Field(None, exclude=True) @@ -382,6 +385,13 @@ def genesis(cls, fork: Fork, env: Environment, state_root: Hash) -> Self: if fork.header_bal_hash_required(block_number=0, timestamp=0) else None ), + "slot_number": ( + ZeroPaddedHexNumber(0) + if fork.header_slot_number_required( + block_number=0, timestamp=0 + ) + else None + ), "fork": fork, } return cls(**environment_values, **extras) @@ -422,6 +432,7 @@ class FixtureExecutionPayload(CamelModel): block_access_list: Bytes | None = Field( None, description="RLP-serialized EIP-7928 Block Access List" ) + slot_number: HexNumber | None = Field(None) @classmethod def from_fixture_header( diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 89fa18afc26..6bbfff2124b 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -361,6 +361,14 @@ def header_bal_hash_required( """Return true if the header must contain block access list hash.""" pass + @classmethod + @abstractmethod + def header_slot_number_required( + cls, *, block_number: int = 0, timestamp: int = 0 + ) -> bool: + """Return true if the header must contain slot_number (EIP-7843).""" + pass + # Gas related abstract methods @classmethod diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index c2d219953f5..fee3045f5a8 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -976,6 +976,14 @@ def header_bal_hash_required( del block_number, timestamp return False + @classmethod + def header_slot_number_required( + cls, *, block_number: int = 0, timestamp: int = 0 + ) -> bool: + """Pre-Amsterdam forks have no slot number in header.""" + del block_number, timestamp + return False + @classmethod def engine_new_payload_version( cls, *, block_number: int = 0, timestamp: int = 0 @@ -3360,6 +3368,14 @@ def header_bal_hash_required( del block_number, timestamp return True + @classmethod + def header_slot_number_required( + cls, *, block_number: int = 0, timestamp: int = 0 + ) -> bool: + """Amsterdam requires slot_number in header (EIP-7843).""" + del block_number, timestamp + return True + @classmethod def is_deployed(cls) -> bool: """Return True if this fork is deployed.""" diff --git a/src/ethereum/forks/amsterdam/blocks.py b/src/ethereum/forks/amsterdam/blocks.py index ef44549d280..94e04b1bbe6 100644 --- a/src/ethereum/forks/amsterdam/blocks.py +++ b/src/ethereum/forks/amsterdam/blocks.py @@ -254,6 +254,14 @@ class Header: [cbalh]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_utils.compute_block_access_list_hash """ # noqa: E501 + slot_number: U64 + """ + The beacon chain slot number corresponding to this execution block. + Introduced in [EIP-7843]. + + [EIP-7843]: https://eips.ethereum.org/EIPS/eip-7843 + """ + @slotted_freezable @dataclass diff --git a/src/ethereum/genesis.py b/src/ethereum/genesis.py index 444302ffebf..51525396c18 100644 --- a/src/ethereum/genesis.py +++ b/src/ethereum/genesis.py @@ -209,7 +209,7 @@ def add_genesis_block( chain.state, address, hardfork.Account( - Uint(int(account.get("nonce", "0"))), + hex_to_uint(account.get("nonce", "0x0")), hex_or_base_10_str_to_u256(account.get("balance", 0)), hex_to_bytes(account.get("code", "0x")), ), @@ -257,11 +257,19 @@ def add_genesis_block( fields["parent_beacon_block_root"] = Hash32(b"\0" * 32) if has_field(hardfork.Header, "requests_hash"): - fields["requests_hash"] = Hash32(b"\0" * 32) + # SHA-256 of empty string per EIP-7685 + fields["requests_hash"] = Hash32( + bytes.fromhex( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ) + ) if has_field(hardfork.Header, "block_access_list_hash"): fields["block_access_list_hash"] = keccak256(rlp.encode([])) + if has_field(hardfork.Header, "slot_number"): + fields["slot_number"] = U64(0) + genesis_header = hardfork.Header(**fields) block_fields = { diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index be719ba7af5..e025a0c14ae 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -11,6 +11,7 @@ from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.utils import has_field from ethereum.utils.byte import left_pad_zero_bytes from ethereum.utils.hexadecimal import hex_to_bytes @@ -148,6 +149,9 @@ def read_excess_blob_gas(self, data: Any, t8n: "T8N") -> None: if t8n.fork.has_block_access_list_hash: arguments["block_access_list_hash"] = Hash32(b"\0" * 32) + if has_field(t8n.fork.Header, "slot_number"): + arguments["slot_number"] = U64(0) + parent_header = t8n.fork.Header(**arguments) self.excess_blob_gas = t8n.fork.calculate_excess_blob_gas(