Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions tests/consensus/devnet/fc/test_attestation_target_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,3 +526,143 @@ def test_attestation_target_walkback_bounded_by_lookback(
for s in range(1, head + 1)
]
fork_choice_test(steps=steps)


def test_attestation_target_selection_after_finality_has_moved(
fork_choice_test: ForkChoiceTestFiller,
) -> None:
"""
Attestation target selection respects a non-zero finalized boundary.

Scenario
--------
1. Justify block_1 in block_3
2. Process block_8 so slots 2 and 7 become justified and slot 1 becomes finalized
3. Extend the empty chain through block_11

Expected Behavior
-----------------
1. latest_justified_slot remains 7
2. latest_finalized_slot remains 1
3. safe_target settles on block_7
4. The attestation target also resolves to block_7
"""
fork_choice_test(
steps=[
BlockStep(
block=BlockSpec(slot=Slot(1), label="block_1"),
checks=StoreChecks(head_slot=Slot(1)),
),
BlockStep(
block=BlockSpec(slot=Slot(2), parent_label="block_1", label="block_2"),
checks=StoreChecks(head_slot=Slot(2)),
),
BlockStep(
block=BlockSpec(
slot=Slot(3),
parent_label="block_2",
label="block_3",
attestations=[
AggregatedAttestationSpec(
validator_ids=[
ValidatorIndex(0),
ValidatorIndex(1),
ValidatorIndex(2),
],
slot=Slot(3),
target_slot=Slot(1),
target_root_label="block_1",
),
],
),
checks=StoreChecks(
head_slot=Slot(3),
latest_justified_slot=Slot(1),
latest_finalized_slot=Slot(0),
),
),
BlockStep(
block=BlockSpec(slot=Slot(4), parent_label="block_3", label="block_4"),
checks=StoreChecks(head_slot=Slot(4)),
),
BlockStep(
block=BlockSpec(slot=Slot(5), parent_label="block_4", label="block_5"),
checks=StoreChecks(head_slot=Slot(5)),
),
BlockStep(
block=BlockSpec(slot=Slot(6), parent_label="block_5", label="block_6"),
checks=StoreChecks(head_slot=Slot(6)),
),
BlockStep(
block=BlockSpec(slot=Slot(7), parent_label="block_6", label="block_7"),
checks=StoreChecks(head_slot=Slot(7)),
),
BlockStep(
block=BlockSpec(
slot=Slot(8),
parent_label="block_7",
label="block_8",
attestations=[
AggregatedAttestationSpec(
validator_ids=[
ValidatorIndex(0),
ValidatorIndex(1),
ValidatorIndex(2),
],
slot=Slot(8),
target_slot=Slot(2),
target_root_label="block_2",
),
AggregatedAttestationSpec(
validator_ids=[
ValidatorIndex(0),
ValidatorIndex(1),
ValidatorIndex(2),
],
slot=Slot(8),
target_slot=Slot(7),
target_root_label="block_7",
),
],
),
checks=StoreChecks(
head_slot=Slot(8),
latest_justified_slot=Slot(7),
latest_finalized_slot=Slot(1),
),
),
BlockStep(
block=BlockSpec(slot=Slot(9), parent_label="block_8", label="block_9"),
checks=StoreChecks(
head_slot=Slot(9),
latest_justified_slot=Slot(7),
latest_finalized_slot=Slot(1),
safe_target_slot=Slot(7),
safe_target_root_label="block_7",
attestation_target_slot=Slot(7),
),
),
BlockStep(
block=BlockSpec(slot=Slot(10), parent_label="block_9", label="block_10"),
checks=StoreChecks(
head_slot=Slot(10),
latest_justified_slot=Slot(7),
latest_finalized_slot=Slot(1),
safe_target_slot=Slot(7),
safe_target_root_label="block_7",
attestation_target_slot=Slot(7),
),
),
BlockStep(
block=BlockSpec(slot=Slot(11), parent_label="block_10", label="block_11"),
checks=StoreChecks(
head_slot=Slot(11),
latest_justified_slot=Slot(7),
latest_finalized_slot=Slot(1),
safe_target_slot=Slot(7),
safe_target_root_label="block_7",
attestation_target_slot=Slot(7),
),
),
],
)
196 changes: 196 additions & 0 deletions tests/consensus/devnet/fc/test_block_production.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,199 @@ def test_produce_block_includes_pending_attestations(
),
],
)


def test_block_builder_recovers_finality_after_non_zero_boundary_stall(
fork_choice_test: ForkChoiceTestFiller,
) -> None:
"""
Block production recovers finality after the finalized slot has already moved.

Scenario
--------
1. Justify block_1 in block_3
2. Process block_8 so slots 2 and 7 become justified and slot 1 becomes finalized
3. Extend the chain through block_11
4. Gossip two aggregated attestations for slot 12:
one from block_7 to block_10, then one from block_10 to block_11
5. Produce block_12 without explicit attestation specs

Expected Behavior
-----------------
1. The builder first includes the block_7 to block_10 attestation
2. That attestation justifies slot 10 and finalizes slot 7
3. The re-iteration then includes the block_10 to block_11 attestation
4. The post-store head is block_12 at slot 12
5. latest_justified_slot is 11
6. latest_finalized_slot is 10
7. The block body contains exactly both aggregated attestations
"""
aggregate_interval = 11 * int(INTERVALS_PER_SLOT) + 2
aggregate_time = math.ceil(aggregate_interval * int(MILLISECONDS_PER_INTERVAL) / 1000)
block_time = 12 * int(SECONDS_PER_SLOT)

