Skip to content

chore: fork choice stores checkpoints with payload status - #8845

Merged
ensi321 merged 1 commit into
nc/epbs-fcfrom
nc/checkpoint-with-payload
Feb 3, 2026
Merged

chore: fork choice stores checkpoints with payload status#8845
ensi321 merged 1 commit into
nc/epbs-fcfrom
nc/checkpoint-with-payload

Conversation

@ensi321

@ensi321 ensi321 commented Feb 3, 2026

Copy link
Copy Markdown
Member

Summary of Changes

Overview

Updated fork choice to store and use payload status for both finalized and justified checkpoints. This ensures getFinalizedBlock() and getJustifiedBlock() return the correct block variant (EMPTY or FULL) based on checkpoint state.

1. Added CheckpointWithPayload Type

New type in store.ts:

export type CheckpointWithPayload = CheckpointWithHex & {payloadStatus: PayloadStatus};

This type extends CheckpointWithHex with a payloadStatus field to track whether the checkpoint uses EMPTY or FULL block variant.

2. Updated ForkChoiceStore

Constructor changes:

  • Now takes justifiedPayloadStatus and finalizedPayloadStatus parameters
  • Stores both finalized and justified checkpoints as CheckpointWithPayload

Interface changes:

  • finalizedCheckpoint and unrealizedFinalizedCheckpointCheckpointWithPayload
  • justified and unrealizedJustified → use CheckpointWithPayload (via renamed types)

3. Renamed Balance Types

  • CheckpointHexWithBalanceCheckpointWithPayloadAndBalance
  • CheckpointHexWithTotalBalanceCheckpointWithPayloadAndTotalBalance

Both now use CheckpointWithPayload instead of CheckpointWithHex.

4. Added getCheckpointPayloadStatus() Helper

Purpose: Determines payload status for a checkpoint by checking state.executionPayloadAvailability

Logic:

  • Pre-Gloas: always returns FULL
  • Gloas: checks state.executionPayloadAvailability at checkpoint slot

Signature:

export function getCheckpointPayloadStatus(
  state: CachedBeaconStateAllForks,
  checkpointEpoch: number
): PayloadStatus

5. Updated onBlock() Processing

For justified checkpoint:

  • Calls getCheckpointPayloadStatus() to compute payload status
  • Creates CheckpointWithPayload with computed status
  • Updates both realized and unrealized justified checkpoints

For finalized checkpoint:

  • Calls getCheckpointPayloadStatus() to compute payload status
  • Creates CheckpointWithPayload with computed status
  • Updates both realized and unrealized finalized checkpoints

6. Updated Fork Choice Methods

getFinalizedBlock():

  • Now uses this.fcStore.finalizedCheckpoint.payloadStatus instead of always using PayloadStatus.FULL

getJustifiedBlock():

  • Now uses this.fcStore.justified.checkpoint.payloadStatus instead of always using PayloadStatus.FULL

getFinalizedCheckpoint() and getJustifiedCheckpoint():

  • Return type changed to CheckpointWithPayload

7. Updated Initialization

initializeForkChoiceFromFinalizedState():

  • Computes justifiedPayloadStatus using getCheckpointPayloadStatus()
  • Computes finalizedPayloadStatus using getCheckpointPayloadStatus()
  • Passes both to ForkChoiceStore constructor

initializeForkChoiceFromUnfinalizedState():

  • Computes payload status for both justified and finalized checkpoints
  • Passes both to ForkChoiceStore constructor

@ensi321
ensi321 requested a review from a team as a code owner February 3, 2026 02:54
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @ensi321, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the fork choice mechanism by integrating payload status directly into checkpoint management. By storing and utilizing the payload status (EMPTY or FULL) for justified and finalized checkpoints, the system can accurately determine the correct block variant, especially critical for the Gloas fork. This ensures consistency and correctness in block retrieval methods.

