Skip to content

Conversation

@vedant-asati
Copy link
Contributor

@vedant-asati vedant-asati commented Dec 10, 2025

Motivation
This PR introduces support for backfilling blocks, enabling historical block sync, following the spec, and to be a more useful peer to the network.

Tracks: #7753

Description
This PR continues the work from PR #8353, which got little cluttereddue to an incorrect base merge, to provide a clean continuation.
It builds on the db changes already merged in PR #8085 (BackfillState & BackfillRange repos).

This adds:

  • new BackfillSync class implementation
  • peer management of eligible peers
  • create & send ReqResp requests (beacon_blocks_by_range)
  • validate responses
  • persist validated blocks to db
  • update backfill db states (epochBackfillState and backfillRange)

Future Work(separate PRs):

  • Backfill blobs, custody columns
  • Move backfill into dedicated worker thread
  • explore BroadcastChannel integration across threads

AI Assistance Disclosure
AI (Gemini/ChatGPT) was used to get reviews and trace bugs in the implementation.

@vedant-asati vedant-asati requested a review from a team as a code owner December 10, 2025 07:08
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @vedant-asati, 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 upgrades the beacon node's ability to perform historical block synchronization, making it a more robust and useful participant in the network. It introduces a new, more resilient BackfillSync implementation that efficiently fetches, validates, and persists historical blocks from peers. The changes include a refined peer selection mechanism and direct integration with dedicated database repositories to accurately track and manage the state of backfilled data, ensuring data integrity and continuity even after restarts or in the presence of network inconsistencies.

Highlights

  • New Backfill Sync Implementation: A completely re-architected BackfillSync class (BackfillSyncV2) has been introduced to manage historical block synchronization, replacing the previous implementation.
  • Enhanced Peer Management: The new backfill sync includes sophisticated peer management, tracking peer scores, request history, and response times to select optimal peers for block fetching and to handle peer disconnections and failures more robustly.
  • Database State Integration: The backfill process now directly interacts with new BackfillRange and BackfillState database repositories to persist and track the progress of historical block data, including blocks, blobs, and custody columns, ensuring data integrity and continuity.
  • Improved Block Fetching and Validation: The system can now efficiently request blocks by range (beacon_blocks_by_range), validate their sequence and proposer signatures, and handle scenarios like missed slots with retry mechanisms.
  • CLI Option for Forced Checkpoint Sync: A new CLI option forceCheckpointSync has been added, allowing users to reinitialize backfill states from a checkpoint, overriding previous backfill progress and providing more control over the sync process.
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.

Copy link
Contributor

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

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 introduces a new implementation for backfill sync, a significant feature for historical block synchronization. The changes are extensive, including a new BackfillSync class, updates to the database schema, and new peer management logic. My review has identified several areas for improvement to ensure the feature is robust and maintainable. Key issues include a critical typo in a property name, a potential bug in handling multi-epoch finalization, incomplete error handling, and several instances of throwing generic errors which can make debugging difficult. Additionally, there are opportunities to improve code quality by removing debug code, refactoring duplicated logic, and replacing magic numbers with constants. Addressing these points will greatly enhance the quality and reliability of the new backfill sync implementation.

Comment on lines +75 to +92
// Todo: verify if this function runs every epoch, else intermediate epoch backfill states will be empty.
// Below could be a possible solution to this issue.

// // In case of long unfinality, this needs to be done to save multiple epochs
// // First, find all *unique* epochs from the list of finalized blocks
// const uniqueEpochs = Array.from(new Set(finalizedCanonicalBlocks.map((block) => block.finalizedEpoch)));
// const backfillStates: KeyValue<number, EpochBackfillState>[] = uniqueEpochs.map((epoch) => {
// return {
// key: epoch,
// value: {
// hasBlock: true,
// // check if blobs & columns are filled in live chain
// hasBlobs: finalizedPostDeneb ? true : null,
// columnIndices: finalizedPostFulu ? [] : null,
// },
// };
// });
// await db.backfillState.batchPut(backfillStates);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The Todo comment on line 75 highlights a potential issue: if finalization spans multiple epochs, this function might not be called for every epoch, which could lead to gaps in the backfillState. The current implementation only updates the state for finalized.epoch. The commented-out code block below it provides a good solution by iterating over all finalized blocks to update the state for each unique epoch. This logic should be implemented to ensure correctness and prevent gaps in the backfill state.

