Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ def client_genesis(fixture: BlockchainFixtureCommon) -> dict:
alloc = to_json(fixture.pre)
# NOTE: nethermind requires account keys without '0x' prefix
genesis["alloc"] = {k.replace("0x", ""): v for k, v in alloc.items()}
# NOTE: geth expects slotNumber as plain integer, not hex string
if "slotNumber" in genesis:
genesis["slotNumber"] = int(genesis["slotNumber"], 16)
return genesis


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ def _payload_attributes(
if next_fork.engine_payload_attribute_max_blobs_per_block()
else None
),
slot_number=(
0 if next_fork.engine_payload_attribute_slot_number() else None
),
)

def _finalize_payload(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ class FixtureHeader(CamelModel):
block_access_list_hash: (
Annotated[Hash, HeaderForkRequirement("bal_hash")] | None
) = Field(None, alias="blockAccessListHash")
slot_number: (
Annotated[ZeroPaddedHexNumber, HeaderForkRequirement("slot_number")]
| None
) = Field(None)

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

Expand Down Expand Up @@ -349,7 +353,7 @@ def get_default_from_annotation(
def genesis(cls, fork: Fork, env: Environment, state_root: Hash) -> Self:
"""Get the genesis header for the given fork."""
environment_values = env.model_dump(
exclude_none=True, exclude={"withdrawals"}
exclude_none=True, exclude={"withdrawals", "slot_number"}
)
if env.withdrawals is not None:
environment_values["withdrawals_root"] = Withdrawal.list_root(
Expand All @@ -366,6 +370,7 @@ def genesis(cls, fork: Fork, env: Environment, state_root: Hash) -> Self:
if fork.header_bal_hash_required()
else None
),
"slot_number": 0 if fork.header_slot_number_required() else None,
"fork": fork,
}
return cls(**environment_values, **extras)
Expand Down Expand Up @@ -406,6 +411,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(
Expand Down
14 changes: 14 additions & 0 deletions packages/testing/src/execution_testing/forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,12 @@ def empty_block_bal_item_count(cls) -> int:
"""
pass

@classmethod
@abstractmethod
def header_slot_number_required(cls) -> bool:
"""Return true if the header must contain slot number (EIP-7843)."""
pass

# Gas related abstract methods

@classmethod
Expand Down Expand Up @@ -896,6 +902,14 @@ def engine_payload_attribute_max_blobs_per_block(cls) -> bool:
"""
pass

@classmethod
@abstractmethod
def engine_payload_attribute_slot_number(cls) -> bool:
"""
Return true if the payload attributes include the slot number.
"""
pass

# Engine API method versions
@classmethod
def engine_new_payload_version(cls) -> Optional[int]:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
EIP-7843: SLOTNUM opcode.

Opcode to get the current slot number.

https://eips.ethereum.org/EIPS/eip-7843
"""

from typing import Callable, Dict, List

from execution_testing.vm import (
OpcodeBase,
Opcodes,
)

from ....base_fork import BaseFork


class EIP7843(
BaseFork,
# Engine API method version bumps
# New field `slotNumber` in ExecutionPayload
engine_new_payload_version_bump=True,
engine_get_payload_version_bump=True,
engine_forkchoice_updated_version_bump=True,
):
"""EIP-7843 class."""

@classmethod
def header_slot_number_required(cls) -> bool:
"""Slot number in header required."""
return True

@classmethod
def engine_payload_attribute_slot_number(cls) -> bool:
"""Payload attributes include the slot number."""
return True

@classmethod
def opcode_gas_map(
cls,
) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]:
"""Add SLOTNUM opcode gas cost."""
gas_costs = cls.gas_costs()
base_map = super(EIP7843, cls).opcode_gas_map()
return {
**base_map,
Opcodes.SLOTNUM: gas_costs.BASE,
}

@classmethod
def valid_opcodes(cls) -> List[Opcodes]:
"""Add SLOTNUM opcode."""
return [Opcodes.SLOTNUM] + super(EIP7843, cls).valid_opcodes()
12 changes: 12 additions & 0 deletions packages/testing/src/execution_testing/forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,11 @@ def header_beacon_root_required(cls) -> bool:
"""At genesis, header must not contain parent beacon block root."""
return False

@classmethod
def header_slot_number_required(cls) -> bool:
"""At genesis, header must not contain slot number (EIP-7843)."""
return False

@classmethod
def engine_new_payload_blob_hashes(cls) -> bool:
"""At genesis, payloads do not have blob hashes."""
Expand Down Expand Up @@ -964,6 +969,13 @@ def engine_payload_attribute_max_blobs_per_block(cls) -> bool:
"""
return False

@classmethod
def engine_payload_attribute_slot_number(cls) -> bool:
"""
At genesis, payload attributes do not include the slot number.
"""
return False

@classmethod
def get_reward(cls) -> int:
"""
Expand Down
1 change: 1 addition & 0 deletions packages/testing/src/execution_testing/rpc/rpc_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ class PayloadAttributes(CamelModel):
parent_beacon_block_root: Hash | None = None
target_blobs_per_block: HexNumber | None = None
max_blobs_per_block: HexNumber | None = None
slot_number: HexNumber | None = None


class BlobsBundle(CamelModel):
Expand Down
26 changes: 23 additions & 3 deletions packages/testing/src/execution_testing/specs/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
HeaderNonce,
HexNumber,
Number,
ZeroPaddedHexNumber,
)
from execution_testing.client_clis import (
BlockExceptionWithMessage,
Expand Down Expand Up @@ -165,6 +166,7 @@ class Header(CamelModel):
parent_beacon_block_root: Removable | Hash | None = None
requests_hash: Removable | Hash | None = None
block_access_list_hash: Removable | Hash | None = None
slot_number: Removable | HexNumber | None = None

REMOVE_FIELD: ClassVar[Removable] = Removable()
"""
Expand Down Expand Up @@ -349,6 +351,8 @@ def set_environment(self, env: Environment) -> Environment:
and self.block_access_list is not None
):
new_env_values["block_access_list"] = self.block_access_list
if not isinstance(self.slot_number, Removable):
new_env_values["slot_number"] = self.slot_number
"""
These values are required, but they depend on the previous environment,
so they can be calculated here.
Expand Down Expand Up @@ -670,22 +674,37 @@ def generate_block_data(
if (blob_gas_per_blob := fork.blob_gas_per_blob()) > 0:
blob_gas_used = blob_gas_per_blob * count_blobs(txs)

# Prepare slot_number for header initialization
slot_number_value: ZeroPaddedHexNumber | None = None
if fork.header_slot_number_required():
slot_number_value = ZeroPaddedHexNumber(
int(env.slot_number) if env.slot_number is not None else 0
)

header = FixtureHeader(
**(
transition_tool_output.result.model_dump(
exclude_none=True,
exclude={"blob_gas_used", "transactions_trie"},
)
| env.model_dump(exclude_none=True, exclude={"blob_gas_used"})
| env.model_dump(
exclude_none=True,
exclude={"blob_gas_used", "slot_number"},
)
),
blob_gas_used=blob_gas_used,
transactions_trie=Transaction.list_root(txs),
extra_data=block.extra_data
if block.extra_data is not None
else b"",
slot_number=slot_number_value,
fork=fork,
)

# Clear block_access_list_hash if the fork doesn't require it
if not fork.header_bal_hash_required():
header.block_access_list_hash = None

if block.header_verify is not None:
# Verify the header after transition tool processing.
try:
Expand Down Expand Up @@ -766,8 +785,9 @@ def generate_block_data(
bal = block.expected_block_access_list.modify_if_invalid_test(
t8n_bal
)
if bal != t8n_bal:
# If the BAL was modified, update the header hash
if bal != t8n_bal and fork.header_bal_hash_required():
# If the BAL was modified and the fork requires it, update the
# header hash
header.block_access_list_hash = Hash(bal.rlp.keccak256())

built_block = BuiltBlock(
Expand Down
1 change: 1 addition & 0 deletions packages/testing/src/execution_testing/specs/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ def _generate_blockchain_blocks(self) -> List[Block]:
"extra_data": self.env.extra_data,
"withdrawals": self.env.withdrawals,
"parent_beacon_block_root": self.env.parent_beacon_block_root,
"slot_number": self.env.slot_number,
"txs": [self.tx],
"ommers": [],
"header_verify": self.blockchain_test_header_verify,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class EnvironmentInStateTestFiller(BaseModel):
current_excess_blob_gas: ValueInFiller | None = Field(
None, alias="currentExcessBlobGas"
)
current_slot_number: ValueInFiller | None = Field(None, alias="slotNumber")

model_config = ConfigDict(extra="forbid")

Expand Down Expand Up @@ -72,4 +73,6 @@ def get_environment(self, tags: TagDict) -> Environment:
kwargs["base_fee_per_gas"] = self.current_base_fee
if self.current_excess_blob_gas is not None:
kwargs["excess_blob_gas"] = self.current_excess_blob_gas
if self.current_slot_number is not None:
kwargs["slot_number"] = self.current_slot_number
return Environment(**kwargs)
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class EnvironmentGeneric(CamelModel, Generic[NumberBoundTypeVar]):
excess_blob_gas: NumberBoundTypeVar | None = Field(
None, alias="currentExcessBlobGas"
)
slot_number: NumberBoundTypeVar | None = Field(None, alias="slotNumber")

parent_difficulty: NumberBoundTypeVar | None = Field(None)
parent_timestamp: NumberBoundTypeVar | None = Field(None)
Expand Down Expand Up @@ -200,6 +201,9 @@ def set_fork_requirements(self, fork: Fork) -> "Environment":
):
updated_values["parent_beacon_block_root"] = 0

if fork.header_slot_number_required() and self.slot_number is None:
updated_values["slot_number"] = 0

return self.copy(**updated_values)

def __hash__(self) -> int:
Expand Down
30 changes: 30 additions & 0 deletions packages/testing/src/execution_testing/vm/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2225,6 +2225,36 @@ class Opcodes(Opcode, Enum):
Source: [EIP-7516](https://eips.ethereum.org/EIPS/eip-7516)
"""

SLOTNUM = Opcode(0x4B, popped_stack_items=0, pushed_stack_items=1)
"""
SLOTNUM() = slotNumber
----

Description
----
Returns the current slot number as provided by the consensus layer.
The slot number is passed from the consensus layer to the execution
layer through the engine API.

Inputs
----
- None

Outputs
----
- slotNumber: current slot number (uint64)

Fork
----
Amsterdam

Gas
----
2

Source: [EIP-7843](https://eips.ethereum.org/EIPS/eip-7843)
"""

POP = Opcode(0x50, popped_stack_items=1)
"""
POP()
Expand Down
5 changes: 4 additions & 1 deletion src/ethereum/forks/amsterdam/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""
The Amsterdam fork ([EIP-7773]) includes block-level access lists.
The Amsterdam fork ([EIP-7773]) includes block-level access lists and the
partial header hash that the consensus layer (Gloas) cross-checks against.

### Changes

- [EIP-7928: Block-Level Access Lists][EIP-7928]
- [EIP-8237: Partial Header Hash][EIP-8237]

### Releases

[EIP-7773]: https://eips.ethereum.org/EIPS/eip-7773
[EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
[EIP-8237]: https://eips.ethereum.org/EIPS/eip-8237
"""

from ethereum.fork_criteria import ForkCriteria, Unscheduled
Expand Down
18 changes: 18 additions & 0 deletions src/ethereum/forks/amsterdam/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,24 @@ class Header:
[EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
[cbalh]: ref:ethereum.forks.amsterdam.block_access_lists.hash_block_access_list
""" # noqa: E501
slot_number: U64
"""
The slot number of this block as provided by the consensus layer.
Introduced in [EIP-7843].

[EIP-7843]: https://eips.ethereum.org/EIPS/eip-7843
"""

partial_header_hash: Hash32
"""
[SHA2-256] commitment cross-checked against the consensus layer.
Last RLP field of the header so the block hash covers it. Introduced
in [EIP-8237]. See [`compute_partial_header_hash`][cphh].

[SHA2-256]: https://en.wikipedia.org/wiki/SHA-2
[EIP-8237]: https://eips.ethereum.org/EIPS/eip-8237
[cphh]: ref:ethereum.forks.amsterdam.partial_header_hash.compute_partial_header_hash
""" # noqa: E501


@slotted_freezable
Expand Down
Loading
Loading