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 @@ -33,6 +33,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
12 changes: 11 additions & 1 deletion packages/testing/src/execution_testing/fixtures/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,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 @@ -361,7 +365,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 @@ -378,6 +382,11 @@ 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": (
0
if fork.header_slot_number_required(block_number=0, timestamp=0)
else None
),
"fork": fork,
}
return cls(**environment_values, **extras)
Expand Down Expand Up @@ -418,6 +427,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
8 changes: 8 additions & 0 deletions packages/testing/src/execution_testing/forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 42 additions & 3 deletions packages/testing/src/execution_testing/forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,14 @@ def header_beacon_root_required(
del block_number, timestamp
return False

@classmethod
def header_slot_number_required(
cls, *, block_number: int = 0, timestamp: int = 0
) -> bool:
"""At genesis, header must not contain slot number (EIP-7843)."""
del block_number, timestamp
return False

@classmethod
def engine_new_payload_blob_hashes(
cls, *, block_number: int = 0, timestamp: int = 0
Expand Down Expand Up @@ -3338,9 +3346,7 @@ class Amsterdam(BPO2):
def header_bal_hash_required(
cls, *, block_number: int = 0, timestamp: int = 0
) -> bool:
"""
From Amsterdam, header must contain block access list hash (EIP-7928).
"""
"""BAL hash in header required from Amsterdam (EIP-7928)."""
del block_number, timestamp
return True

Expand All @@ -3367,3 +3373,36 @@ def engine_execution_payload_block_access_list(
"""
del block_number, timestamp
return True

@classmethod
def header_slot_number_required(
cls, *, block_number: int = 0, timestamp: int = 0
) -> bool:
"""Slot number in header required from Amsterdam (EIP-7843)."""
del block_number, timestamp
return True

@classmethod
def opcode_gas_map(
cls, *, block_number: int = 0, timestamp: int = 0
) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]:
"""Add SLOTNUM opcode gas cost for Amsterdam (EIP-7843)."""
gas_costs = cls.gas_costs(
block_number=block_number, timestamp=timestamp
)
base_map = super(Amsterdam, cls).opcode_gas_map(
block_number=block_number, timestamp=timestamp
)
return {
**base_map,
Opcodes.SLOTNUM: gas_costs.G_BASE,
}

@classmethod
def valid_opcodes(
cls, *, block_number: int = 0, timestamp: int = 0
) -> List[Opcodes]:
"""Add SLOTNUM opcode for Amsterdam (EIP-7843)."""
return [Opcodes.SLOTNUM] + super(Amsterdam, cls).valid_opcodes(
block_number=block_number, timestamp=timestamp
)
15 changes: 13 additions & 2 deletions packages/testing/src/execution_testing/specs/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class Header(CamelModel):
parent_beacon_block_root: Removable | Hash | None = None
requests_hash: Removable | Hash | None = None
bal_hash: Removable | Hash | None = None
slot_number: Removable | HexNumber | None = None

REMOVE_FIELD: ClassVar[Removable] = Removable()
"""
Expand Down Expand Up @@ -325,6 +326,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 @@ -651,6 +654,12 @@ def generate_block_data(
fork=self.fork,
)

# Clear block_access_list_hash if the fork doesn't require it
if not self.fork.header_bal_hash_required(
block_number=int(env.number), timestamp=int(env.timestamp)
):
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 @@ -747,8 +756,10 @@ 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 self.fork.header_bal_hash_required(
block_number=int(env.number), timestamp=int(env.timestamp)
):
# 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 @@ -310,6 +310,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 @@ -223,6 +224,14 @@ def set_fork_requirements(self, fork: Fork) -> "Environment":
):
updated_values["parent_beacon_block_root"] = 0

if (
fork.header_slot_number_required(
block_number=number, timestamp=timestamp
)
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
----
TBD

Gas
----
2

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

POP = Opcode(0x50, popped_stack_items=1)
"""
POP()
Expand Down
7 changes: 7 additions & 0 deletions src/ethereum/forks/amsterdam/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,13 @@ class Header:
[EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
[cbalh]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_utils.compute_block_access_list_hash
""" # 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
"""


@slotted_freezable
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 @@ -249,6 +249,7 @@ def state_transition(chain: BlockChain, block: Block) -> None:
excess_blob_gas=block.header.excess_blob_gas,
parent_beacon_block_root=block.header.parent_beacon_block_root,
state_changes=StateChanges(),
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 @@ -50,6 +50,7 @@ class BlockEnvironment:
excess_blob_gas: U64
parent_beacon_block_root: Hash32
state_changes: StateChanges
slot_number: U64


@dataclass
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
33 changes: 33 additions & 0 deletions src/ethereum/forks/amsterdam/vm/instructions/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,36 @@ def chain_id(evm: Evm) -> None:

# PROGRAM COUNTER
evm.pc += Uint(1)


def slot_number(evm: Evm) -> None:
"""
Push the current slot number onto the stack.

The slot number is provided by the consensus layer and passed to the
execution layer through the engine API.

Parameters
----------
evm :
The current EVM frame.

Raises
------
:py:class:`~ethereum.forks.amsterdam.vm.exceptions.StackOverflowError`
If `len(stack)` is equal to `1024`.
:py:class:`~ethereum.forks.amsterdam.vm.exceptions.OutOfGasError`
If `evm.gas_left` is less than `2`.

"""
# STACK
pass

# GAS
charge_gas(evm, GAS_BASE)

# OPERATION
push(evm.stack, U256(evm.message.block_env.slot_number))

# PROGRAM COUNTER
evm.pc += Uint(1)
9 changes: 9 additions & 0 deletions src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ def has_withdrawal(self) -> bool:
"""Check if the fork has a `Withdrawal` class."""
return hasattr(self._module("blocks"), "Withdrawal")

@property
def has_slot_number(self) -> bool:
"""Check if the fork supports the SLOTNUM opcode (EIP-7843)."""
try:
block_env = self._module("vm").BlockEnvironment
return "slot_number" in block_env.__dataclass_fields__
except (ModuleNotFoundError, AttributeError):
return False

@property
def decode_transaction(self) -> Any:
"""decode_transaction function of the fork."""
Expand Down
2 changes: 2 additions & 0 deletions src/ethereum_spec_tools/evm_tools/t8n/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ def block_environment(self) -> Any:

if self.fork.has_block_access_list_hash:
kw_arguments["state_changes"] = StateChanges()
if self.fork.has_slot_number:
kw_arguments["slot_number"] = self.env.slot_number

return block_environment(**kw_arguments)

Expand Down
Loading
Loading