Repository navigation
fix(beacon): cache the anchor's execution hash so its child imports optimistically - #710
Conversation
…ptimistically is_optimistic_candidate_block reads the spec's is_execution_block(parent) off the execution-hash cache, which was filled only when a block was imported. The checkpoint anchor is never imported, so its first child looked like the child of a pre-merge block and, with the EL still syncing, waited out the 128-slot horizon: a checkpoint-synced follower sat at its anchor for up to ~25 min (observed on Hoodi, ~11 min, failing a multisync head-stall check). forkchoiceUpdated also sent a zero finalized and safe hash for the anchor. The spec reads is_execution_block from the block body, and its checkpoint-sync section lets the anchor be assumed valid, so get_forkchoice_store now caches the anchor's payload hash (the hash only, no verdict). A restart emptied the cache the same way, so resume rebuilds it for the unfinalized window from the stored blocks. One helper on SignedBeaconBlock decides which blocks have a hash to cache, shared by import, the anchor and resume.
🤖 Claude Code ReviewReview of PR 710: cache the anchor's execution hash, rebuild it on resumeThe approach is sound and I found no blocking issues. The anchor is the one block that never goes through What looks good
Minor points (non-blocking)
Overall this is a clean, well-tested fix. Only the stale doc comment in point 1 is worth touching before merge. Automated review by Claude (Anthropic) · sonnet · custom prompt |
🤖 Codex Code ReviewLooks good overall. The change is narrowly scoped, matches the spec intent around
Notes
Minor suggestion
Validation
Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Kimi Code ReviewI'll review this PR which fixes execution hash caching for checkpoint-sync anchors and resume scenarios. Let me analyze the changes carefully. SummaryThis PR fixes a consensus-critical bug where post-merge checkpoint-sync anchors and their first children were incorrectly subject to the Detailed Review1.
|
| Aspect | Verdict |
|---|---|
| Consensus correctness | ✅ Correct |
| Security | ✅ No vulnerabilities introduced |
| Performance | |
| Rust idioms | ✅ Good use of Option, filter |
| Test coverage | ✅ Comprehensive |
| Documentation | ✅ Clear and accurate |
Recommend merge after considering the double-decode optimization.
Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt
🗒️ Description / Motivation
A checkpoint-synced
ethlambda beaconpaired with an execution client that is still syncing sat at its anchor for up to ~25 minutes, loggingNot importing: the execution client has not validated this block and it is not an optimistic candidatefor the first post-anchor block until it was 128 slots old. Found by ethrex's multisync (lambdaclass/ethrex#7461) on Hoodi: ethlambda parked ~11 minutes and failed the run's 10-minute head-stall check, while Lighthouse in the same setup imports immediately.It is a spec deviation.
specs/bellatrix/optimistic-sync.md:and its "Checkpoint Sync" section lets the anchor be assumed
VALID. ethlambda readsis_execution_block(parent)off the execution-hash cache, which was filled only when a block was imported. The anchor is never imported, so its first child looked like the child of a pre-merge block. The same gap madeforkchoiceUpdatedsend a zero finalized/safe hash for the anchor, and a restart emptied the cache for every stored block (embedded ethrex restarts a few dozen blocks behind and answersSYNCING, so the park could repeat after a restart).What Changed
SignedBeaconBlock::execution_block_hash_if_execution_block(types): the one rule for which blocks have an execution hash to cache (the payload hash, minus a pre-merge all-zero one). Import now uses it, with no behavior change.get_forkchoice_store: caches the anchor's payload hash. Only the hash, no verdict.Store::restore_beacon_scratch: on resume, rebuilds the cache for the unfinalizedLiveChainwindow it already walks, against each block's own slot so pruning still bounds it.docs/beacon_engine.mdand the doc comments: the cache is filled at import, for the anchor, and on resume. The section that described the anchor park as expected behavior now describes the fix.Correctness / Behavior Guarantees
optimistic_roots, payload verdicts andlatestValidHashhandling are unchanged.Tests Added / Run
execution_block_hash_if_execution_block_gives_post_merge_hashes_only(types)a_post_merge_anchor_makes_its_first_child_an_optimistic_candidate,an_anchor_with_no_execution_payload_caches_no_hash, plus a gloas-anchor assertion (fork choice)resume_rebuilds_execution_hashes_for_the_unfinalized_window(storage)cargo test --profile release-fast --libforethlambda-types,ethlambda-storage,ethlambda-state-transition,ethlambda-blockchain: all pass.not an optimistic candidateline in any Hoodi log. Hoodi synced in 24 to 29 minutes. Before the fix, Hoodi failed its first run with the head stuck at the anchor for 10 minutes.Related Issues / PRs
✅ Verification Checklist
make fmt— cleanmake lint(clippy with-D warnings) — cleanmake test(test-consensusplustest-node, atrelease-fast) — only the four crates'--libtests above were run