feat: state cache for ePBS - #8868
Conversation
Summary of ChangesHello @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 significantly refactors the beacon node's state caching mechanism to accommodate the requirements of ePBS. By introducing a Highlights
🧠 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
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
This pull request introduces significant refactoring to the state caching mechanism to support ePBS, where a block can have two distinct states: one with a payload and one without. The core of the changes is the introduction of a payloadPresent flag to differentiate these states, which is propagated through various types and functions related to state management. Key updates include modifying the in-memory cache key to incorporate this flag, adjusting state retrieval logic to handle both variants, and dynamically increasing cache sizes for the Gloas fork. The implementation is largely consistent and well-structured. However, I've identified a critical issue in the persistence logic where the datastore key for checkpoint states does not distinguish between the two state variants, potentially leading to data loss when both variants are persisted for the same block. This needs to be addressed to ensure the integrity of the state cache.
3d88a87 to
26595a7
Compare
Performance Report✔️ no performance regression detected Full benchmark results
|
There was a problem hiding this comment.
💡 Codex Review
readKeys() now filters strictly to 84-char filenames, so legacy 82-char checkpoint files (old 40-byte key format) are silently ignored. That breaks upgrade compatibility for nodes using the file CP datastore because previously persisted checkpoint states are no longer discoverable by readLatestSafe(), despite compatibility handling existing on the DB-key deserialization path.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
relevant discussion on discord here, seems like we can't properly serve states yet, well at least on the epbs-devnet-0 branch, not sure if that's still the case with this PR, or if we need further tweaks after this |
…dStatus Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
it's mostly fixed now on our |
…tracking (#9019) Replace `Set<boolean>` with a numeric bitmask in the epochIndex of PersistentCheckpointStateCache. Since payloadPresent is boolean, the Set could only ever hold {true}, {false}, or {true, false} — at most 2 elements. A full Set object with hash table internals is significant overhead for tracking 1-2 bits per root per epoch. The bitmask uses PayloadAvailability (NOT_PRESENT=1, PRESENT=2) as bit flags with standard bitwise ops: OR to set, AND to check, AND-NOT to clear. This eliminates one Set allocation per root while keeping the optimization entirely internal to the class — no public API changes. Follow-up of #9006 (comment) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
| meta: ExecutionOptimisticFinalizedAndVersionCodec, | ||
| }, | ||
| }, | ||
| // TODO GLOAS: this endpoint needs to be updated because post-gloas there could be two variants of the persisted checkpoint state (empty or full). |
There was a problem hiding this comment.
more of a question, do we actually wanna persist full states? since for checkpoint sync we always wanna use post block state (without payload applied) what's the use case for this?
instead of storing 2 states we could also just load post block state and apply payload if needed
There was a problem hiding this comment.
yes that could be a good improvement
right now in in checkpoint state cache it has no context of payload
whenever it goes out of memory windows (of 3 epochs), it persists whatever it has
the down side of not persisting payload state is when we have a finalized checkpoint of > 3 epochs ago, we may not have the state, we have to load post block Uint8Array state, deserialize and apply payload
| * For Gloas (ePBS), each block can have two states: block state and payload state. | ||
| * Double the cache size to maintain the same effective block depth. | ||
| */ | ||
| export const DEFAULT_MAX_BLOCK_STATES_GLOAS = 128; |
There was a problem hiding this comment.
something feels off to me here bu would have to explore this more myself, I don't think it's necessary to store payload state, where do we use payload state if we have post block state of the next slot?
There was a problem hiding this comment.
whenever we run state-transition with block/payload, we need to store BeaconState in this cache
in forky condition, next blocks may build on either block or payload of this block
and we need to be able to get BeaconState from this cache given a state root, to run the state-transition later
There was a problem hiding this comment.
right, that makes sense, we could still only cache block state and apply the payload but there are definitely trade-offs of each approach
in the happy case we need the state with the payload applied so it makes sense to cache it
| envelopeEntries.push({key: blocks[i].slot, value: bytes}); | ||
| migratedRoots.push(blocks[i].root); | ||
| } else { | ||
| logger.debug("Payload in forkchoice but missing in db", {slot: blocks[i].slot, root: toRootHex(blocks[i].root)}); |
There was a problem hiding this comment.
If we have a FULL node in fork choice but we don't have payload in db.executionPayloadEnvelope, it sounds like an error not debug to me?
There was a problem hiding this comment.
we have a convention to make it debug to improve UX, users don't care this much
There was a problem hiding this comment.
agree, this doesn't need to be logged at error, there isn't much a user can do in this case, it's also an invariant that should not happen
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## unstable #8868 +/- ##
============================================
- Coverage 52.32% 52.27% -0.05%
============================================
Files 848 848
Lines 62326 62175 -151
Branches 4572 4545 -27
============================================
- Hits 32612 32505 -107
+ Misses 29649 29605 -44
Partials 65 65 🚀 New features to boost your workflow:
|
|
🎉 This PR is included in v1.42.0 🎉 |
Summary
This PR extends the Gloas ePBS state cache architecture to support dual state variants (block state and payload state) by threading the
payloadPresentflag 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:payloadPresentKey Changes
1. State Cache Type System Updates
packages/beacon-node/src/chain/stateCache/types.ts(+208/-111 lines total across files):CheckpointHex→CheckpointHexPayloadwith requiredpayloadPresent: booleanfieldCheckpointStateCacheinterface methods to acceptpayloadPresentparameter:add(cp, state, payloadPresent)- explicitly marks state variant when adding to cachegetLatest(rootHex, maxEpoch, payloadPresent)- retrieves specific state variantgetOrReloadLatest(rootHex, maxEpoch, payloadPresent)- reloads specific state variant from diskupdatePreComputedCheckpoint(rootHex, epoch, payloadPresent)- tracks payload status for pre-computed statesprocessState()method signature unchanged (manages both variants internally)2. PersistentCheckpointStateCache Implementation
packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts(~289 lines modified):"epoch-rootHex"to"epoch-rootHex-payloadPresent"toCheckpointHexPayload()helper to include payload status in keyspayloadPresentvariants inprocessPastEpoch()for memory management3. Regeneration Layer - Dual State Support
packages/beacon-node/src/chain/regen/interface.ts&queued.ts®en.ts:processPayloadState(payloadState)method for explicit payload state caching (Gloas-only)processExecutionPayloadEnvelope()when payload is revealedprocessState()which handles block state cachingaddCheckpointState(cp, state, payloadPresent)to accept payload flagupdatePreComputedCheckpoint()withpayloadPresentparametergetCheckpointState()and related methods to passpayloadPresentthrough cache lookups4. Block Import - Payload Status Propagation
packages/beacon-node/src/chain/blocks/importBlock.ts:payloadPresentfrom block type:payloadPresent = true(execution payload embedded in block, always FULL variant)payloadPresent = false(block state only, PENDING/EMPTY variant, payload not yet revealed)payloadPresentthrough checkpoint caching operations:regen.addCheckpointState(cp, checkpointState, payloadPresent)5. Archive Store - Historical State Management
packages/beacon-node/src/chain/archiveStore/:archiveStore.archiveState()to acceptpayloadPresentparameterfrequencyStateArchiveStrategy) to propagate payload status6. API & Validation Layer Updates
packages/beacon-node/src/api/impl/:payloadPresentflagTechnical Details
Checkpoint Key Format (Post-Gloas)
State Variant Semantics
Block State (
payloadPresent = false): State after processing beacon block, before execution payloadPayload State (
payloadPresent = true): State after processing execution payloadMigration & Compatibility
payloadPresent = truepayloadPresent = true)Depends On
nc/epbs-fc- Fork choice stores checkpoints with payload status (chore: fork choice stores checkpoints with payload status #8845)AI Disclosure: This PR was written primarily by Claude Code.