Skip to content

Commit e835d41

Browse files
tcoratgerclaude
andauthored
test(state-transition): cover JUSTIFIED_SLOT_OUT_OF_RANGE rejection (leanEthereum#1100)
The defensive guard on the justification-bitfield read path had zero consensus-vector coverage. A refactor that dropped it, raised a raw IndexError, or returned not-justified would pass CI and could diverge clients. Add a state_transition vector that drives a vote whose source slot sits one past the tracked window and assert the rejection reason plus the full formatted message. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 49ef89f commit e835d41

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""State Transition: justification-bitfield range guard"""
2+
3+
import pytest
4+
5+
from consensus_testing import (
6+
AggregatedAttestationSpec,
7+
BlockSpec,
8+
ExpectedRejection,
9+
StateTransitionTestFiller,
10+
)
11+
from lean_spec.spec.forks import RejectionReason, Slot, ValidatorIndex
12+
13+
pytestmark = pytest.mark.valid_until("Lstar")
14+
15+
16+
def test_source_slot_beyond_tracked_range_rejects_block(
17+
state_transition_test: StateTransitionTestFiller,
18+
) -> None:
19+
"""
20+
A vote whose source slot sits past the tracked justification window rejects the block.
21+
22+
Given
23+
-----
24+
- 4 validators.
25+
- the chain:
26+
genesis -> block_1(1) -> block(2)
27+
- finalization stays at genesis, so the tracked window is anchored at slot 0.
28+
- processing block(2) tracks justification only for slot 1, a window of length 1.
29+
- block(2) carries a forced V0, V1, V2 vote targeting block_1.
30+
- the vote's source slot is forced to 2, one past the tracked window.
31+
32+
When
33+
----
34+
- the chain processes block(2).
35+
36+
Then
37+
----
38+
- the source-slot read finds no tracked bit for slot 2.
39+
- the block is rejected with JUSTIFIED_SLOT_OUT_OF_RANGE.
40+
- the message names slot 2, finalized boundary 0, and tracked length 1.
41+
"""
42+
state_transition_test(
43+
blocks=[
44+
BlockSpec(slot=Slot(1), label="block_1"),
45+
BlockSpec(
46+
slot=Slot(2),
47+
parent_label="block_1",
48+
forced_attestations=[
49+
AggregatedAttestationSpec(
50+
validator_indices=[
51+
ValidatorIndex(0),
52+
ValidatorIndex(1),
53+
ValidatorIndex(2),
54+
],
55+
slot=Slot(2),
56+
target_slot=Slot(1),
57+
target_root_label="block_1",
58+
source_root_label="block_1",
59+
source_slot=Slot(2),
60+
),
61+
],
62+
),
63+
],
64+
post=None,
65+
expected_rejection=ExpectedRejection(
66+
reason=RejectionReason.JUSTIFIED_SLOT_OUT_OF_RANGE,
67+
exact_message=(
68+
"Slot 2 is outside the tracked range (finalized_boundary=0, tracked_length=1)"
69+
),
70+
),
71+
)

0 commit comments

Comments
 (0)