From ca92ee72057b452554b9227c51f55f868e20dd3b Mon Sep 17 00:00:00 2001 From: shaaibu7 Date: Wed, 15 Apr 2026 08:51:54 +0100 Subject: [PATCH 1/2] test(fc): add attestation target walkback bound filler Adds test_attestation_target_walkback_bounded_by_lookback to verify that get_attestation_target caps the walk from head at JUSTIFICATION_LOOKBACK_SLOTS, preventing target collapse to safe_target when the head runs far ahead. Head and target are derived symbolically from JUSTIFICATION_LOOKBACK_SLOTS and Slot.is_justifiable_after, so the test stays valid if either the constant or the justifiability rules change. --- .../fc/test_attestation_target_selection.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/consensus/devnet/fc/test_attestation_target_selection.py b/tests/consensus/devnet/fc/test_attestation_target_selection.py index a62fcb695..fe1846d04 100644 --- a/tests/consensus/devnet/fc/test_attestation_target_selection.py +++ b/tests/consensus/devnet/fc/test_attestation_target_selection.py @@ -9,6 +9,7 @@ StoreChecks, ) +from lean_spec.subspecs.chain.config import JUSTIFICATION_LOOKBACK_SLOTS from lean_spec.subspecs.containers.slot import Slot from lean_spec.subspecs.containers.validator import ValidatorIndex @@ -472,3 +473,55 @@ def test_attestation_target_justifiable_constraint( ) fork_choice_test(steps=steps) + + +def test_attestation_target_walkback_bounded_by_lookback( + fork_choice_test: ForkChoiceTestFiller, +) -> None: + """ + Attestation target walks back at most JUSTIFICATION_LOOKBACK_SLOTS from head. + + Scenario + -------- + Build a long chain (10+ blocks) with no attestations so the safe target + stays at genesis. Pick the smallest such head where + (head - JUSTIFICATION_LOOKBACK_SLOTS) is justifiable from genesis, so the + bounded walk lands cleanly without the secondary justifiability walk + altering the result. + + Expected: + - Head far ahead of safe target + - Safe target at genesis (slot 0) + - Attestation target at (head - JUSTIFICATION_LOOKBACK_SLOTS) + - NOT at the safe target (slot 0) + + Why This Matters + ---------------- + The walkback bound prevents validators from always targeting the same + old block when the head runs far ahead of safe target. + + Without the bound, the target would collapse to genesis and the + justification frontier would never advance. + """ + lookback = int(JUSTIFICATION_LOOKBACK_SLOTS) + + # Smallest "long chain" head where the bounded walk lands on a + # justifiable slot, so the secondary justifiability walk is a no-op. + head = 10 + while not Slot(head - lookback).is_justifiable_after(Slot(0)): + head += 1 + head_slot = Slot(head) + target_slot = Slot(head - lookback) + + steps = [ + BlockStep( + block=BlockSpec(slot=Slot(s), label=f"block_{s}"), + checks=( + StoreChecks(head_slot=head_slot, attestation_target_slot=target_slot) + if Slot(s) == head_slot + else None + ), + ) + for s in range(1, head + 1) + ] + fork_choice_test(steps=steps) From 1f57be4fd613ca2b18d8c107625a6ffebe9c1b6d Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:00:38 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix(test):=20correct=20walkback=20bound=20d?= =?UTF-8?q?ocstring=20=E2=80=94=20risk=20is=20over-aggressive=20targeting,?= =?UTF-8?q?=20not=20genesis=20collapse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without the walkback bound the target stays near head (too aggressive), it does not collapse to genesis. The docstring previously described the opposite failure mode. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../devnet/fc/test_attestation_target_selection.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/consensus/devnet/fc/test_attestation_target_selection.py b/tests/consensus/devnet/fc/test_attestation_target_selection.py index fe1846d04..90f231bf3 100644 --- a/tests/consensus/devnet/fc/test_attestation_target_selection.py +++ b/tests/consensus/devnet/fc/test_attestation_target_selection.py @@ -497,11 +497,12 @@ def test_attestation_target_walkback_bounded_by_lookback( Why This Matters ---------------- - The walkback bound prevents validators from always targeting the same - old block when the head runs far ahead of safe target. + The walkback bound keeps the attestation target conservatively behind + head, anchored toward safe target. - Without the bound, the target would collapse to genesis and the - justification frontier would never advance. + Without the bound, validators would target blocks too close to head, + bypassing the safe target governor and attesting to blocks without + sufficient supermajority endorsement. """ lookback = int(JUSTIFICATION_LOOKBACK_SLOTS)