fix(forkchoice): correct LMD-GHOST orphan-skip, tighten checkpoint advance, assert start_root#727
Merged
tcoratger merged 2 commits intoMay 17, 2026
Conversation
…vance, assert start_root Three correctness fixes surfaced by the consensus-researcher review. Behavior on the hot path is preserved; an inconsistency between on_block and build_block tie semantics is resolved in favor of the documented "store-authoritative on tie" rule. 1. _compute_lmd_ghost_head orphan-skip was dead code. `if not block.parent_root: continue` was always False because `block.parent_root` is `Bytes32` (always 32 bytes, never empty). Genesis blocks landed under children_map[Bytes32.zero()] instead of being skipped. The bucket was never consulted because the walk anchors at the justified root and only descends. The misleading filter is removed; a comment now explains why genesis cannot pollute the walk. 2. Checkpoint advance semantics centralized in Checkpoint.advance_to. Two `max(...)` call sites in lstar/spec.py had opposite argument orders, which silently produced opposite tie behavior. The comment at on_block explicitly documents "store wins on tie"; build_block contradicted that. Both sites now use store.latest_*.advance_to(...) which encodes the documented intent in the type itself. 3. _compute_lmd_ghost_head now asserts start_root is a known block. Previously a bad anchor produced a cryptic KeyError deep in the weight loop. The assert states the invariant up front. An existing test that constructed a malformed store is updated to expect the clearer AssertionError. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three correctness fixes surfaced by the consensus-researcher review.
its behavior.
Checkpoint.advance_to,resolving an inconsistency where
on_blockandbuild_blockhadopposite tie semantics because of different
max()argument orders.with a useful message instead of a
KeyErrordeep in the weight loop.Behavior on the hot path is preserved. The only intentional semantic
change is at
build_blockon slot ties: the store now wins consistentlywith the documented intent at
on_block.What was wrong
1. Dead orphan-skip in
_compute_lmd_ghost_headblock.parent_rootisBytes32— always 32 bytes, sobool(parent_root)is always
True. The branch never fired. Genesis blocks landed inchildren_map[Bytes32.zero()]instead of being skipped. The bucket wasnever consulted because the walk anchors at the justified root and only
descends, so the bug was silent. The filter is removed and the comment
now explains why genesis cannot pollute the walk.
2. Inconsistent tie semantics across
max()call sitesSlot-only
__lt__plusmax()means tie behavior depends on argumentorder. The two sites had opposite orders. The fix introduces
Checkpoint.advance_to(candidate)which encodes the documented "receiverwins on tie" rule in the type. Both call sites now read:
store.latest_*.advance_to(post_state.latest_*).3. Missing invariant on
_compute_lmd_ghost_headBoth callers pass
store.latest_justified.rootwhich is always a knownblock. A future misuse would have produced a cryptic
KeyErrordeep inthe weight loop. A one-line assert states the invariant up front.
Test plan
Checkpoint.advance_tocovering higher slot,lower slot, slot tie, and idempotence
_compute_lmd_ghost_headrejecting an unknownanchor
test_produce_block_missing_parent_stateupdated toexpect the clearer
AssertionErrortests/lean_spec/subspecs/containers/test_checkpoint.pyand
tests/lean_spec/forks/lstar/forkchoice/passruff check,ruff format --check,ty checkall green🤖 Generated with Claude Code