Skip to content
Merged
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 @@ -436,6 +436,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 @@ -881,6 +887,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 @@ -925,6 +925,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 @@ -965,6 +970,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: 21 additions & 5 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 @@ -351,6 +353,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 @@ -672,19 +676,30 @@ 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"",
extra_data=(
block.extra_data if block.extra_data is not None else b""
),
slot_number=slot_number_value,
fork=fork,
)

Expand Down Expand Up @@ -777,7 +792,8 @@ def generate_block_data(
t8n_bal
)
if bal != t8n_bal:
# If the BAL was modified, update the header hash
# 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
10 changes: 9 additions & 1 deletion src/ethereum/forks/amsterdam/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class Header:
[`keccak256`]: ref:ethereum.crypto.hash.keccak256
[changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes
[Trie]: ref:ethereum.merkle_patricia_trie.Trie
""" # noqa: E501
"""

transactions_root: Root
"""
Expand Down Expand Up @@ -258,6 +258,14 @@ class Header:
[cbalh]: ref:ethereum.forks.amsterdam.block_access_lists.hash_block_access_list
""" # noqa: E501

slot_number: U64
Comment thread
marioevz marked this conversation as resolved.
"""
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
"""


@slotted_freezable
@dataclass
Expand Down
1 change: 1 addition & 0 deletions src/ethereum/forks/amsterdam/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ def execute_block(
excess_blob_gas=block.header.excess_blob_gas,
parent_beacon_block_root=block.header.parent_beacon_block_root,
block_access_list_builder=BlockAccessListBuilder(),
slot_number=block.header.slot_number,
)

block_output = apply_body(
Expand Down
1 change: 1 addition & 0 deletions src/ethereum/forks/amsterdam/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class BlockEnvironment:
excess_blob_gas: U64
parent_beacon_block_root: Hash32
block_access_list_builder: BlockAccessListBuilder
slot_number: U64


@dataclass
Expand Down
1 change: 1 addition & 0 deletions src/ethereum/forks/amsterdam/vm/gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class GasCosts:
OPCODE_CHAINID = BASE
OPCODE_BASEFEE = BASE
OPCODE_BLOBBASEFEE = BASE
OPCODE_SLOTNUM = BASE
OPCODE_BLOBHASH = Uint(3)
OPCODE_PUSH = VERY_LOW
OPCODE_PUSH0 = BASE
Expand Down
2 changes: 2 additions & 0 deletions src/ethereum/forks/amsterdam/vm/instructions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class Ops(enum.Enum):
BASEFEE = 0x48
BLOBHASH = 0x49
BLOBBASEFEE = 0x4A
SLOTNUM = 0x4B

# Control Flow Ops
STOP = 0x00
Expand Down Expand Up @@ -251,6 +252,7 @@ class Ops(enum.Enum):
Ops.PREVRANDAO: block_instructions.prev_randao,
Ops.GASLIMIT: block_instructions.gas_limit,
Ops.CHAINID: block_instructions.chain_id,
Ops.SLOTNUM: block_instructions.slot_number,
Ops.MLOAD: memory_instructions.mload,
Ops.MSTORE: memory_instructions.mstore,
Ops.MSTORE8: memory_instructions.mstore8,
Expand Down
Loading
Loading