Context
LMD-GHOST selects the head based on attestation weight, not fork depth. A shorter fork with more attestation support should win over a deeper fork with less support.
This is a fundamental property of LMD-GHOST that distinguishes it from longest-chain rules. Every client must implement this correctly.
The existing fork choice head tests cover competing forks, but they use chain depth (more blocks = more weight implicitly, since each block's proposer attests). This test explicitly separates depth from weight.
What to test
Write a fork choice filler that:
- Creates a common prefix at slot 1
- Fork A: extends 5 blocks deep (slots 2–6) with 1 attestation each
- Fork B: extends only 2 blocks (slots 2–3) but with 4 attestations per block
- Verifies LMD-GHOST selects Fork B (heavier), not Fork A (deeper)
Setup with 6 validators
- Fork A: blocks at slots 2, 3, 4, 5, 6 — each block has 1 validator attesting
- Fork B: blocks at slots 2, 3 — but validators 0, 1, 2, 3 all attest to slot 3
- Fork B has more total weight on its head (4 attestations) vs Fork A (1 attestation on its head)
Key assertions
- `StoreChecks(head_root_label="fork_b_3")` — head is on the shorter but heavier fork
- After removing attestations from Fork B validators, head could flip to Fork A
Where to add the test
Add to: `tests/consensus/devnet/fc/test_fork_choice_head.py`
Code skeleton
def test_head_selection_by_weight_not_depth(
fork_choice_test: ForkChoiceTestFiller,
) -> None:
"""Shorter fork with more attestation weight wins over deeper fork."""
fork_choice_test(
num_validators=6,
steps=[
# Common prefix
BlockStep(block=BlockSpec(slot=Slot(1), label="common")),
# Fork A: 5 blocks deep, minimal attestations
BlockStep(
block=BlockSpec(slot=Slot(2), parent_label="common", label="a_2"),
),
BlockStep(block=BlockSpec(slot=Slot(3), parent_label="a_2", label="a_3")),
BlockStep(block=BlockSpec(slot=Slot(4), parent_label="a_3", label="a_4")),
BlockStep(block=BlockSpec(slot=Slot(5), parent_label="a_4", label="a_5")),
BlockStep(block=BlockSpec(slot=Slot(6), parent_label="a_5", label="a_6")),
# Fork B: only 2 blocks, but heavy attestation support
BlockStep(
block=BlockSpec(slot=Slot(2), parent_label="common", label="b_2"),
),
BlockStep(
block=BlockSpec(slot=Slot(3), parent_label="b_2", label="b_3"),
),
# 4 out of 6 validators attest to fork B's head
*[
AttestationStep(
attestation=GossipAttestationSpec(
validator_id=ValidatorIndex(i),
slot=Slot(3),
target_slot=Slot(3),
target_root_label="b_3",
),
)
for i in range(4)
],
# Head should be fork B (heavier), not fork A (deeper)
# TODO: tick to accept attestations, then check head
],
)
How to run
uv run fill --fork=devnet --clean -n auto -k test_head_selection_by_weight
References
- `Store._compute_lmd_ghost_head`: `src/lean_spec/subspecs/forkchoice/store.py`
- `Store.compute_block_weights`: same file
- LMD-GHOST: Latest Message Driven Greediest Heaviest Observed SubTree
Context
LMD-GHOST selects the head based on attestation weight, not fork depth. A shorter fork with more attestation support should win over a deeper fork with less support.
This is a fundamental property of LMD-GHOST that distinguishes it from longest-chain rules. Every client must implement this correctly.
The existing fork choice head tests cover competing forks, but they use chain depth (more blocks = more weight implicitly, since each block's proposer attests). This test explicitly separates depth from weight.
What to test
Write a fork choice filler that:
Setup with 6 validators
Key assertions
Where to add the test
Add to: `tests/consensus/devnet/fc/test_fork_choice_head.py`
Code skeleton
How to run
References