Context
The safe target is a block root that has at least 2/3 of total validator weight behind it. It acts as a "confirmed" head — more conservative than the LMD-GHOST head, but more recent than the finalized checkpoint. Clients use it to report a "safe" head to execution layer consumers.
The safe target is recomputed at interval 3 of each slot via `Store.update_safe_target`. It runs LMD-GHOST with a `min_score` threshold equal to `ceil(2/3 * num_validators)`, so only blocks with supermajority weight qualify.
No spec test filler currently exercises `update_safe_target`.
What to test
Write a fork choice filler that:
- Creates 6 validators (makes the 2/3 threshold = 4 validators)
- Builds a 5-block chain
- Has 4 out of 6 validators attest to the head (meets 2/3 threshold)
- Ticks to interval 3 to trigger `update_safe_target`
- Verifies the safe target advances to the block with supermajority weight
Key assertions
- Before interval 3: safe target is at the initial anchor (genesis)
- After interval 3 with 4/6 attestations: safe target advances to the attested block
- `StoreChecks(safe_target=...)` validates the safe target root
Where to add the test
Create a new file: `tests/consensus/devnet/fc/test_safe_target.py`
Code skeleton
"""Safe target update tests."""
from consensus_testing import (
AttestationStep,
BlockSpec,
BlockStep,
ForkChoiceTestFiller,
GossipAttestationSpec,
StoreChecks,
TickStep,
)
from lean_spec.subspecs.containers.slot import Slot
from lean_spec.subspecs.containers.validator import ValidatorIndex
def test_safe_target_advances_with_supermajority_weight(
fork_choice_test: ForkChoiceTestFiller,
) -> None:
"""Safe target advances when >= 2/3 validators attest to a block."""
# 6 validators -> 2/3 threshold = 4
fork_choice_test(
num_validators=6,
steps=[
# Build chain
BlockStep(block=BlockSpec(slot=Slot(1), label="block_1")),
BlockStep(block=BlockSpec(slot=Slot(2), label="block_2")),
# 4 out of 6 validators attest (meets threshold)
*[
AttestationStep(
attestation=GossipAttestationSpec(
validator_id=ValidatorIndex(i),
slot=Slot(2),
target_slot=Slot(2),
target_root_label="block_2",
),
)
for i in range(4)
],
# Tick to interval 3 to trigger update_safe_target
TickStep(
time=..., # interval 3 of slot 3
checks=StoreChecks(
# safe_target should now point to block_2 or its ancestor
# that has supermajority weight
),
),
],
)
Note: Check if `fork_choice_test` accepts a `num_validators` parameter, or if you need to use a custom genesis state. Refer to `tests/consensus/devnet/fc/test_fork_choice_reorgs.py` which uses 6+ validators for examples.
How to run
uv run fill --fork=devnet --clean -n auto -k test_safe_target_advances
References
- `Store.update_safe_target`: `src/lean_spec/subspecs/forkchoice/store.py`
- `Store._compute_lmd_ghost_head` with `min_score`: same file
- Threshold: `3 * count >= 2 * num_validators` (integer arithmetic, no floats)
Context
The safe target is a block root that has at least 2/3 of total validator weight behind it. It acts as a "confirmed" head — more conservative than the LMD-GHOST head, but more recent than the finalized checkpoint. Clients use it to report a "safe" head to execution layer consumers.
The safe target is recomputed at interval 3 of each slot via `Store.update_safe_target`. It runs LMD-GHOST with a `min_score` threshold equal to `ceil(2/3 * num_validators)`, so only blocks with supermajority weight qualify.
No spec test filler currently exercises `update_safe_target`.
What to test
Write a fork choice filler that:
Key assertions
Where to add the test
Create a new file: `tests/consensus/devnet/fc/test_safe_target.py`
Code skeleton
How to run
References