Skip to content

Commit da58896

Browse files
committed
test verify mid-loop finalized_slot visibility
1 parent c7b7232 commit da58896

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"""Fork Choice: Finalization advances mid-attestation processing.
2+
3+
This test verifies that attestations see updated finalized_slot during processing,
4+
as required by the 3sf-mini specification.
5+
6+
Reference: https://github.com/leanEthereum/leanSpec/pull/443
7+
"""
8+
9+
import pytest
10+
from consensus_testing import (
11+
AggregatedAttestationSpec,
12+
BlockSpec,
13+
BlockStep,
14+
ForkChoiceTestFiller,
15+
StoreChecks,
16+
)
17+
18+
from lean_spec.subspecs.containers.slot import Slot
19+
from lean_spec.subspecs.containers.validator import ValidatorIndex
20+
21+
pytestmark = pytest.mark.valid_until("Devnet")
22+
23+
24+
def test_finalization_advances_mid_attestation_processing(
25+
fork_choice_test: ForkChoiceTestFiller,
26+
) -> None:
27+
"""
28+
Verify attestations see updated finalized_slot during processing.
29+
30+
Scenario
31+
--------
32+
Process two attestations (both with supermajority) in the same block:
33+
34+
- Attestation A: source=1, target=2 -> justifies slot 2, finalizes slot 1
35+
- Attestation B: source=1, target=7 -> only justifiable after finalization
36+
37+
Justifiability
38+
--------------
39+
Slot 7 justifiability depends on finalized_slot:
40+
41+
- finalized=0: delta=7, NOT justifiable (7 > 5, not square, not pronic)
42+
- finalized=1: delta=6, IS justifiable (pronic = 2*3)
43+
44+
Expected Behavior (with fix)
45+
----------------------------
46+
47+
1. Attestation A justifies slot 2 and finalizes slot 1
48+
2. Attestation B sees updated finalized_slot=1, is justifiable, gets processed
49+
3. Attestation B justifies slot 7 (supermajority)
50+
4. latest_justified_slot = 7 (B processed after A)
51+
52+
Bug Behavior (without fix)
53+
--------------------------
54+
55+
1. Attestation A justifies slot 2 and finalizes slot 1
56+
2. Attestation B sees stale self.latest_finalized.slot=0, NOT justifiable
57+
3. Attestation B is SKIPPED
58+
4. latest_justified_slot = 2 (only A was processed)
59+
60+
This test FAILS without the fix (expects slot 7, gets slot 2).
61+
62+
Reference
63+
---------
64+
Fix: https://github.com/leanEthereum/leanSpec/pull/443
65+
"""
66+
fork_choice_test(
67+
steps=[
68+
# Build chain through slot 7
69+
BlockStep(
70+
block=BlockSpec(slot=Slot(1), label="block_1"),
71+
checks=StoreChecks(head_slot=Slot(1)),
72+
),
73+
BlockStep(
74+
block=BlockSpec(slot=Slot(2), label="block_2"),
75+
checks=StoreChecks(head_slot=Slot(2)),
76+
),
77+
# Slot 3: Justify slot 1 (source=0 -> target=1)
78+
# Need 3/4 validators for supermajority (3*3=9 >= 2*4=8)
79+
BlockStep(
80+
block=BlockSpec(
81+
slot=Slot(3),
82+
label="block_3",
83+
attestations=[
84+
AggregatedAttestationSpec(
85+
validator_ids=[
86+
ValidatorIndex(0),
87+
ValidatorIndex(1),
88+
ValidatorIndex(2),
89+
],
90+
slot=Slot(3),
91+
target_slot=Slot(1),
92+
target_root_label="block_1",
93+
),
94+
],
95+
),
96+
checks=StoreChecks(
97+
head_slot=Slot(3),
98+
latest_justified_slot=Slot(1),
99+
latest_finalized_slot=Slot(0),
100+
),
101+
),
102+
# Extend chain to slot 7
103+
BlockStep(
104+
block=BlockSpec(slot=Slot(4), label="block_4"),
105+
checks=StoreChecks(head_slot=Slot(4)),
106+
),
107+
BlockStep(
108+
block=BlockSpec(slot=Slot(5), label="block_5"),
109+
checks=StoreChecks(head_slot=Slot(5)),
110+
),
111+
BlockStep(
112+
block=BlockSpec(slot=Slot(6), label="block_6"),
113+
checks=StoreChecks(head_slot=Slot(6)),
114+
),
115+
BlockStep(
116+
block=BlockSpec(slot=Slot(7), label="block_7"),
117+
checks=StoreChecks(head_slot=Slot(7)),
118+
),
119+
# Slot 8: The critical block with both attestations
120+
BlockStep(
121+
block=BlockSpec(
122+
slot=Slot(8),
123+
attestations=[
124+
# Attestation A: Justify slot 2 and finalize slot 1
125+
# Source will be slot 1 (latest_justified from parent state)
126+
# Target is slot 2
127+
# Finalization: range(1+1, 2) = empty -> finalizes slot 1
128+
# Need 3/4 validators for supermajority
129+
AggregatedAttestationSpec(
130+
validator_ids=[
131+
ValidatorIndex(0),
132+
ValidatorIndex(1),
133+
ValidatorIndex(2),
134+
],
135+
slot=Slot(8),
136+
target_slot=Slot(2),
137+
target_root_label="block_2",
138+
),
139+
# Attestation B: Target slot 7 - ALSO needs supermajority
140+
# With finalized=0: delta=7, NOT justifiable -> SKIPPED
141+
# With finalized=1: delta=6, IS justifiable (pronic) -> PROCESSED
142+
#
143+
# If processed, slot 7 becomes justified and latest_justified=7
144+
# If skipped, latest_justified stays at 2
145+
# This is how we detect the bug!
146+
AggregatedAttestationSpec(
147+
validator_ids=[
148+
ValidatorIndex(0),
149+
ValidatorIndex(1),
150+
ValidatorIndex(2),
151+
],
152+
slot=Slot(8),
153+
target_slot=Slot(7),
154+
target_root_label="block_7",
155+
),
156+
],
157+
),
158+
checks=StoreChecks(
159+
head_slot=Slot(8),
160+
# With the fix: B is processed -> slot 7 justified -> latest_justified=7
161+
# Without fix: B is skipped -> latest_justified stays at 2
162+
latest_justified_slot=Slot(7),
163+
latest_finalized_slot=Slot(1),
164+
),
165+
),
166+
],
167+
)

0 commit comments

Comments
 (0)