Skip to content

Commit

Permalink
Merge pull request #223 from spencer-tb/tests-eip4788
Browse files Browse the repository at this point in the history
Initial Beacon Block Root Tests EIP-4788
  • Loading branch information
spencer-tb authored Aug 1, 2023
2 parents fa19dfa + 8726766 commit 4aa816d
Show file tree
Hide file tree
Showing 14 changed files with 617 additions and 7 deletions.
17 changes: 17 additions & 0 deletions src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ def header_blob_gas_used_required(cls, block_number: int = 0, timestamp: int = 0
"""
pass

@classmethod
@abstractmethod
def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool:
"""
Returns true if the header must contain parent beacon block root
"""
pass

@classmethod
@abstractmethod
def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int:
Expand Down Expand Up @@ -115,6 +123,15 @@ def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int =
"""
pass

@classmethod
@abstractmethod
def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool:
"""
Returns true if the engine api version requires new payload calls to include a parent
beacon block root.
"""
pass

# Meta information about the fork
@classmethod
def name(cls) -> str:
Expand Down
28 changes: 28 additions & 0 deletions src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,27 @@ def engine_new_payload_version(
"""
return None

@classmethod
def header_beacon_root_required(cls, block_number: int = 0, timestamp: int = 0) -> bool:
"""
At genesis, header must not contain parent beacon block root
"""
return False

@classmethod
def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int = 0) -> bool:
"""
At genesis, payloads do not have blob hashes.
"""
return False

@classmethod
def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool:
"""
At genesis, payloads do not have a parent beacon block root.
"""
return False

@classmethod
def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""
Expand Down Expand Up @@ -270,6 +284,13 @@ def header_blob_gas_used_required(cls, block_number: int = 0, timestamp: int = 0
"""
return True

@classmethod
def header_beacon_root_required(cls, block_number: int = 0, timestamp: int = 0) -> bool:
"""
Parent beacon block root is required starting from Cancun.
"""
return True

@classmethod
def engine_new_payload_version(
cls, block_number: int = 0, timestamp: int = 0
Expand All @@ -285,3 +306,10 @@ def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int =
Starting at Cancun, payloads must have blob hashes.
"""
return True

@classmethod
def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool:
"""
Starting at Cancun, payloads must have a parent beacon block root.
"""
return True
2 changes: 2 additions & 0 deletions src/ethereum_test_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
AccessList,
Account,
Auto,
BeaconRoot,
Block,
EngineAPIError,
Environment,
Expand Down Expand Up @@ -52,6 +53,7 @@
"Auto",
"BaseTest",
"BaseTestConfig",
"BeaconRoot",
"Block",
"BlockchainTest",
"BlockchainTestFiller",
Expand Down
2 changes: 2 additions & 0 deletions src/ethereum_test_tools/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .constants import (
AddrAA,
AddrBB,
BeaconRoot,
EmptyTrieRoot,
EngineAPIError,
HistoryStorageAddress,
Expand Down Expand Up @@ -61,6 +62,7 @@
"AddrBB",
"Alloc",
"Auto",
"BeaconRoot",
"Block",
"Bloom",
"Bytes",
Expand Down
1 change: 1 addition & 0 deletions src/ethereum_test_tools/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ZeroAddress = bytes([0] * 20)

HistoryStorageAddress = "0x000000000000000000000000000000000000000b"
BeaconRoot = bytes.fromhex("3e97e493f9123f7455a3be1b388db32876beea7d165a3b63528d8f9a38b7249f")


class EngineAPIError(IntEnum):
Expand Down
41 changes: 39 additions & 2 deletions src/ethereum_test_tools/common/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Useful types for generating Ethereum tests.
"""
from copy import copy, deepcopy
from dataclasses import dataclass, fields
from dataclasses import dataclass, fields, replace
from itertools import count
from typing import (
Any,
Expand Down Expand Up @@ -948,6 +948,13 @@ class Environment:
cast_type=Number,
),
)
beacon_root: Optional[FixedSizeBytesConvertible] = field(
default=None,
json_encoder=JSONEncoder.Field(
name="beaconRoot",
cast_type=Hash,
),
)
extra_data: Optional[BytesConvertible] = field(
default=None,
json_encoder=JSONEncoder.Field(
Expand Down Expand Up @@ -1036,6 +1043,9 @@ def set_fork_requirements(self, fork: Fork) -> "Environment":
):
res.blob_gas_used = 0

if fork.header_beacon_root_required(number, timestamp) and res.beacon_root is None:
res.beacon_root = 0

return res


Expand Down Expand Up @@ -1798,6 +1808,7 @@ class Header:
withdrawals_root: Optional[FixedSizeBytesConvertible | Removable] = None
blob_gas_used: Optional[NumberConvertible | Removable] = None
excess_blob_gas: Optional[NumberConvertible | Removable] = None
beacon_root: Optional[FixedSizeBytesConvertible | Removable] = None
hash: Optional[FixedSizeBytesConvertible] = None

REMOVE_FIELD: ClassVar[Removable] = Removable()
Expand Down Expand Up @@ -2075,6 +2086,16 @@ class FixtureHeader:
json_encoder=JSONEncoder.Field(name="excessBlobGas", cast_type=ZeroPaddedHexNumber),
)

beacon_root: Optional[Hash] = header_field(
default=None,
source=HeaderFieldSource(
parse_type=Hash,
fork_requirement_check="header_beacon_root_required",
source_environment="beacon_root",
),
json_encoder=JSONEncoder.Field(name="beaconRoot"),
)

hash: Optional[Hash] = header_field(
default=None,
source=HeaderFieldSource(
Expand Down Expand Up @@ -2172,6 +2193,8 @@ def build(
header.append(Uint(int(self.blob_gas_used)))
if self.excess_blob_gas is not None:
header.append(Uint(self.excess_blob_gas))
if self.beacon_root is not None:
header.append(self.beacon_root)

block = [
header,
Expand Down Expand Up @@ -2253,6 +2276,8 @@ def set_environment(self, env: Environment) -> Environment:
new_env.excess_blob_gas = self.excess_blob_gas
if not isinstance(self.blob_gas_used, Removable):
new_env.blob_gas_used = self.blob_gas_used
if not isinstance(self.beacon_root, Removable):
new_env.beacon_root = self.beacon_root
"""
These values are required, but they depend on the previous environment,
so they can be calculated here.
Expand Down Expand Up @@ -2422,6 +2447,13 @@ class FixtureEngineNewPayload:
version: int = field(
json_encoder=JSONEncoder.Field(),
)
beacon_root: Optional[FixedSizeBytesConvertible] = field(
default=None,
json_encoder=JSONEncoder.Field(
name="parentBeaconBlockRoot",
cast_type=Hash,
),
)
error_code: Optional[EngineAPIError] = field(
default=None,
json_encoder=JSONEncoder.Field(
Expand Down Expand Up @@ -2449,7 +2481,9 @@ def from_fixture_header(

new_payload = cls(
payload=FixtureExecutionPayload.from_fixture_header(
header=header, transactions=transactions, withdrawals=withdrawals
header=replace(header, beacon_root=None),
transactions=transactions,
withdrawals=withdrawals,
),
version=new_payload_version,
error_code=error_code,
Expand All @@ -2460,6 +2494,9 @@ def from_fixture_header(
transactions
)

if fork.engine_new_payload_beacon_root(header.number, header.timestamp):
new_payload.beacon_root = header.beacon_root

return new_payload


Expand Down
1 change: 1 addition & 0 deletions src/ethereum_test_tools/spec/blockchain_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def make_genesis(
if env.withdrawals is not None
else None
),
beacon_root=Hash.or_none(env.beacon_root),
)

genesis_rlp, genesis.hash = genesis.build(
Expand Down
10 changes: 9 additions & 1 deletion src/ethereum_test_tools/spec/state_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
State test filler.
"""
from copy import copy
from dataclasses import dataclass
from typing import Any, Callable, Dict, Generator, List, Mapping, Optional, Tuple, Type

Expand Down Expand Up @@ -57,7 +58,13 @@ def make_genesis(
"""
Create a genesis block from the state test definition.
"""
env = self.env.set_fork_requirements(fork)
env = copy(self.env)

# Remove fields that should not be present in the genesis block.
env.withdrawals = None
env.beacon_root = None

env = env.set_fork_requirements(fork)

genesis = FixtureHeader(
parent_hash=Hash(0),
Expand Down Expand Up @@ -93,6 +100,7 @@ def make_genesis(
if env.withdrawals is not None
else None
),
beacon_root=Hash.or_none(env.beacon_root),
)

genesis_rlp, genesis.hash = genesis.build(
Expand Down
9 changes: 5 additions & 4 deletions src/evm_transition_tool/tests/test_evaluate.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import json
import json # noqa: D100
import os
from pathlib import Path
from shutil import which
Expand Down Expand Up @@ -64,7 +64,7 @@
),
],
)
def test_calc_state_root(
def test_calc_state_root( # noqa: D103
t8n: TransitionTool,
fork: Fork,
alloc: Dict,
Expand All @@ -81,7 +81,7 @@ class TestEnv:

@pytest.mark.parametrize("evm_tool", [GethTransitionTool])
@pytest.mark.parametrize("binary_arg", ["no_binary_arg", "path_type", "str_type"])
def test_evm_tool_binary_arg(evm_tool, binary_arg):
def test_evm_tool_binary_arg(evm_tool, binary_arg): # noqa: D103
if binary_arg == "no_binary_arg":
evm_tool().version()
return
Expand All @@ -100,7 +100,7 @@ def test_evm_tool_binary_arg(evm_tool, binary_arg):

@pytest.mark.parametrize("t8n", [GethTransitionTool()])
@pytest.mark.parametrize("test_dir", os.listdir(path=FIXTURES_ROOT))
def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None:
def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None: # noqa: D103
alloc_path = Path(FIXTURES_ROOT, test_dir, "alloc.json")
txs_path = Path(FIXTURES_ROOT, test_dir, "txs.json")
env_path = Path(FIXTURES_ROOT, test_dir, "env.json")
Expand All @@ -125,5 +125,6 @@ def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None:
),
)
print(result)
print(expected.get("result"))
assert result_alloc == expected.get("alloc")
assert result == expected.get("result")
13 changes: 13 additions & 0 deletions src/evm_transition_tool/transition_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,14 @@ def calc_state_root(self, *, alloc: Any, fork: Fork, debug_output_path: str = ""
if fork.header_withdrawals_required(0, 0):
env["withdrawals"] = []

if fork.header_excess_blob_gas_required(0, 0):
env["currentExcessBlobGas"] = "0"

if fork.header_beacon_root_required(0, 0):
env[
"beaconRoot"
] = "0x0000000000000000000000000000000000000000000000000000000000000000"

_, result = self.evaluate(
alloc=alloc,
txs=[],
Expand Down Expand Up @@ -268,6 +276,11 @@ def calc_withdrawals_root(
if fork.header_excess_blob_gas_required(0, 0):
env["currentExcessBlobGas"] = "0"

if fork.header_beacon_root_required(0, 0):
env[
"beaconRoot"
] = "0x0000000000000000000000000000000000000000000000000000000000000000"

_, result = self.evaluate(
alloc={},
txs=[],
Expand Down
3 changes: 3 additions & 0 deletions tests/cancun/eip4788_beacon_root/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Cross-client EIP-4788 Tests
"""
Loading

0 comments on commit 4aa816d

Please sign in to comment.