Skip to content

Commit 2f7daf1

Browse files
felix314159marioevz
authored andcommitted
feat(src): Initial EIP-7843 (SLOTNUM) Implementation (#2007)
* feat(amsterdam): Implement EIP-7843 SLOTNUM opcode * mario feedback
1 parent 2c549ce commit 2f7daf1

21 files changed

Lines changed: 326 additions & 6 deletions

File tree

packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/single_test_client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ def client_genesis(fixture: BlockchainFixtureCommon) -> dict:
3333
alloc = to_json(fixture.pre)
3434
# NOTE: nethermind requires account keys without '0x' prefix
3535
genesis["alloc"] = {k.replace("0x", ""): v for k, v in alloc.items()}
36+
# NOTE: geth expects slotNumber as plain integer, not hex string
37+
if "slotNumber" in genesis:
38+
genesis["slotNumber"] = int(genesis["slotNumber"], 16)
3639
return genesis
3740

3841

packages/testing/src/execution_testing/fixtures/blockchain.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ class FixtureHeader(CamelModel):
214214
block_access_list_hash: (
215215
Annotated[Hash, HeaderForkRequirement("bal_hash")] | None
216216
) = Field(None, alias="blockAccessListHash")
217+
slot_number: (
218+
Annotated[ZeroPaddedHexNumber, HeaderForkRequirement("slot_number")]
219+
| None
220+
) = Field(None)
217221

218222
fork: Fork | None = Field(None, exclude=True)
219223

@@ -365,7 +369,7 @@ def get_default_from_annotation(
365369
def genesis(cls, fork: Fork, env: Environment, state_root: Hash) -> Self:
366370
"""Get the genesis header for the given fork."""
367371
environment_values = env.model_dump(
368-
exclude_none=True, exclude={"withdrawals"}
372+
exclude_none=True, exclude={"withdrawals", "slot_number"}
369373
)
370374
if env.withdrawals is not None:
371375
environment_values["withdrawals_root"] = Withdrawal.list_root(
@@ -382,6 +386,11 @@ def genesis(cls, fork: Fork, env: Environment, state_root: Hash) -> Self:
382386
if fork.header_bal_hash_required(block_number=0, timestamp=0)
383387
else None
384388
),
389+
"slot_number": (
390+
0
391+
if fork.header_slot_number_required(block_number=0, timestamp=0)
392+
else None
393+
),
385394
"fork": fork,
386395
}
387396
return cls(**environment_values, **extras)
@@ -422,6 +431,7 @@ class FixtureExecutionPayload(CamelModel):
422431
block_access_list: Bytes | None = Field(
423432
None, description="RLP-serialized EIP-7928 Block Access List"
424433
)
434+
slot_number: HexNumber | None = Field(None)
425435

426436
@classmethod
427437
def from_fixture_header(

packages/testing/src/execution_testing/forks/base_fork.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,14 @@ def header_bal_hash_required(
361361
"""Return true if the header must contain block access list hash."""
362362
pass
363363

364+
@classmethod
365+
@abstractmethod
366+
def header_slot_number_required(
367+
cls, *, block_number: int = 0, timestamp: int = 0
368+
) -> bool:
369+
"""Return true if the header must contain slot number (EIP-7843)."""
370+
pass
371+
364372
# Gas related abstract methods
365373

366374
@classmethod

packages/testing/src/execution_testing/forks/forks/forks.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,14 @@ def header_beacon_root_required(
976976
del block_number, timestamp
977977
return False
978978

979+
@classmethod
980+
def header_slot_number_required(
981+
cls, *, block_number: int = 0, timestamp: int = 0
982+
) -> bool:
983+
"""At genesis, header must not contain slot number (EIP-7843)."""
984+
del block_number, timestamp
985+
return False
986+
979987
@classmethod
980988
def engine_new_payload_blob_hashes(
981989
cls, *, block_number: int = 0, timestamp: int = 0
@@ -3338,9 +3346,7 @@ class Amsterdam(BPO2):
33383346
def header_bal_hash_required(
33393347
cls, *, block_number: int = 0, timestamp: int = 0
33403348
) -> bool:
3341-
"""
3342-
From Amsterdam, header must contain block access list hash (EIP-7928).
3343-
"""
3349+
"""BAL hash in header required from Amsterdam (EIP-7928)."""
33443350
del block_number, timestamp
33453351
return True
33463352

@@ -3367,3 +3373,36 @@ def engine_execution_payload_block_access_list(
33673373
"""
33683374
del block_number, timestamp
33693375
return True
3376+
3377+
@classmethod
3378+
def header_slot_number_required(
3379+
cls, *, block_number: int = 0, timestamp: int = 0
3380+
) -> bool:
3381+
"""Slot number in header required from Amsterdam (EIP-7843)."""
3382+
del block_number, timestamp
3383+
return True
3384+
3385+
@classmethod
3386+
def opcode_gas_map(
3387+
cls, *, block_number: int = 0, timestamp: int = 0
3388+
) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]:
3389+
"""Add SLOTNUM opcode gas cost for Amsterdam (EIP-7843)."""
3390+
gas_costs = cls.gas_costs(
3391+
block_number=block_number, timestamp=timestamp
3392+
)
3393+
base_map = super(Amsterdam, cls).opcode_gas_map(
3394+
block_number=block_number, timestamp=timestamp
3395+
)
3396+
return {
3397+
**base_map,
3398+
Opcodes.SLOTNUM: gas_costs.G_BASE,
3399+
}
3400+
3401+
@classmethod
3402+
def valid_opcodes(
3403+
cls, *, block_number: int = 0, timestamp: int = 0
3404+
) -> List[Opcodes]:
3405+
"""Add SLOTNUM opcode for Amsterdam (EIP-7843)."""
3406+
return [Opcodes.SLOTNUM] + super(Amsterdam, cls).valid_opcodes(
3407+
block_number=block_number, timestamp=timestamp
3408+
)

packages/testing/src/execution_testing/specs/blockchain.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class Header(CamelModel):
157157
parent_beacon_block_root: Removable | Hash | None = None
158158
requests_hash: Removable | Hash | None = None
159159
bal_hash: Removable | Hash | None = None
160+
slot_number: Removable | HexNumber | None = None
160161

161162
REMOVE_FIELD: ClassVar[Removable] = Removable()
162163
"""
@@ -328,6 +329,8 @@ def set_environment(self, env: Environment) -> Environment:
328329
and self.block_access_list is not None
329330
):
330331
new_env_values["block_access_list"] = self.block_access_list
332+
if not isinstance(self.slot_number, Removable):
333+
new_env_values["slot_number"] = self.slot_number
331334
"""
332335
These values are required, but they depend on the previous environment,
333336
so they can be calculated here.
@@ -667,6 +670,12 @@ def generate_block_data(
667670
fork=self.fork,
668671
)
669672

673+
# Clear block_access_list_hash if the fork doesn't require it
674+
if not self.fork.header_bal_hash_required(
675+
block_number=int(env.number), timestamp=int(env.timestamp)
676+
):
677+
header.block_access_list_hash = None
678+
670679
if block.header_verify is not None:
671680
# Verify the header after transition tool processing.
672681
try:
@@ -763,8 +772,10 @@ def generate_block_data(
763772
bal = block.expected_block_access_list.modify_if_invalid_test(
764773
t8n_bal
765774
)
766-
if bal != t8n_bal:
767-
# If the BAL was modified, update the header hash
775+
if bal != t8n_bal and self.fork.header_bal_hash_required(
776+
block_number=int(env.number), timestamp=int(env.timestamp)
777+
):
778+
# If the BAL was modified and the fork requires it, update the header hash
768779
header.block_access_list_hash = Hash(bal.rlp.keccak256())
769780

770781
built_block = BuiltBlock(

packages/testing/src/execution_testing/specs/state.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ def _generate_blockchain_blocks(self) -> List[Block]:
311311
"extra_data": self.env.extra_data,
312312
"withdrawals": self.env.withdrawals,
313313
"parent_beacon_block_root": self.env.parent_beacon_block_root,
314+
"slot_number": self.env.slot_number,
314315
"txs": [self.tx],
315316
"ommers": [],
316317
"header_verify": self.blockchain_test_header_verify,

packages/testing/src/execution_testing/specs/static_state/environment.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class EnvironmentInStateTestFiller(BaseModel):
3333
current_excess_blob_gas: ValueInFiller | None = Field(
3434
None, alias="currentExcessBlobGas"
3535
)
36+
current_slot_number: ValueInFiller | None = Field(None, alias="slotNumber")
3637

3738
model_config = ConfigDict(extra="forbid")
3839

@@ -72,4 +73,6 @@ def get_environment(self, tags: TagDict) -> Environment:
7273
kwargs["base_fee_per_gas"] = self.current_base_fee
7374
if self.current_excess_blob_gas is not None:
7475
kwargs["excess_blob_gas"] = self.current_excess_blob_gas
76+
if self.current_slot_number is not None:
77+
kwargs["slot_number"] = self.current_slot_number
7578
return Environment(**kwargs)

packages/testing/src/execution_testing/test_types/block_types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class EnvironmentGeneric(CamelModel, Generic[NumberBoundTypeVar]):
101101
excess_blob_gas: NumberBoundTypeVar | None = Field(
102102
None, alias="currentExcessBlobGas"
103103
)
104+
slot_number: NumberBoundTypeVar | None = Field(None, alias="slotNumber")
104105

105106
parent_difficulty: NumberBoundTypeVar | None = Field(None)
106107
parent_timestamp: NumberBoundTypeVar | None = Field(None)
@@ -223,6 +224,14 @@ def set_fork_requirements(self, fork: Fork) -> "Environment":
223224
):
224225
updated_values["parent_beacon_block_root"] = 0
225226

227+
if (
228+
fork.header_slot_number_required(
229+
block_number=number, timestamp=timestamp
230+
)
231+
and self.slot_number is None
232+
):
233+
updated_values["slot_number"] = 0
234+
226235
return self.copy(**updated_values)
227236

228237
def __hash__(self) -> int:

packages/testing/src/execution_testing/vm/opcodes.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2225,6 +2225,36 @@ class Opcodes(Opcode, Enum):
22252225
Source: [EIP-7516](https://eips.ethereum.org/EIPS/eip-7516)
22262226
"""
22272227

2228+
SLOTNUM = Opcode(0x4B, popped_stack_items=0, pushed_stack_items=1)
2229+
"""
2230+
SLOTNUM() = slotNumber
2231+
----
2232+
2233+
Description
2234+
----
2235+
Returns the current slot number as provided by the consensus layer.
2236+
The slot number is passed from the consensus layer to the execution
2237+
layer through the engine API.
2238+
2239+
Inputs
2240+
----
2241+
- None
2242+
2243+
Outputs
2244+
----
2245+
- slotNumber: current slot number (uint64)
2246+
2247+
Fork
2248+
----
2249+
TBD
2250+
2251+
Gas
2252+
----
2253+
2
2254+
2255+
Source: [EIP-7843](https://eips.ethereum.org/EIPS/eip-7843)
2256+
"""
2257+
22282258
POP = Opcode(0x50, popped_stack_items=1)
22292259
"""
22302260
POP()

src/ethereum/forks/amsterdam/blocks.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,13 @@ class Header:
253253
[EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
254254
[cbalh]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_utils.compute_block_access_list_hash
255255
""" # noqa: E501
256+
slot_number: U64
257+
"""
258+
The slot number of this block as provided by the consensus layer.
259+
Introduced in [EIP-7843].
260+
261+
[EIP-7843]: https://eips.ethereum.org/EIPS/eip-7843
262+
"""
256263

257264

258265
@slotted_freezable

0 commit comments

Comments
 (0)