fork_choice_test(
steps=[
BlockStep(
block=BlockSpec(slot=Slot(1), label="block_1"),
checks=StoreChecks(head_slot=Slot(1)),
),
BlockStep(
block=BlockSpec(slot=Slot(2), parent_label="block_1", label="block_2"),
checks=StoreChecks(head_slot=Slot(2)),
),
BlockStep(
block=BlockSpec(
slot=Slot(3),
parent_label="block_2",
label="block_3",
attestations=[
AggregatedAttestationSpec(
validator_ids=[
ValidatorIndex(0),
ValidatorIndex(1),
ValidatorIndex(2),
],
slot=Slot(3),
target_slot=Slot(1),
target_root_label="block_1",
),
],
),
checks=StoreChecks(
head_slot=Slot(3),
latest_justified_slot=Slot(1),
latest_finalized_slot=Slot(0),
),
),
BlockStep(
block=BlockSpec(slot=Slot(4), parent_label="block_3", label="block_4"),
checks=StoreChecks(head_slot=Slot(4)),
),
BlockStep(
block=BlockSpec(slot=Slot(5), parent_label="block_4", label="block_5"),
checks=StoreChecks(head_slot=Slot(5)),
),
BlockStep(
block=BlockSpec(slot=Slot(6), parent_label="block_5", label="block_6"),
checks=StoreChecks(head_slot=Slot(6)),
),
BlockStep(
block=BlockSpec(slot=Slot(7), parent_label="block_6", label="block_7"),
checks=StoreChecks(head_slot=Slot(7)),
),
BlockStep(
block=BlockSpec(
slot=Slot(8),
parent_label="block_7",
label="block_8",
attestations=[
AggregatedAttestationSpec(
validator_ids=[
ValidatorIndex(0),
ValidatorIndex(1),
ValidatorIndex(2),
],
slot=Slot(8),
target_slot=Slot(2),
target_root_label="block_2",
),
AggregatedAttestationSpec(
validator_ids=[
ValidatorIndex(0),
ValidatorIndex(1),
ValidatorIndex(2),
],
slot=Slot(8),
target_slot=Slot(7),
target_root_label="block_7",
),
],
),
checks=StoreChecks(
head_slot=Slot(8),
latest_justified_slot=Slot(7),
latest_justified_root_label="block_7",
latest_finalized_slot=Slot(1),
latest_finalized_root_label="block_1",
),
),
*[
BlockStep(
block=BlockSpec(
slot=Slot(n),
parent_label=f"block_{n - 1}",
label=f"block_{n}",
),
checks=(StoreChecks(head_slot=Slot(n)) if n == 9 or n == 11 else None),
)
for n in range(9, 12)
],
TickStep(time=aggregate_time),
GossipAggregatedAttestationStep(
attestation=GossipAggregatedAttestationSpec(
validator_ids=[
ValidatorIndex(0),
ValidatorIndex(1),
ValidatorIndex(2),
],
slot=Slot(12),
target_slot=Slot(10),
target_root_label="block_10",
source_root_label="block_7",
source_slot=Slot(7),
),
),
GossipAggregatedAttestationStep(
attestation=GossipAggregatedAttestationSpec(
validator_ids=[
ValidatorIndex(1),
ValidatorIndex(2),
ValidatorIndex(3),
],
slot=Slot(12),
target_slot=Slot(11),
target_root_label="block_11",
source_root_label="block_10",
source_slot=Slot(10),
),
),
TickStep(
time=block_time,
checks=StoreChecks(
latest_justified_slot=Slot(7),
latest_finalized_slot=Slot(1),
latest_known_aggregated_target_slots=[
Slot(2),
Slot(7),
Slot(10),
Slot(11),
],
),
),
BlockStep(
block=BlockSpec(slot=Slot(12), parent_label="block_11", label="block_12"),
checks=StoreChecks(
head_slot=Slot(12),
head_root_label="block_12",
latest_justified_slot=Slot(11),
latest_justified_root_label="block_11",
latest_finalized_slot=Slot(10),
latest_finalized_root_label="block_10",
block_attestation_count=2,
block_attestations=[
AggregatedAttestationCheck(
participants={0, 1, 2},
attestation_slot=Slot(12),
target_slot=Slot(10),
),
AggregatedAttestationCheck(
participants={1, 2, 3},
attestation_slot=Slot(12),
target_slot=Slot(11),
),
],
),
),
],
)
Loading
Loading