Skip to content

Commit 4aa816d

Browse files
authored
Merge pull request #223 from spencer-tb/tests-eip4788
Initial Beacon Block Root Tests EIP-4788
2 parents fa19dfa + 8726766 commit 4aa816d

File tree

14 files changed

+617
-7
lines changed

14 files changed

+617
-7
lines changed

src/ethereum_test_forks/base_fork.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ def header_blob_gas_used_required(cls, block_number: int = 0, timestamp: int = 0
8787
"""
8888
pass
8989

90+
@classmethod
91+
@abstractmethod
92+
def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool:
93+
"""
94+
Returns true if the header must contain parent beacon block root
95+
"""
96+
pass
97+
9098
@classmethod
9199
@abstractmethod
92100
def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int:
@@ -115,6 +123,15 @@ def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int =
115123
"""
116124
pass
117125

126+
@classmethod
127+
@abstractmethod
128+
def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool:
129+
"""
130+
Returns true if the engine api version requires new payload calls to include a parent
131+
beacon block root.
132+
"""
133+
pass
134+
118135
# Meta information about the fork
119136
@classmethod
120137
def name(cls) -> str:

src/ethereum_test_forks/forks/forks.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,27 @@ def engine_new_payload_version(
7070
"""
7171
return None
7272

73+
@classmethod
74+
def header_beacon_root_required(cls, block_number: int = 0, timestamp: int = 0) -> bool:
75+
"""
76+
At genesis, header must not contain parent beacon block root
77+
"""
78+
return False
79+
7380
@classmethod
7481
def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int = 0) -> bool:
7582
"""
7683
At genesis, payloads do not have blob hashes.
7784
"""
7885
return False
7986

87+
@classmethod
88+
def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool:
89+
"""
90+
At genesis, payloads do not have a parent beacon block root.
91+
"""
92+
return False
93+
8094
@classmethod
8195
def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int:
8296
"""
@@ -270,6 +284,13 @@ def header_blob_gas_used_required(cls, block_number: int = 0, timestamp: int = 0
270284
"""
271285
return True
272286

287+
@classmethod
288+
def header_beacon_root_required(cls, block_number: int = 0, timestamp: int = 0) -> bool:
289+
"""
290+
Parent beacon block root is required starting from Cancun.
291+
"""
292+
return True
293+
273294
@classmethod
274295
def engine_new_payload_version(
275296
cls, block_number: int = 0, timestamp: int = 0
@@ -285,3 +306,10 @@ def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int =
285306
Starting at Cancun, payloads must have blob hashes.
286307
"""
287308
return True
309+
310+
@classmethod
311+
def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool:
312+
"""
313+
Starting at Cancun, payloads must have a parent beacon block root.
314+
"""
315+
return True

src/ethereum_test_tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
AccessList,
99
Account,
1010
Auto,
11+
BeaconRoot,
1112
Block,
1213
EngineAPIError,
1314
Environment,
@@ -52,6 +53,7 @@
5253
"Auto",
5354
"BaseTest",
5455
"BaseTestConfig",
56+
"BeaconRoot",
5557
"Block",
5658
"BlockchainTest",
5759
"BlockchainTestFiller",

src/ethereum_test_tools/common/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .constants import (
55
AddrAA,
66
AddrBB,
7+
BeaconRoot,
78
EmptyTrieRoot,
89
EngineAPIError,
910
HistoryStorageAddress,
@@ -61,6 +62,7 @@
6162
"AddrBB",
6263
"Alloc",
6364
"Auto",
65+
"BeaconRoot",
6466
"Block",
6567
"Bloom",
6668
"Bytes",

src/ethereum_test_tools/common/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
ZeroAddress = bytes([0] * 20)
2222

2323
HistoryStorageAddress = "0x000000000000000000000000000000000000000b"
24+
BeaconRoot = bytes.fromhex("3e97e493f9123f7455a3be1b388db32876beea7d165a3b63528d8f9a38b7249f")
2425

2526

2627
class EngineAPIError(IntEnum):

src/ethereum_test_tools/common/types.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Useful types for generating Ethereum tests.
33
"""
44
from copy import copy, deepcopy
5-
from dataclasses import dataclass, fields
5+
from dataclasses import dataclass, fields, replace
66
from itertools import count
77
from typing import (
88
Any,
@@ -948,6 +948,13 @@ class Environment:
948948
cast_type=Number,
949949
),
950950
)
951+
beacon_root: Optional[FixedSizeBytesConvertible] = field(
952+
default=None,
953+
json_encoder=JSONEncoder.Field(
954+
name="beaconRoot",
955+
cast_type=Hash,
956+
),
957+
)
951958
extra_data: Optional[BytesConvertible] = field(
952959
default=None,
953960
json_encoder=JSONEncoder.Field(
@@ -1036,6 +1043,9 @@ def set_fork_requirements(self, fork: Fork) -> "Environment":
10361043
):
10371044
res.blob_gas_used = 0
10381045

1046+
if fork.header_beacon_root_required(number, timestamp) and res.beacon_root is None:
1047+
res.beacon_root = 0
1048+
10391049
return res
10401050

10411051

@@ -1798,6 +1808,7 @@ class Header:
17981808
withdrawals_root: Optional[FixedSizeBytesConvertible | Removable] = None
17991809
blob_gas_used: Optional[NumberConvertible | Removable] = None
18001810
excess_blob_gas: Optional[NumberConvertible | Removable] = None
1811+
beacon_root: Optional[FixedSizeBytesConvertible | Removable] = None
18011812
hash: Optional[FixedSizeBytesConvertible] = None
18021813

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

2089+
beacon_root: Optional[Hash] = header_field(
2090+
default=None,
2091+
source=HeaderFieldSource(
2092+
parse_type=Hash,
2093+
fork_requirement_check="header_beacon_root_required",
2094+
source_environment="beacon_root",
2095+
),
2096+
json_encoder=JSONEncoder.Field(name="beaconRoot"),
2097+
)
2098+
20782099
hash: Optional[Hash] = header_field(
20792100
default=None,
20802101
source=HeaderFieldSource(
@@ -2172,6 +2193,8 @@ def build(
21722193
header.append(Uint(int(self.blob_gas_used)))
21732194
if self.excess_blob_gas is not None:
21742195
header.append(Uint(self.excess_blob_gas))
2196+
if self.beacon_root is not None:
2197+
header.append(self.beacon_root)
21752198

21762199
block = [
21772200
header,
@@ -2253,6 +2276,8 @@ def set_environment(self, env: Environment) -> Environment:
22532276
new_env.excess_blob_gas = self.excess_blob_gas
22542277
if not isinstance(self.blob_gas_used, Removable):
22552278
new_env.blob_gas_used = self.blob_gas_used
2279+
if not isinstance(self.beacon_root, Removable):
2280+
new_env.beacon_root = self.beacon_root
22562281
"""
22572282
These values are required, but they depend on the previous environment,
22582283
so they can be calculated here.
@@ -2422,6 +2447,13 @@ class FixtureEngineNewPayload:
24222447
version: int = field(
24232448
json_encoder=JSONEncoder.Field(),
24242449
)
2450+
beacon_root: Optional[FixedSizeBytesConvertible] = field(
2451+
default=None,
2452+
json_encoder=JSONEncoder.Field(
2453+
name="parentBeaconBlockRoot",
2454+
cast_type=Hash,
2455+
),
2456+
)
24252457
error_code: Optional[EngineAPIError] = field(
24262458
default=None,
24272459
json_encoder=JSONEncoder.Field(
@@ -2449,7 +2481,9 @@ def from_fixture_header(
24492481

24502482
new_payload = cls(
24512483
payload=FixtureExecutionPayload.from_fixture_header(
2452-
header=header, transactions=transactions, withdrawals=withdrawals
2484+
header=replace(header, beacon_root=None),
2485+
transactions=transactions,
2486+
withdrawals=withdrawals,
24532487
),
24542488
version=new_payload_version,
24552489
error_code=error_code,
@@ -2460,6 +2494,9 @@ def from_fixture_header(
24602494
transactions
24612495
)
24622496

2497+
if fork.engine_new_payload_beacon_root(header.number, header.timestamp):
2498+
new_payload.beacon_root = header.beacon_root
2499+
24632500
return new_payload
24642501

24652502

src/ethereum_test_tools/spec/blockchain_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def make_genesis(
9494
if env.withdrawals is not None
9595
else None
9696
),
97+
beacon_root=Hash.or_none(env.beacon_root),
9798
)
9899

99100
genesis_rlp, genesis.hash = genesis.build(

src/ethereum_test_tools/spec/state_test.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
State test filler.
33
"""
4+
from copy import copy
45
from dataclasses import dataclass
56
from typing import Any, Callable, Dict, Generator, List, Mapping, Optional, Tuple, Type
67

@@ -57,7 +58,13 @@ def make_genesis(
5758
"""
5859
Create a genesis block from the state test definition.
5960
"""
60-
env = self.env.set_fork_requirements(fork)
61+
env = copy(self.env)
62+
63+
# Remove fields that should not be present in the genesis block.
64+
env.withdrawals = None
65+
env.beacon_root = None
66+
67+
env = env.set_fork_requirements(fork)
6168

6269
genesis = FixtureHeader(
6370
parent_hash=Hash(0),
@@ -93,6 +100,7 @@ def make_genesis(
93100
if env.withdrawals is not None
94101
else None
95102
),
103+
beacon_root=Hash.or_none(env.beacon_root),
96104
)
97105

98106
genesis_rlp, genesis.hash = genesis.build(

src/evm_transition_tool/tests/test_evaluate.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import json
1+
import json # noqa: D100
22
import os
33
from pathlib import Path
44
from shutil import which
@@ -64,7 +64,7 @@
6464
),
6565
],
6666
)
67-
def test_calc_state_root(
67+
def test_calc_state_root( # noqa: D103
6868
t8n: TransitionTool,
6969
fork: Fork,
7070
alloc: Dict,
@@ -81,7 +81,7 @@ class TestEnv:
8181

8282
@pytest.mark.parametrize("evm_tool", [GethTransitionTool])
8383
@pytest.mark.parametrize("binary_arg", ["no_binary_arg", "path_type", "str_type"])
84-
def test_evm_tool_binary_arg(evm_tool, binary_arg):
84+
def test_evm_tool_binary_arg(evm_tool, binary_arg): # noqa: D103
8585
if binary_arg == "no_binary_arg":
8686
evm_tool().version()
8787
return
@@ -100,7 +100,7 @@ def test_evm_tool_binary_arg(evm_tool, binary_arg):
100100

101101
@pytest.mark.parametrize("t8n", [GethTransitionTool()])
102102
@pytest.mark.parametrize("test_dir", os.listdir(path=FIXTURES_ROOT))
103-
def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None:
103+
def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None: # noqa: D103
104104
alloc_path = Path(FIXTURES_ROOT, test_dir, "alloc.json")
105105
txs_path = Path(FIXTURES_ROOT, test_dir, "txs.json")
106106
env_path = Path(FIXTURES_ROOT, test_dir, "env.json")
@@ -125,5 +125,6 @@ def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None:
125125
),
126126
)
127127
print(result)
128+
print(expected.get("result"))
128129
assert result_alloc == expected.get("alloc")
129130
assert result == expected.get("result")

src/evm_transition_tool/transition_tool.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,14 @@ def calc_state_root(self, *, alloc: Any, fork: Fork, debug_output_path: str = ""
226226
if fork.header_withdrawals_required(0, 0):
227227
env["withdrawals"] = []
228228

229+
if fork.header_excess_blob_gas_required(0, 0):
230+
env["currentExcessBlobGas"] = "0"
231+
232+
if fork.header_beacon_root_required(0, 0):
233+
env[
234+
"beaconRoot"
235+
] = "0x0000000000000000000000000000000000000000000000000000000000000000"
236+
229237
_, result = self.evaluate(
230238
alloc=alloc,
231239
txs=[],
@@ -268,6 +276,11 @@ def calc_withdrawals_root(
268276
if fork.header_excess_blob_gas_required(0, 0):
269277
env["currentExcessBlobGas"] = "0"
270278

279+
if fork.header_beacon_root_required(0, 0):
280+
env[
281+
"beaconRoot"
282+
] = "0x0000000000000000000000000000000000000000000000000000000000000000"
283+
271284
_, result = self.evaluate(
272285
alloc={},
273286
txs=[],
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Cross-client EIP-4788 Tests
3+
"""

0 commit comments

Comments
 (0)