Context
The safe target only advances when a block has at least 2/3 of total validator weight behind it. When attestation weight is insufficient, the safe target must remain at the justified root (typically genesis for a fresh chain).
This is the "negative" counterpart to the supermajority test — equally important for client teams to verify they don't incorrectly advance the safe target.
What to test
Write a fork choice filler that:
- Creates 6 validators (2/3 threshold = 4)
- Builds a chain
- Has only 2 out of 6 validators attest (below threshold)
- Ticks to interval 3 to trigger `update_safe_target`
- Verifies the safe target does NOT advance — it stays at the justified root
Key assertions
- After interval 3 with only 2/6 attestations: safe target remains at genesis/justified root
- `StoreChecks(safe_target=...)` validates the safe target is unchanged
Where to add the test
Add to: `tests/consensus/devnet/fc/test_safe_target.py`
Code skeleton
def test_safe_target_stays_at_justified_with_insufficient_weight(
fork_choice_test: ForkChoiceTestFiller,
) -> None:
"""Safe target does not advance when < 2/3 validators attest."""
# 6 validators -> threshold = 4, only 2 attest
fork_choice_test(
num_validators=6,
steps=[
BlockStep(block=BlockSpec(slot=Slot(1), label="block_1")),
BlockStep(block=BlockSpec(slot=Slot(2), label="block_2")),
# Only 2 out of 6 validators attest (below threshold)
AttestationStep(
attestation=GossipAttestationSpec(
validator_id=ValidatorIndex(0),
slot=Slot(2),
target_slot=Slot(2),
target_root_label="block_2",
),
),
AttestationStep(
attestation=GossipAttestationSpec(
validator_id=ValidatorIndex(1),
slot=Slot(2),
target_slot=Slot(2),
target_root_label="block_2",
),
),
# Tick to interval 3
TickStep(
time=..., # interval 3 of slot 3
checks=StoreChecks(
# safe_target should still be at genesis/justified root
),
),
],
)
How to run
uv run fill --fork=devnet --clean -n auto -k test_safe_target_stays
References
- `Store.update_safe_target`: `src/lean_spec/subspecs/forkchoice/store.py`
- Threshold arithmetic: `3 * count >= 2 * num_validators` — with 2/6: `32=6 < 26=12`, fails
Context
The safe target only advances when a block has at least 2/3 of total validator weight behind it. When attestation weight is insufficient, the safe target must remain at the justified root (typically genesis for a fresh chain).
This is the "negative" counterpart to the supermajority test — equally important for client teams to verify they don't incorrectly advance the safe target.
What to test
Write a fork choice filler that:
Key assertions
Where to add the test
Add to: `tests/consensus/devnet/fc/test_safe_target.py`
Code skeleton
How to run
References