From 74a44de2e5b0707eaf3165a6b839f7e5f88fa931 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 12 Aug 2026 19:04:31 +0800 Subject: [PATCH] fix: recompute head before proposer boost dependent root check The spec computes `head = get_head(store)` inside `on_block`, immediately before `update_proposer_boost_root(store, head.root, block_root)` compares the head's proposer-shuffling dependent root against the imported block's. We were passing the cached `this.head` instead, which can be stale: an epoch transition tick pulls up the justified checkpoint, which moves the head, but nothing recomputes it before the next block is imported. Surfaced by the alpha.13 compliance vectors (nightly comptests, red since 2026-08-04): FAIL electra|fulu/fork_choice_compliance/block_tree_test/pyspec_tests/ block_tree_test_15_397678783_1 Error: Invalid proposer boost root at step 136 Expected: "0xa27134108a78f561433773014c169ddc29fd5ae46905aba26b9c7c31b8842eda" Received: "0x0000000000000000000000000000000000000000000000000000000000000000" In that case the `tick 192` step crosses into epoch 4 and pulls justified from epoch 2 to 3, moving the head off `0xcc0a54e2` (slot 24). The block imported at slot 32 shares its dependent root with the new head but not the stale one, so the boost was withheld. Split out `isProposerBoostCandidate` so `updateHead()` stays off the hot path: it now runs only for the first timely block of a slot, bounding the extra `get_head()` to at most once per slot rather than once per block import. Measured that cost while reviewing this: `updateHead()` is ~4.6ms at 1M validators, and flat regardless of vote churn since `computeDeltas()` scans every validator either way. Refresh the stale "400ms / run as of Aug 2021" claim on its docblock, which is off by ~80x and is the figure a reader would otherwise use to judge this change. --- .../fork-choice/src/forkChoice/forkChoice.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 23c78c14d170..608252372983 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -534,7 +534,11 @@ export class ForkChoice implements IForkChoice { * Run the fork choice rule to determine the head. * Update the head cache. * - * Very expensive function (400ms / run as of Aug 2021). Call when the head really needs to be re-calculated. + * Cost is dominated by the `computeDeltas()` scan over every validator, so it is flat no matter how + * many votes actually changed: ~4.6ms at 1M validators, ~9.5ms at 2M, plus ~30% if the proto-array + * has grown to a day of non-finality (measured Aug 2026 on a M3 Mac, see + * `test/perf/forkChoice/updateHead.test.ts`). Only call when the head really needs to be + * re-calculated. * * ## Specification * @@ -759,12 +763,17 @@ export class ForkChoice implements IForkChoice { // The store field `this.proposerBoostRoot` and `updateCheckpoints()` are mutated only after // `protoArray.onBlock()` succeeds const isTimely = this.isBlockTimely(block, blockDelaySec); - const isProposerBoostBlock = + const isProposerBoostCandidate = this.opts?.proposerBoost === true && isTimely && // only boost the first block we see - this.proposerBoostRoot === null && - this.isProposerBoostSameDependentRoot(this.head.blockRoot, parentRootHex); + this.proposerBoostRoot === null; + const isProposerBoostBlock = + isProposerBoostCandidate && + // Cached `this.head` may be stale especially after epoch transition. + // Need to updateHead() here to get the correct head + // This code only executes when block is timely. So minimal exposure to minor regression + this.isProposerBoostSameDependentRoot(this.updateHead().blockRoot, parentRootHex); // Candidate boost root used for protoArray.onBlock's best-child weighting. Committed to the // store only after the insertion succeeds. const proposerBoostRoot = isProposerBoostBlock ? blockRootHex : this.proposerBoostRoot;