Skip to content

Commit 627ddb7

Browse files
committed
eip7732 fork choice tests (part1)
1 parent cecfd12 commit 627ddb7

File tree

4 files changed

+222
-2
lines changed

4 files changed

+222
-2
lines changed

presets/minimal/eip7732.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
# Execution
44
# ---------------------------------------------------------------
5-
# 2**1(= 2)
6-
PTC_SIZE: 2
5+
# 2**3(= 8)
6+
PTC_SIZE: 8
77
# 2**2 (= 4)
88
MAX_PAYLOAD_ATTESTATIONS: 4
99
# floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK) (= 9 + 1 + 5 = 15)

tests/core/pyspec/eth2spec/test/eip7732/fork_choice/__init__.py

Whitespace-only changes.
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
from eth2spec.test.context import (
2+
spec_state_test,
3+
with_eip7732_and_later,
4+
)
5+
from eth2spec.test.helpers.block import (
6+
build_empty_block_for_next_slot,
7+
)
8+
from eth2spec.test.helpers.fork_choice import (
9+
check_head_against_root,
10+
get_anchor_root,
11+
get_genesis_forkchoice_store_and_block,
12+
on_tick_and_append_step,
13+
output_head_check,
14+
tick_and_add_block,
15+
)
16+
from eth2spec.test.helpers.state import (
17+
state_transition_and_sign_block,
18+
)
19+
20+
21+
def build_and_yield_execution_payload_envelope(spec, beacon_block):
22+
"""
23+
Build a SignedExecutionPayloadEnvelope for the given beacon block and yield it for testing.
24+
This simulates the builder revealing the execution payload after the beacon block.
25+
"""
26+
# Get the execution payload header from the beacon block
27+
payload_header = beacon_block.body.signed_execution_payload_header.message
28+
29+
# Build execution payload from the header
30+
execution_payload = spec.ExecutionPayload(
31+
parent_hash=payload_header.parent_block_hash,
32+
fee_recipient=payload_header.fee_recipient,
33+
state_root=payload_header.state_root,
34+
receipts_root=payload_header.receipts_root,
35+
logs_bloom=payload_header.logs_bloom,
36+
prev_randao=payload_header.prev_randao,
37+
block_number=payload_header.block_number,
38+
gas_limit=payload_header.gas_limit,
39+
gas_used=payload_header.gas_used,
40+
timestamp=payload_header.timestamp,
41+
extra_data=payload_header.extra_data,
42+
base_fee_per_gas=payload_header.base_fee_per_gas,
43+
block_hash=payload_header.block_hash,
44+
transactions=[], # Empty for testing
45+
withdrawals=payload_header.withdrawals,
46+
blob_gas_used=payload_header.blob_gas_used,
47+
excess_blob_gas=payload_header.excess_blob_gas,
48+
)
49+
50+
# Create execution payload envelope
51+
envelope = spec.ExecutionPayloadEnvelope(
52+
beacon_block_root=spec.hash_tree_root(beacon_block),
53+
payload=execution_payload,
54+
builder_index=spec.ValidatorIndex(0), # Use validator 0 as builder for testing
55+
blob_kzg_commitments=[], # Empty for basic test
56+
payload_withheld=False,
57+
)
58+
59+
# Sign the envelope (empty signature for testing)
60+
signed_envelope = spec.SignedExecutionPayloadEnvelope(
61+
message=envelope,
62+
signature=spec.BLSSignature(),
63+
)
64+
65+
return signed_envelope
66+
67+
68+
def process_execution_payload_reveal(spec, store, signed_block, test_steps):
69+
"""
70+
Process execution payload reveal for a beacon block in EIP7732.
71+
This simulates the full flow: beacon block -> execution payload reveal -> store update.
72+
"""
73+
# Build and yield the execution payload envelope
74+
signed_envelope = build_and_yield_execution_payload_envelope(spec, signed_block.message)
75+
76+
# Yield the execution payload envelope for test file generation
77+
envelope_name = f"execution_payload_envelope_{spec.hash_tree_root(signed_block.message).hex()[:8]}"
78+
yield envelope_name, signed_envelope
79+
80+
# Process the execution payload envelope through the store
81+
spec.on_execution_payload(store, signed_envelope)
82+
83+
# Add test step for execution payload processing
84+
test_steps.append({
85+
"execution_payload": envelope_name,
86+
"valid": True,
87+
})
88+
89+
return signed_envelope
90+
91+
92+
@with_eip7732_and_later
93+
@spec_state_test
94+
def test_genesis(spec, state):
95+
"""Test genesis initialization with EIP7732 fork choice modifications"""
96+
test_steps = []
97+
# Initialization
98+
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
99+
yield "anchor_state", state
100+
yield "anchor_block", anchor_block
101+
102+
anchor_root = get_anchor_root(spec, state)
103+
check_head_against_root(spec, store, anchor_root)
104+
105+
# EIP7732-specific assertions
106+
assert hasattr(store, "execution_payload_states"), (
107+
"Store should have execution_payload_states field"
108+
)
109+
assert hasattr(store, "ptc_vote"), "Store should have ptc_vote field"
110+
assert anchor_root in store.execution_payload_states, (
111+
"Anchor block should be in execution_payload_states"
112+
)
113+
assert anchor_root in store.ptc_vote, "Anchor block should have ptc_vote entry"
114+
115+
# Check PTC vote initialization
116+
ptc_vote = store.ptc_vote[anchor_root]
117+
assert len(ptc_vote) == spec.PTC_SIZE, f"PTC vote should have {spec.PTC_SIZE} entries"
118+
assert all(vote == False for vote in ptc_vote), "All PTC votes should be False initially"
119+
120+
# Verify get_head returns ForkChoiceNode
121+
head = spec.get_head(store)
122+
assert isinstance(head, spec.ForkChoiceNode), "get_head should return ForkChoiceNode in EIP7732"
123+
124+
output_head_check(spec, store, test_steps)
125+
126+
yield "steps", test_steps
127+
128+
129+
@with_eip7732_and_later
130+
@spec_state_test
131+
def test_basic(spec, state):
132+
"""Basic EIP7732 fork choice test - similar to phase0 test_basic but with payload status tracking"""
133+
test_steps = []
134+
135+
# Add EIP7732-specific metadata
136+
yield "test_scenario", "meta", "basic_fork_choice"
137+
yield "tests_payload_status", "meta", True
138+
yield "tests_execution_payload_states", "meta", True
139+
140+
# Initialization
141+
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
142+
yield "anchor_state", state
143+
yield "anchor_block", anchor_block
144+
145+
# Set initial time and record tick
146+
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
147+
on_tick_and_append_step(spec, store, current_time, test_steps)
148+
assert store.time == current_time
149+
150+
# Verify initial EIP7732 state
151+
anchor_root = get_anchor_root(spec, state)
152+
check_head_against_root(spec, store, anchor_root)
153+
154+
# Check initial head has PENDING payload status
155+
head = spec.get_head(store)
156+
assert head.payload_status == spec.PAYLOAD_STATUS_PENDING, "Initial head should have PENDING status"
157+
158+
# On receiving a block of `GENESIS_SLOT + 1` slot
159+
block = build_empty_block_for_next_slot(spec, state)
160+
signed_block = state_transition_and_sign_block(spec, state, block)
161+
yield from tick_and_add_block(spec, store, signed_block, test_steps)
162+
163+
# Verify block was added to both stores
164+
block_root = signed_block.message.hash_tree_root()
165+
assert block_root in store.blocks, "Block should be in store.blocks"
166+
assert block_root in store.block_states, "Block should have block state"
167+
assert block_root in store.ptc_vote, "Block should have PTC vote entry"
168+
169+
# Head should now be the new block with PENDING status
170+
check_head_against_root(spec, store, block_root)
171+
head = spec.get_head(store)
172+
assert head.payload_status == spec.PAYLOAD_STATUS_PENDING, "New head should have PENDING status"
173+
174+
# Process execution payload reveal (simulates builder revealing payload)
175+
yield from process_execution_payload_reveal(spec, store, signed_block, test_steps)
176+
177+
# Verify block now has execution payload state
178+
assert block_root in store.execution_payload_states, "Block should now have execution payload state"
179+
180+
# On receiving a block of next slot
181+
store.time = current_time + spec.config.SECONDS_PER_SLOT * 2
182+
block_2 = build_empty_block_for_next_slot(spec, state)
183+
signed_block_2 = state_transition_and_sign_block(spec, state, block_2)
184+
yield from tick_and_add_block(spec, store, signed_block_2, test_steps)
185+
186+
# Process execution payload reveal for second block
187+
block_2_root = signed_block_2.message.hash_tree_root()
188+
check_head_against_root(spec, store, block_2_root)
189+
yield from process_execution_payload_reveal(spec, store, signed_block_2, test_steps)
190+
191+
# Add EIP7732-specific checks to test steps
192+
test_steps.append({
193+
"checks": {
194+
"execution_payload_states_count": len(store.execution_payload_states),
195+
"blocks_with_ptc_votes": len(store.ptc_vote),
196+
"head_payload_status": int(spec.get_head(store).payload_status),
197+
}
198+
})
199+
200+
yield "steps", test_steps

tests/core/pyspec/eth2spec/test/helpers/fork_choice.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,3 +552,23 @@ def get_pow_block_file_name(pow_block):
552552
def add_pow_block(spec, store, pow_block, test_steps):
553553
yield get_pow_block_file_name(pow_block), pow_block
554554
test_steps.append({"pow_block": get_pow_block_file_name(pow_block)})
555+
556+
557+
# EIP7732 Fork Choice Helpers
558+
559+
560+
def create_payload_attestation_message(
561+
spec, validator_index, beacon_block_root, slot, payload_present=True
562+
):
563+
"""Create PayloadAttestationMessage for PTC voting"""
564+
data = spec.PayloadAttestationData(
565+
beacon_block_root=beacon_block_root,
566+
slot=slot,
567+
payload_present=payload_present,
568+
)
569+
570+
return spec.PayloadAttestationMessage(
571+
validator_index=validator_index,
572+
data=data,
573+
signature=spec.BLSSignature(), # Empty signature for testing
574+
)

0 commit comments

Comments
 (0)