Highlights

  • New Type CheckpointWithPayload Introduced: A new type, CheckpointWithPayload, has been added which extends CheckpointWithHex to include a payloadStatus field. This field tracks whether a checkpoint corresponds to an EMPTY or FULL block variant, crucial for post-Gloas forks.
  • ForkChoiceStore Enhancements: The ForkChoiceStore constructor now accepts justifiedPayloadStatus and finalizedPayloadStatus parameters. Both finalized and justified checkpoints are now stored as CheckpointWithPayload instances, and related interface types have been updated accordingly.
  • Balance Type Renaming: Existing balance-related types CheckpointHexWithBalance and CheckpointHexWithTotalBalance have been renamed to CheckpointWithPayloadAndBalance and CheckpointWithPayloadAndTotalBalance respectively, reflecting their updated dependency on CheckpointWithPayload.
  • New Helper Function getCheckpointPayloadStatus(): A new utility function, getCheckpointPayloadStatus(), has been implemented. This function determines the payload status for a given checkpoint: it always returns FULL for pre-Gloas forks and checks state.executionPayloadAvailability for Gloas forks.
  • Updated onBlock() Processing: The onBlock() method now computes the appropriate payloadStatus for both justified and finalized checkpoints using the new getCheckpointPayloadStatus() helper. These checkpoints are then created and updated as CheckpointWithPayload.
  • Fork Choice Method Adjustments: The getFinalizedBlock() and getJustifiedBlock() methods now directly utilize the payloadStatus stored within their respective checkpoints, removing previous conditional logic. Additionally, getFinalizedCheckpoint() and getJustifiedCheckpoint() now return CheckpointWithPayload.
  • Initialization Process Updates: Both initializeForkChoiceFromFinalizedState() and initializeForkChoiceFromUnfinalizedState() functions have been modified to compute and pass the correct justifiedPayloadStatus and finalizedPayloadStatus to the ForkChoiceStore constructor during initialization.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • packages/beacon-node/src/chain/emitter.ts
    • Updated imports to use CheckpointWithPayload instead of CheckpointWithHex.
    • Modified IChainEvents interface to reflect forkChoiceJustified and forkChoiceFinalized events now emit CheckpointWithPayload.
  • packages/beacon-node/src/chain/forkChoice/index.ts
    • Imported getCheckpointPayloadStatus.
    • Modified initializeForkChoiceFromFinalizedState to calculate and pass justifiedPayloadStatus and finalizedPayloadStatus to the ForkChoiceStore constructor.
    • Modified initializeForkChoiceFromUnfinalizedState to calculate and pass justifiedPayloadStatus and finalizedPayloadStatus to the ForkChoiceStore constructor.
  • packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts
    • Imported PayloadStatus.
    • Updated mock IForkChoiceStore instances to include payloadStatus: PayloadStatus.FULL for all checkpoint types.
  • packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts
    • Imported PayloadStatus.
    • Updated forkChoice.getFinalizedCheckpoint.mockReturnValue to include payloadStatus: PayloadStatus.FULL.
  • packages/beacon-node/test/unit/chain/seenCache/seenBlockInput.test.ts
    • Imported PayloadStatus.
    • Updated CheckpointWithHex objects in tests to include payloadStatus: PayloadStatus.FULL.
  • packages/beacon-node/test/unit/chain/validation/block.test.ts
    • Imported PayloadStatus.
    • Updated forkChoice.getFinalizedCheckpoint.mockReturnValue to include payloadStatus: PayloadStatus.FULL.
  • packages/fork-choice/src/forkChoice/forkChoice.ts
    • Imported CachedBeaconStateGloas and ForkSeq.
    • Changed import toCheckpointWithHex to toCheckpointWithPayload.
    • Updated return types of getFinalizedCheckpoint() and getJustifiedCheckpoint() to CheckpointWithPayload.
    • Modified onBlock() processing to calculate and use payloadStatus for justified and finalized checkpoints via getCheckpointPayloadStatus and toCheckpointWithPayload.
    • Updated getJustifiedBlock() and getFinalizedBlock() to retrieve payloadStatus directly from the stored checkpoint.
    • Adjusted updateCheckpoints and updateUnrealizedCheckpoints private methods to accept CheckpointWithPayload.
    • Added getCheckpointPayloadStatus function to determine payload status based on the state and checkpoint epoch.
  • packages/fork-choice/src/forkChoice/interface.ts
    • Imported CheckpointWithPayload.
    • Renamed CheckpointHexWithBalance to CheckpointWithPayloadAndBalance and updated its checkpoint type to CheckpointWithPayload.
    • Renamed CheckpointHexWithTotalBalance to CheckpointWithPayloadAndTotalBalance and updated its base type.
  • packages/fork-choice/src/forkChoice/store.ts
    • Imported PayloadStatus.
    • Defined new type CheckpointWithPayload extending CheckpointWithHex with a payloadStatus field.
    • Updated IForkChoiceStore interface to use CheckpointWithPayloadAndTotalBalance, CheckpointWithPayloadAndBalance, and CheckpointWithPayload.
    • Modified ForkChoiceStore class to use the new CheckpointWithPayload types for its internal checkpoint storage.
    • Updated ForkChoiceStore constructor to accept justifiedPayloadStatus and finalizedPayloadStatus and use toCheckpointWithPayload.
    • Adjusted set justified and set finalizedCheckpoint methods to handle CheckpointWithPayload.
    • Added toCheckpointWithPayload helper function.
  • packages/fork-choice/src/index.ts
    • Exported getCheckpointPayloadStatus from forkChoice.ts.
    • Exported CheckpointWithPayloadAndBalance and CheckpointWithPayloadAndTotalBalance from interface.ts.
    • Exported CheckpointWithPayload from store.ts.
  • packages/fork-choice/test/perf/forkChoice/util.ts
    • Updated mock IForkChoiceStore instances to include payloadStatus: PayloadStatus.FULL for all checkpoint types.
  • packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts
    • Updated mock IForkChoiceStore instances to include payloadStatus: PayloadStatus.FULL for all checkpoint types.
  • packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts
    • Updated mock IForkChoiceStore instances to include payloadStatus: PayloadStatus.FULL for all checkpoint types.
  • packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts
    • Updated mock IForkChoiceStore instances to include payloadStatus: PayloadStatus.FULL for all checkpoint types.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully updates the fork choice mechanism to store and use payload status for checkpoints, which is a crucial improvement for correctness, particularly with the Gloas fork. The introduction of CheckpointWithPayload and the getCheckpointPayloadStatus helper function are well-implemented, and the changes are consistently applied throughout the codebase, including the test files. I have one suggestion to refactor a section of the code to enhance maintainability by reducing duplication. Overall, this is a solid and well-executed change.

@twoeths twoeths left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me

@ensi321
ensi321 merged commit f9a1253 into nc/epbs-fc Feb 3, 2026
14 of 16 checks passed
@ensi321
ensi321 deleted the nc/checkpoint-with-payload branch February 3, 2026 04:08
@codecov

codecov Bot commented Feb 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.40%. Comparing base (8859b5b) to head (bb289f2).
⚠️ Report is 2 commits behind head on nc/epbs-fc.

Additional details and impacted files
@@              Coverage Diff               @@
##           nc/epbs-fc    #8845      +/-   ##
==============================================
+ Coverage       52.39%   52.40%   +0.01%     
==============================================
  Files             848      848              
  Lines           63489    63464      -25     
  Branches         4689     4689              
==============================================
- Hits            33263    33259       -4     
+ Misses          30158    30137      -21     
  Partials           68       68              
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

twoeths added a commit that referenced this pull request Mar 19, 2026
## Summary

This PR extends the **Gloas ePBS state cache architecture** to support
dual state variants (block state and payload state) by threading the
`payloadPresent` flag through the state cache layer, regeneration
system, and archive store.

## Context

Building on the fork choice changes in `nc/epbs-fc` (which stores
checkpoints with payload status), this PR completes the state cache
implementation for Gloas ePBS by:
1. Extending all checkpoint cache operations to track `payloadPresent`
2. Updating the regeneration layer to explicitly handle both state
variants
3. Propagating payload status from block import through to checkpoint
caching

## Key Changes

### 1. State Cache Type System Updates

**`packages/beacon-node/src/chain/stateCache/types.ts`** (+208/-111
lines total across files):
- Renamed `CheckpointHex` → `CheckpointHexPayload` with required
`payloadPresent: boolean` field
- Updated `CheckpointStateCache` interface methods to accept
`payloadPresent` parameter:
- `add(cp, state, payloadPresent)` - explicitly marks state variant when
adding to cache
- `getLatest(rootHex, maxEpoch, payloadPresent)` - retrieves specific
state variant
- `getOrReloadLatest(rootHex, maxEpoch, payloadPresent)` - reloads
specific state variant from disk
- `updatePreComputedCheckpoint(rootHex, epoch, payloadPresent)` - tracks
payload status for pre-computed states
- Kept `processState()` method signature unchanged (manages both
variants internally)

### 2. PersistentCheckpointStateCache Implementation


**`packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts`**
(~289 lines modified):
- Extended cache key format from `"epoch-rootHex"` to
`"epoch-rootHex-payloadPresent"`
- Updated `toCheckpointHexPayload()` helper to include payload status in
keys
- Modified all cache operations (add, get, getLatest, etc.) to handle
dual state variants
- Implemented logic to iterate both `payloadPresent` variants in
`processPastEpoch()` for memory management
- Updated cache metrics and debugging utilities to reflect dual state
architecture

### 3. Regeneration Layer - Dual State Support

**`packages/beacon-node/src/chain/regen/interface.ts`** &
**`queued.ts`** & **`regen.ts`**:
- Added `processPayloadState(payloadState)` method for explicit payload
state caching (Gloas-only)
- Called after `processExecutionPayloadEnvelope()` when payload is
revealed
  - Complements `processState()` which handles block state caching
- Updated `addCheckpointState(cp, state, payloadPresent)` to accept
payload flag
- Extended `updatePreComputedCheckpoint()` with `payloadPresent`
parameter
- Modified `getCheckpointState()` and related methods to pass
`payloadPresent` through cache lookups

### 4. Block Import - Payload Status Propagation

**`packages/beacon-node/src/chain/blocks/importBlock.ts`**:
- Derive `payloadPresent` from block type:
- **Pre-Gloas**: `payloadPresent = true` (execution payload embedded in
block, always FULL variant)
- **Post-Gloas**: `payloadPresent = false` (block state only,
PENDING/EMPTY variant, payload not yet revealed)
- Thread `payloadPresent` through checkpoint caching operations:
  - `regen.addCheckpointState(cp, checkpointState, payloadPresent)`

### 5. Archive Store - Historical State Management

**`packages/beacon-node/src/chain/archiveStore/`**:
- Updated `archiveStore.archiveState()` to accept `payloadPresent`
parameter
- Modified archival strategies (`frequencyStateArchiveStrategy`) to
propagate payload status
- Ensured historical states maintain proper metadata for state variant
tracking

### 6. API & Validation Layer Updates

**`packages/beacon-node/src/api/impl/`**:
- Updated validator API to retrieve correct state variant with
`payloadPresent` flag
- Modified beacon state utilities to handle payload-aware checkpoint
lookups
- Ensured API endpoints return appropriate state variant based on block
type

## Technical Details

### Checkpoint Key Format (Post-Gloas)

```typescript
// Pre-Gloas (always single variant):
"100-0x1234abcd"  // epoch-rootHex

// Post-Gloas (dual variants):
"100-0x1234abcd-false"  // epoch-rootHex-payloadPresent (block state)
"100-0x1234abcd-true"   // epoch-rootHex-payloadPresent (payload state)
```

### State Variant Semantics

- **Block State** (`payloadPresent = false`): State after processing
beacon block, before execution payload
  - Only exists for Gloas blocks (PENDING/EMPTY variants)

- **Payload State** (`payloadPresent = true`): State after processing
execution payload
  - Exists for all pre-Gloas blocks (single variant)
  - Exists for Gloas blocks after payload revelation

## Migration & Compatibility

- **Pre-Gloas blocks**: All state cache operations default to
`payloadPresent = true`
- **Backward compatibility**: Checkpoint cache can handle mix of
pre-Gloas and post-Gloas states
- **Cache key migration**: Existing cache entries remain valid (treated
as `payloadPresent = true`)

## Depends On

- `nc/epbs-fc` - Fork choice stores checkpoints with payload status
(#8845)

---

**AI Disclosure**: This PR was written primarily by Claude Code.

---------

Co-authored-by: twoeths <10568965+twoeths@users.noreply.github.com>
Co-authored-by: Tuyen Nguyen <twoeths@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cayman <caymannava@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants