Skip to content

Handle epoch boundary reorgs for proposer preferences #9197

Description

@lodekeeper

Problem

The proposer preferences implementation does not correctly handle epoch boundary reorgs that change the proposerLookahead for the next epoch. When the last block(s) of an epoch get reorged, the RANDAO mix changes, producing different proposer assignments for the next epoch. This can cause valid proposer preferences to be silently dropped.

Root Cause

The RANDAO seed for epoch N+1 proposers depends on the RANDAO mix from epoch N-1 (get_seed uses epoch - MIN_SEED_LOOKAHEAD - 1). Reorging any block in epoch N-1 can change this mix → different seed → different proposer assignments for epoch N+1.

The CL spec gossip conditions are sound (they validate against the current head state), but Lodestar's implementation has gaps in how it caches and evicts preferences.

Issues Found

1. Pool Keying Bug (Critical)

ProposerPreferencesPool uses Map<Slot, SignedProposerPreferences> — keyed by slot only. After a reorg where the proposer for slot S changes from validator V to validator W:

  • V's stale preferences occupy the slot key
  • W's valid preferences arrive, pass gossip validation, but the pool's add() sees has(proposalSlot) === true and returns AlreadyKnown
  • W's preferences are silently dropped

Fix: Key by (Slot, ValidatorIndex) or allow overwriting when the validatorIndex differs. When looking up preferences for bid matching, verify the entry's validatorIndex against the current head's proposerLookahead.

2. Pool/Cache Eviction on Reorg (Important)

Neither the pool nor the seen cache react to fork choice changes. After an epoch boundary reorg:

  • Stale preferences from the old proposer remain in the pool until slot-based pruning
  • The seen cache remembers the old proposer's message (no functional impact since dedup is per validatorIndex, but wastes memory)

Fix: On head change, if the dependent root for the next epoch changed:

  • Evict pool entries for next-epoch slots where stored validatorIndex no longer matches state.proposerLookahead
  • Optionally clear seen cache entries for affected slots

3. Validator Re-broadcast on Duty Change (Important)

If a validator's proposer duty for the next epoch disappears (or appears) after a reorg, the validator service must detect this and:

  • Re-broadcast preferences for newly assigned slots
  • Stop broadcasting preferences for slots it no longer proposes

This is critical for the validator client — without it, the correct post-reorg proposer may never have preferences on the network, meaning builders cannot submit valid bids for that slot. Same pattern as attestation duty dependent root tracking.

4. Downstream Bid Invalidation (Important)

Execution payload bids must match the proposer's fee_recipient and gas_limit. After a reorg changes the proposer:

  • Bids built against V's preferences are orphaned
  • The bid pool should evict bids for slots where the proposer changed

Relevant Files

  • packages/beacon-node/src/chain/opPools/proposerPreferencesPool.ts
  • packages/beacon-node/src/chain/seenCache/seenProposerPreferences.ts
  • packages/beacon-node/src/chain/validation/proposerPreferences.ts
  • packages/validator/src/services/proposerPreferences.ts

Context

  • Spec gossip conditions: specs/gloas/p2p-interface.md (proposer_preferences topic)
  • proposer_lookahead computed in process_proposer_lookahead during epoch processing
  • MIN_SEED_LOOKAHEAD = 1 → next-epoch proposers depend on current epoch boundary state

Activity

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

    spec-gloasIssues targeting the Glamsterdam spec version

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions