Skip to content

Proposer boost wrongly denied: dependent-root gate uses stale cached head across epoch-boundary ticks #9666

Description

@ensi321

Describe the bug

The proposer-boost dependent-root gate added in #9565 compares the dependent root of the incoming block against the cached this.head:

https://github.com/ChainSafe/lodestar/blob/6e4cd4e2c8dc4d1b4bfc3e35337030cf7d708060/packages/fork-choice/src/forkChoice/forkChoice.ts#L719-L724

The spec computes the head fresh inside on_block, right before inserting the block:

# Compute head before applying the block
head = get_head(store)
...
update_proposer_boost_root(store, head.root, block_root)

https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/phase0/fork-choice.md#on_block

These diverge when the tick preceding the block import crosses an epoch boundary: on_tick's pull-up can update the justified checkpoint, which changes filter_block_tree and therefore the head. Lodestar's cached this.head still points at the pre-tick branch, the dependent-root comparison is made against the wrong chain, and the boost is denied for a block the spec would boost.

Impact

The first timely block after an epoch-boundary justification change can lose its proposer boost, weakening reorg resistance for that slot. Narrow window, but it is exactly the epoch-boundary slot where fork choice is most contested.

Reproduction

Found by the fork-choice compliance suite (consensus-specs model-generated vectors, integration in progress): case block_tree_test_16_201284350_1 fails identically on every pre-gloas fork (altair→fulu, 1471/1472 each) at a check step expecting proposer_boost_root set after a block delivered at the exact start of an epoch.

Instrumented trace at the failing import: isTimely=true, proposerBoostRoot=null, but sameDepRoot=false with this.head still on the pre-tick branch. Forcing a fresh head in the gate makes the fork pass 1472/1472.

Candidate fix

Use a freshly computed head in the gate (e.g. this.updateHead() instead of this.head), which also matches the spec's ordering of vote processing relative to on_block. Needs a perf sanity-check (one findHead per block import) and should be sequenced with:

Activity

  1. ensi321 commented on Aug 25, 2026

    @ensi321
    MemberAuthor

    Closed by #9864

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions