ChainSafe / ChainSafe/lodestar
Handle epoch boundary reorgs for proposer preferences
- Dominant language
- TypeScript
- Stars
- 1.4k
- Forks
- 483
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 150
Description
## 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` — 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
Contributor guide
Research direction
Start with packages/beacon-node/src/chain/opPools/proposerPreferencesPool.ts and packages/beacon-node/src/chain/seenCache/seenProposerPreferences.ts, then trace fork-choice head changes and process_proposer_lookahead. Review packages/beacon-node/src/chain/validation/proposerPreferences.ts and packages/validator/src/services/proposerPreferences.ts for duty updates. Done means epoch-boundary reorgs replace or evict stale preferences, update validator broadcasts, and invalidate affected bids.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- blockchain, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100