continue;
}
if (!validationRes.nextAnchor) {
throw Error;
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Throwing a generic Error object without a descriptive message makes debugging difficult. This occurs here and in other places in this file (e.g., lines 595, 604, 809, 910). A more specific error with a helpful message should be thrown.

Suggested change
throw Error;
throw new Error("Validation failed to produce a next anchor");

Comment on lines +457 to +482
// Todo
// if (error instanceof BackfillSyncError) {
// switch (error.type.code) {
// // case BackfillSyncErrorCode.INTERNAL_ERROR:
// // // Break it out of the loop and throw error
// // this.status = BackfillSyncStatus.aborted;
// // break;
// // case BackfillSyncErrorCode.NOT_ANCHORED:
// // // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
// // case BackfillSyncErrorCode.NOT_LINEAR:
// // // Lets try to jump directly to the parent of this anchorBlock as previous
// // // (segment) of blocks could be orphaned/missed
// // if (this.syncAnchor.anchorBlock) {
// // this.syncAnchor = {
// // anchorBlock: null,
// // anchorBlockRoot: this.syncAnchor.anchorBlock.message.parentRoot,
// // anchorSlot: null,
// // lastBackSyncedBlock: this.syncAnchor.lastBackSyncedBlock,
// // };
// // }

// // // falls through
// case BackfillSyncErrorCode.INVALID_SIGNATURE:
// this.network.reportPeer("goodPeer", PeerAction.LowToleranceError, "BadSyncBlocks");
// }
// }
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The catch block for the main sync logic contains a large commented-out section for handling specific BackfillSyncError types. The current implementation only logs the error. This commented-out logic, which includes actions like reporting a misbehaving peer, should be implemented to make the backfill sync more robust.

Comment on lines +48 to +56
// DEBUG_CODE
logger.info("Updated backfillRange while migrating from hot to cold db", {
beginningEpoch: computeEpochAtSlot(finalizedBlockFC.slot),
endingEpoch: previousBackfillRange?.endingEpoch || computeEpochAtSlot(chain.anchorStateLatestBlockSlot),
previousBackfillRangeBeginningEpoch: previousBackfillRange?.beginningEpoch,
previousBackfillRangeEndingEpoch: previousBackfillRange?.endingEpoch,
chainAnchorStateLatestBlockSlotEpoch: computeEpochAtSlot(chain.anchorStateLatestBlockSlot),
});
// DEBUG_CODE
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This DEBUG_CODE block, along with the one at lines 66-73, appears to be for debugging. This verbose logging should be removed before merging to keep the codebase clean and avoid excessive noise in production logs. Similar debug logging is present in other new files in this PR and should also be removed.

Comment on lines 238 to 273
modules.logger.error("Invalid prevBackfillRange in db. Reinitializing backfill states using anchorState.");
// use anchor from modules
const {checkpoint: anchorCp} = computeAnchorCheckpoint(config, anchorState);
const anchorBlockParentRoot = anchorState.latestBlockHeader.toValue().parentRoot;
const anchorSlot = anchorState.latestBlockHeader.slot;
syncAnchor = {
anchorBlockParentRoot,
anchorBlock: null,
anchorBlockRoot: anchorCp.root, // this may help
anchorSlot,
lastBackSyncedBlock: null,
};
// Initialize backfill states to maintain point of reference for future
await modules.db.backfillRange.put({beginningEpoch: anchorCp.epoch, endingEpoch: anchorCp.epoch});
await modules.db.backfillState.put(anchorCp.epoch, {hasBlock: true, hasBlobs: true, columnIndices: []});
}
} else {
// Todo: Remove this duplicate code.
if (isForcedCheckpointSync)
modules.logger.warn("ForcedCheckpointSync. Initializing backfill states using anchorState(checkpointState).");
else modules.logger.warn("prevBackfillRange absent in db. Initializing backfill states using anchorState.");
// use anchor from modules
const {checkpoint: anchorCp} = computeAnchorCheckpoint(config, anchorState);
const anchorBlockParentRoot = anchorState.latestBlockHeader.toValue().parentRoot;
const anchorSlot = anchorState.latestBlockHeader.slot;
syncAnchor = {
anchorBlockParentRoot,
anchorBlock: null,
anchorBlockRoot: anchorCp.root, // this may help
anchorSlot,
lastBackSyncedBlock: null,
};
// Initialize backfill states to maintain point of reference for future
await modules.db.backfillRange.put({beginningEpoch: anchorCp.epoch, endingEpoch: anchorCp.epoch});
await modules.db.backfillState.put(anchorCp.epoch, {hasBlock: true, hasBlobs: true, columnIndices: []});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

There is significant code duplication between the else block starting at line 236 and the one at line 254. Both blocks handle the initialization of syncAnchor and backfill states when starting from the anchorState. This logic should be extracted into a helper function to reduce duplication and improve maintainability.

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.

1 participant