ChainSafe / ChainSafe/lodestar
Fix payload status of anchor block
- Dominant language
- TypeScript
- Stars
- 1.4k
- Forks
- 483
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 150
Description
### Problem description
Problem
Both fork choice initialization paths hardcode the anchor block's payload status for post-gloas states:
- packages/beacon-node/src/chain/forkChoice/index.ts:162 (initializeForkChoiceFromFinalizedState)
- packages/beacon-node/src/chain/forkChoice/index.ts:266 (initializeForkChoiceFromUnfinalizedState)
payloadStatus: isForkPostGloas ? PayloadStatus.PENDING : PayloadStatus.FULL,
For a post-gloas anchor state — i.e. every checkpoint-synced node after the gloas fork — PENDING is incorrect. The anchor is finalized (or at minimum fully processed); whether its payload was revealed is settled and derivable from the state itself:
state.latest_execution_payload_bid.block_hash == state.latest_block_hash → FULL
otherwise → EMPTY
This is the same predicate as is_parent_block_full (https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/gloas/beacon-chain.md#is_parent_block_full), already implemented as isParentBlockFull in packages/state-transition/src/block/processParentExecutionPayload.ts.
Impact
- A PENDING anchor implies its envelope may still arrive; downstream head selection and attestation logic treat PENDING nodes differently from FULL/EMPTY ones, so a checkpoint-synced node starts fork choice from a state that misrepresents the anchor.
- The alpha.12 fork choice compliance vectors include gloas anchor states, so this plausibly contributes to the gloas compliance-suite Invalid head divergences.
### Solution description
Proposed fix
Derive the anchor payload status from the anchor state at both init sites using the isParentBlockFull predicate (export it or inline the block-hash comparison), keeping PayloadStatus.FULL pre-gloas. Add unit coverage for both init paths with FULL and EMPTY gloas anchor states.
History
PR #8987 attempted this (March 2026) but was pushed incomplete — it references a getCheckpointPayloadStatus helper that is never defined — and the surrounding code has since been refactored (#9259, #9257). Close it in favor of a fresh PR; this issue supersedes it.
### Additional context
_No response_
Contributor guide
Research direction
Start in packages/beacon-node/src/chain/forkChoice/index.ts at initializeForkChoiceFromFinalizedState and initializeForkChoiceFromUnfinalizedState, then inspect isParentBlockFull in packages/state-transition/src/block/processParentExecutionPayload.ts. Add unit coverage for FULL and EMPTY post-gloas anchor states in both initialization paths while preserving FULL for pre-gloas states; done means the anchor status is derived from the state rather than set to PENDING.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- blockchain
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100