Skip to content

test(fc): safe target advances with supermajority attestation weight #558

@tcoratger

Description

@tcoratger

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:

  1. Creates 6 validators (makes the 2/3 threshold = 4 validators)
  2. Builds a 5-block chain
  3. Has 4 out of 6 validators attest to the head (meets 2/3 threshold)
  4. Ticks to interval 3 to trigger `update_safe_target`
  5. 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)

Metadata

Metadata

Assignees

Labels

good first issueGood for newcomerstestsScope: Changes to the spec tests

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions