From 4a6afd7e8ba2b47c161dcb0ba41ad8f6fc929a6a Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:02:33 +0200 Subject: [PATCH] fix(testing): make finalized-safety head check scheme-independent A fork-choice vector asserted head_slot=5 at the step that adds an empty sibling block above the canonical head. At that step both children of the justified block carry zero distinguishing fork-choice weight, so the head is a pure tie broken by the larger block root. A block root embeds the post-state root, which embeds the validator public keys, so the tie resolves differently under the test and production signature schemes. The vector passed under the test scheme and failed under production, where the sibling at slot 6 won the tie. Stop asserting which sibling is head at the tie step. Assert instead that the empty fork block changes neither justification nor finalization, which holds under both schemes. The genuine "above fork wins" property is still asserted at the next step, where the fork gathers the votes that justify it and move the head onto it with no tie. Also add a repository rule: every test change must update the associated documentation in the same change, per the documentation rules. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 11 +++++++++++ .../lstar/fork_choice/test_finalized_safety.py | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 662f818cc..4ddda2145 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,3 +117,14 @@ subspecifications that the Lean Ethereum protocol relies on. metacharacters escaped); prefer the explicit full-equality assertion above. - This mirrors the full-equality rule for ordinary assertions: assert the whole object, never a piece of it. +- **CRITICAL - KEEP TEST DOCUMENTATION IN SYNC WITH THE TEST**: This is a STRICT requirement. Every + time you change a test, update the documentation that describes it in the SAME change, following + the documentation rules (`.claude/rules/documentation.md`, and for `tests/consensus/` the + Given/When/Then standard in that file). A test's docstring is part of the test; a change that + leaves the docstring describing the old behavior is incomplete. + - When you add, remove, or change a step, assertion, or expected value, reconcile the docstring so + it still describes exactly what the test does. + - For `tests/consensus/` vectors, the step assertions and the Given/When/Then docstring must stay + one-to-one: if an assertion changes, the matching docstring line changes with it. + - Do not weaken a docstring into vagueness to avoid updating it; describe the new behavior + precisely, as the doc-writer rules require. diff --git a/tests/consensus/lstar/fork_choice/test_finalized_safety.py b/tests/consensus/lstar/fork_choice/test_finalized_safety.py index 5e7095999..7296ef4c4 100644 --- a/tests/consensus/lstar/fork_choice/test_finalized_safety.py +++ b/tests/consensus/lstar/fork_choice/test_finalized_safety.py @@ -323,8 +323,10 @@ def test_fork_above_finalized_wins_at_or_below_loses( BlockStep( block=BlockSpec(slot=Slot(6), parent_label="block_4", label="above_6"), checks=StoreChecks( - head_slot=Slot(5), - head_root_label="block_5", + latest_justified_slot=Slot(4), + latest_justified_root_label="block_4", + latest_finalized_slot=Slot(3), + latest_finalized_root_label="block_3", ), ), BlockStep(