Skip to content

Commit b71a085

Browse files
authored
eip7732: add process payload att tests (#4463)
This PR adds the following payload attestation tests - Valid basic attestations (payload present/not present) - Partial PTC participation - Invalid beacon block root/slot timing - Invalid signatures and edge cases
1 parent f722fc6 commit b71a085

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
from eth2spec.test.context import (
2+
always_bls,
3+
spec_state_test,
4+
with_eip7732_and_later,
5+
)
6+
from eth2spec.test.helpers.keys import privkeys
7+
from eth2spec.utils.ssz.ssz_typing import Bitvector
8+
9+
10+
def run_payload_attestation_processing(spec, state, payload_attestation, valid=True):
11+
"""
12+
Run ``process_payload_attestation``, yielding:
13+
- pre-state ('pre')
14+
- payload_attestation ('payload_attestation')
15+
- post-state ('post').
16+
If ``valid == False``, run expecting ``AssertionError``
17+
"""
18+
yield "pre", state
19+
yield "payload_attestation", payload_attestation
20+
21+
if not valid:
22+
try:
23+
spec.process_payload_attestation(state, payload_attestation)
24+
assert False, "Expected AssertionError"
25+
except AssertionError:
26+
pass
27+
return
28+
29+
spec.process_payload_attestation(state, payload_attestation)
30+
yield "post", state
31+
32+
33+
def prepare_signed_payload_attestation(
34+
spec,
35+
state,
36+
slot=None,
37+
beacon_block_root=None,
38+
payload_present=True,
39+
attesting_indices=None,
40+
valid_signature=True,
41+
):
42+
"""
43+
Helper to create a signed payload attestation with customizable parameters.
44+
"""
45+
if slot is None:
46+
if state.slot == 0:
47+
raise ValueError("Cannot attest to previous slot when state.slot is 0")
48+
slot = state.slot - 1 # Attest to previous slot
49+
50+
if beacon_block_root is None:
51+
beacon_block_root = state.latest_block_header.parent_root
52+
53+
# Get the PTC for the attested slot
54+
ptc = spec.get_ptc(state, slot)
55+
56+
if attesting_indices is None:
57+
# Default to all PTC members attesting
58+
attesting_indices = ptc
59+
60+
# Create aggregation bits
61+
aggregation_bits = Bitvector[spec.PTC_SIZE]()
62+
for i, validator_index in enumerate(ptc):
63+
if validator_index in attesting_indices:
64+
aggregation_bits[i] = True
65+
66+
# Create payload attestation data
67+
data = spec.PayloadAttestationData(
68+
beacon_block_root=beacon_block_root,
69+
slot=slot,
70+
payload_present=payload_present,
71+
)
72+
73+
# Create payload attestation
74+
payload_attestation = spec.PayloadAttestation(
75+
aggregation_bits=aggregation_bits,
76+
data=data,
77+
signature=spec.BLSSignature(),
78+
)
79+
80+
if valid_signature and attesting_indices:
81+
# Sign the attestation
82+
signing_root = spec.compute_signing_root(
83+
data, spec.get_domain(state, spec.DOMAIN_PTC_ATTESTER, spec.compute_epoch_at_slot(slot))
84+
)
85+
86+
signatures = []
87+
for validator_index in attesting_indices:
88+
if validator_index < len(privkeys):
89+
signature = spec.bls.Sign(privkeys[validator_index], signing_root)
90+
signatures.append(signature)
91+
92+
if signatures:
93+
payload_attestation.signature = spec.bls.Aggregate(signatures)
94+
95+
return payload_attestation
96+
97+
98+
#
99+
# Valid payload attestation tests
100+
#
101+
102+
103+
@with_eip7732_and_later
104+
@spec_state_test
105+
@always_bls
106+
def test_process_payload_attestation_payload_present(spec, state):
107+
"""
108+
Test basic valid payload attestation processing
109+
"""
110+
spec.process_slots(state, state.slot + 1)
111+
112+
payload_attestation = prepare_signed_payload_attestation(spec, state, payload_present=True)
113+
114+
yield from run_payload_attestation_processing(spec, state, payload_attestation)
115+
116+
117+
@with_eip7732_and_later
118+
@spec_state_test
119+
@always_bls
120+
def test_process_payload_attestation_payload_not_present(spec, state):
121+
"""
122+
Test valid payload attestation indicating payload was not present
123+
"""
124+
spec.process_slots(state, state.slot + 1)
125+
126+
payload_attestation = prepare_signed_payload_attestation(spec, state, payload_present=False)
127+
128+
yield from run_payload_attestation_processing(spec, state, payload_attestation)
129+
130+
131+
@with_eip7732_and_later
132+
@spec_state_test
133+
@always_bls
134+
def test_process_payload_attestation_partial_participation(spec, state):
135+
"""
136+
Test valid payload attestation with only some PTC members participating
137+
"""
138+
spec.process_slots(state, state.slot + 1)
139+
140+
ptc = spec.get_ptc(state, state.slot - 1)
141+
# Only half of the PTC members attest
142+
attesting_indices = ptc[: len(ptc) // 2] if ptc else []
143+
144+
payload_attestation = prepare_signed_payload_attestation(
145+
spec, state, attesting_indices=attesting_indices
146+
)
147+
148+
yield from run_payload_attestation_processing(spec, state, payload_attestation)
149+
150+
151+
#
152+
# Invalid beacon block root tests
153+
#
154+
155+
156+
@with_eip7732_and_later
157+
@spec_state_test
158+
def test_process_payload_attestation_invalid_beacon_block_root(spec, state):
159+
"""
160+
Test payload attestation with wrong beacon block root fails
161+
"""
162+
spec.process_slots(state, state.slot + 1)
163+
164+
wrong_root = spec.Root(b"\x42" * 32)
165+
payload_attestation = prepare_signed_payload_attestation(
166+
spec, state, beacon_block_root=wrong_root
167+
)
168+
169+
yield from run_payload_attestation_processing(spec, state, payload_attestation, valid=False)
170+
171+
172+
#
173+
# Invalid slot timing tests
174+
#
175+
176+
177+
@with_eip7732_and_later
178+
@spec_state_test
179+
def test_process_payload_attestation_future_slot(spec, state):
180+
"""
181+
Test payload attestation for future slot fails
182+
"""
183+
spec.process_slots(state, state.slot + 1)
184+
185+
# Try to attest to current slot (should be previous slot)
186+
payload_attestation = prepare_signed_payload_attestation(spec, state, slot=state.slot)
187+
188+
yield from run_payload_attestation_processing(spec, state, payload_attestation, valid=False)
189+
190+
191+
@with_eip7732_and_later
192+
@spec_state_test
193+
def test_process_payload_attestation_too_old_slot(spec, state):
194+
"""
195+
Test payload attestation for slot too far in the past fails
196+
"""
197+
# Advance state to slot 3
198+
spec.process_slots(state, state.slot + 3)
199+
200+
# Try to attest to slot 0 (2 slots ago, should be 1 slot ago)
201+
payload_attestation = prepare_signed_payload_attestation(spec, state, slot=state.slot - 2)
202+
203+
yield from run_payload_attestation_processing(spec, state, payload_attestation, valid=False)
204+
205+
206+
@with_eip7732_and_later
207+
@spec_state_test
208+
def test_process_payload_attestation_invalid_signature(spec, state):
209+
"""
210+
Test payload attestation with invalid signature fails
211+
"""
212+
# Advance state to slot 1 so we can attest to slot 0
213+
spec.process_slots(state, state.slot + 1)
214+
215+
payload_attestation = prepare_signed_payload_attestation(spec, state, valid_signature=False)
216+
217+
yield from run_payload_attestation_processing(spec, state, payload_attestation, valid=False)
218+
219+
220+
@with_eip7732_and_later
221+
@spec_state_test
222+
def test_process_payload_attestation_no_attesting_indices(spec, state):
223+
"""
224+
Test payload attestation with no attesting indices fails
225+
"""
226+
# Advance state to slot 1 so we can attest to slot 0
227+
spec.process_slots(state, state.slot + 1)
228+
229+
payload_attestation = prepare_signed_payload_attestation(spec, state, attesting_indices=[])
230+
231+
yield from run_payload_attestation_processing(spec, state, payload_attestation, valid=False)

tests/formats/operations/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Operations:
4949
| `deposit_request` | `DepositRequest` | `deposit_request` | `process_deposit_request(state, deposit_request)` (new in Electra) |
5050
| `withdrawal_request` | `WithdrawalRequest` | `withdrawal_request` | `process_withdrawal_request(state, withdrawal_request)` (new in Electra) |
5151
| `consolidation_request` | `ConsolidationRequest` | `consolidation_request` | `process_consolidation_request(state, consolidation_request)` (new in Electra) |
52+
| `payload_attestation` | `PayloadAttestation` | `payload_attestation` | `process_payload_attestation(state, payload_attestation)` (new in EIP7732) |
5253

5354
Note that `block_header` is not strictly an operation (and is a full `Block`),
5455
but processed in the same manner, and hence included here.

0 commit comments

Comments
 (0)