Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
864b9e5
rough-out new BackfillSync class structure
vedant-asati Sep 7, 2025
ad89e11
wip: add peer management
vedant-asati Oct 27, 2025
9e5cd9b
wip: create, send and validate by_range requests
vedant-asati Oct 27, 2025
3606147
fix: blocks_by_range requests validation
vedant-asati Oct 28, 2025
29f36c2
remove debug comments from verify.ts
vedant-asati Oct 28, 2025
9ac4ea3
add verifyBlockProposerSignature
vedant-asati Oct 28, 2025
6e5ffa1
merge: integrate db repository updates from older feature branch
vedant-asati Oct 29, 2025
bc7919d
get it building
vedant-asati Oct 29, 2025
98b9664
integrate new backfill db repos
vedant-asati Nov 10, 2025
024de90
fix the case of single missed slot request and refactor
vedant-asati Nov 30, 2025
eb4eb5b
add backfill stopping condition
vedant-asati Dec 7, 2025
760f448
add checkBackfillStatus fn to get real db view for testing
vedant-asati Dec 8, 2025
f46e9d3
add forceCheckpointSync in SyncOptions to use in backfill class
vedant-asati Dec 8, 2025
c450b6e
rename it
vedant-asati Dec 8, 2025
2e3b1f4
handle filling up empty ranges due to forcedCheckpointSync & cleanup
vedant-asati Dec 9, 2025
488e022
remove redundant files
vedant-asati Dec 10, 2025
405b520
fix typo
vedant-asati Dec 10, 2025
3697f79
use the sleep utility from @lodestar/utils instead of custom promise
vedant-asati Dec 10, 2025
dac6120
hardcode SLOTS_PER_EPOCH=32
vedant-asati Dec 10, 2025
f33dbef
rename SLOTS_PER_EPOCH to better name BACKFILL_BATCH_SIZE
vedant-asati Dec 10, 2025
a306499
fix inconsistent backfillRange when restarting node in same epoch
vedant-asati Dec 14, 2025
a06ed20
update lastSlotRequested on failing requests to avoid re-requesting
vedant-asati Dec 14, 2025
13d427a
cleanup: remove EventEmitter inheritance from BackfillSync
vedant-asati Dec 20, 2025
bcf4894
cleanup: remove unused imports
vedant-asati Dec 20, 2025
e9054e6
fix logging
vedant-asati Dec 20, 2025
1d2431a
fix: manually handle incorrect encoding of BACKFILL_RANGE_KEY as 0xff…
vedant-asati Dec 23, 2025
49f72ea
fix: avoid skipping all peers when only few recently requested peers …
vedant-asati Dec 23, 2025
57d8447
add backfill event emitter for testing
vedant-asati Dec 23, 2025
c1842cd
feat: add e2e test for backfillSync
vedant-asati Dec 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export class ArchiveStore {
const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex);
timer?.({source: ArchiveStoreTask.ForkchoicePrune});

// Todo: Uncomment after integrating backfill db repositories
timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
await updateBackfillRange({chain: this.chain, db: this.db, logger: this.logger}, finalized);
timer?.({source: ArchiveStoreTask.UpdateBackfillRange});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {Key} from "interface-datastore";
import {KeyValue} from "@lodestar/db";
import {CheckpointWithHex} from "@lodestar/fork-choice";
import {Logger} from "@lodestar/logger";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {computeEpochAtSlot} from "@lodestar/state-transition";
import {prettyPrintIndices} from "@lodestar/utils";
import {IBeaconDb} from "../../../db/interface.js";
import {BackfillState, EpochBackfillState} from "../../../db/repositories/backfillState.ts";
import {IBeaconChain} from "../../interface.js";

// Todo: Update comments wrt BackfillRange
/**
* Backfill sync relies on verified connected ranges (which are represented as key,value
* with a verified jump from a key back to value). Since the node could have progressed
Expand All @@ -18,31 +25,71 @@ export async function updateBackfillRange(
finalized: CheckpointWithHex
): Promise<void> {
try {
// const {ancestors: finalizedCanonicalBlocks, nonAncestors: finalizedNonCanonicalBlocks} =
// chain.forkChoice.getAllAncestorAndNonAncestorBlocks(finalized.rootHex);

// Mark the sequence in backfill db from finalized block's slot till anchor slot as
// filled.
const finalizedBlockFC = chain.forkChoice.getBlockHex(finalized.rootHex);
if (finalizedBlockFC && finalizedBlockFC.slot > chain.anchorStateLatestBlockSlot) {
await db.backfilledRanges.put(finalizedBlockFC.slot, chain.anchorStateLatestBlockSlot);

// Clear previously marked sequence till anchorStateLatestBlockSlot, without
// touching backfill sync process sequence which are at
// <=anchorStateLatestBlockSlot i.e. clear >anchorStateLatestBlockSlot
// and < currentSlot
const filteredSeqs = await db.backfilledRanges.entries({
gt: chain.anchorStateLatestBlockSlot,
lt: finalizedBlockFC.slot,
const previousBackfillRange = await db.backfillRange.get();

const finalizedPostDeneb = finalized.epoch >= chain.config.DENEB_FORK_EPOCH;
const finalizedPostFulu = finalized.epoch >= chain.config.FULU_FORK_EPOCH;

if (
finalizedBlockFC &&
(finalizedBlockFC.slot > chain.anchorStateLatestBlockSlot ||
(previousBackfillRange && finalizedBlockFC.slot > previousBackfillRange?.endingEpoch * SLOTS_PER_EPOCH))
) {
await db.backfillRange.put({
beginningEpoch: computeEpochAtSlot(finalizedBlockFC.slot),
endingEpoch: previousBackfillRange?.endingEpoch || computeEpochAtSlot(chain.anchorStateLatestBlockSlot),
});
logger.debug("updated backfilledRanges", {
key: finalizedBlockFC.slot,
value: chain.anchorStateLatestBlockSlot,
// 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
Comment on lines +48 to +56
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.


// const custodyColumns = chain.custodyConfig.custodyColumns;
await db.backfillState.put(finalized.epoch, {
hasBlock: true,
// check if blobs & columns are filled in live chain
hasBlobs: finalizedPostDeneb ? true : null,
columnIndices: finalizedPostFulu ? [] : null,
});
if (filteredSeqs.length > 0) {
await db.backfilledRanges.batchDelete(filteredSeqs.map((entry) => entry.key));
logger.debug(
`Forward Sync - cleaned up backfilledRanges between ${finalizedBlockFC.slot},${chain.anchorStateLatestBlockSlot}`,
{seqs: JSON.stringify(filteredSeqs)}
);
}

// DEBUG_CODE
logger.info("Updated backfillState while migrating from hot to cold db", {
finalizedEpoch: finalized.epoch,
hasBlock: true,
hasBlobs: finalizedPostDeneb ? true : null,
columnIndices: finalizedPostFulu ? prettyPrintIndices([]) : null,
});
// DEBUG_CODE

// 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);
Comment on lines +75 to +92
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.

}
} catch (e) {
logger.error("Error updating backfilledRanges on finalization", {epoch: finalized.epoch}, e as Error);
Expand Down
10 changes: 6 additions & 4 deletions packages/beacon-node/src/db/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {CheckpointStateRepository} from "./repositories/checkpointState.js";
import {
AttesterSlashingRepository,
BLSToExecutionChangeRepository,
BackfilledRanges,
BackfillState,
BestLightClientUpdateRepository,
BlobSidecarsArchiveRepository,
BlobSidecarsRepository,
Expand All @@ -23,7 +23,7 @@ import {
SyncCommitteeWitnessRepository,
VoluntaryExitRepository,
} from "./repositories/index.js";
import {PreGenesisState, PreGenesisStateLastProcessedBlock} from "./single/index.js";
import {BackfillRange, PreGenesisState, PreGenesisStateLastProcessedBlock} from "./single/index.js";

export type BeaconDbModules = {
config: ChainForkConfig;
Expand Down Expand Up @@ -59,7 +59,8 @@ export class BeaconDb implements IBeaconDb {
syncCommittee: SyncCommitteeRepository;
syncCommitteeWitness: SyncCommitteeWitnessRepository;

backfilledRanges: BackfilledRanges;
backfillRange: BackfillRange;
backfillState: BackfillState;

constructor(
config: ChainForkConfig,
Expand Down Expand Up @@ -92,7 +93,8 @@ export class BeaconDb implements IBeaconDb {
this.syncCommittee = new SyncCommitteeRepository(config, db);
this.syncCommitteeWitness = new SyncCommitteeWitnessRepository(config, db);

this.backfilledRanges = new BackfilledRanges(config, db);
this.backfillRange = new BackfillRange(config, db);
this.backfillState = new BackfillState(config, db);
}

close(): Promise<void> {
Expand Down
7 changes: 4 additions & 3 deletions packages/beacon-node/src/db/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {CheckpointStateRepository} from "./repositories/checkpointState.js";
import {
AttesterSlashingRepository,
BLSToExecutionChangeRepository,
BackfilledRanges,
BackfillState,
BestLightClientUpdateRepository,
BlobSidecarsArchiveRepository,
BlobSidecarsRepository,
Expand All @@ -21,7 +21,7 @@ import {
SyncCommitteeWitnessRepository,
VoluntaryExitRepository,
} from "./repositories/index.js";
import {PreGenesisState, PreGenesisStateLastProcessedBlock} from "./single/index.js";
import {BackfillRange, PreGenesisState, PreGenesisStateLastProcessedBlock} from "./single/index.js";

/**
* The DB service manages the data layer of the beacon chain
Expand Down Expand Up @@ -65,7 +65,8 @@ export interface IBeaconDb {
syncCommittee: SyncCommitteeRepository;
syncCommitteeWitness: SyncCommitteeWitnessRepository;

backfilledRanges: BackfilledRanges;
backfillState: BackfillState;
backfillRange: BackfillRange;

pruneHotDb(): Promise<void>;

Expand Down
1 change: 0 additions & 1 deletion packages/beacon-node/src/db/repositories/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export {AttesterSlashingRepository} from "./attesterSlashing.js";
export {BackfilledRanges} from "./backfilledRanges.js";
export {BackfillState} from "./backfillState.js";
export {BlobSidecarsRepository} from "./blobSidecars.js";
export {BlobSidecarsArchiveRepository} from "./blobSidecarsArchive.js";
Expand Down
18 changes: 16 additions & 2 deletions packages/beacon-node/src/db/single/backfillRange.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ContainerType, Type, ValueOf} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {Db, DbReqOpts, encodeKey as _encodeKey} from "@lodestar/db";
import {Db, DbReqOpts} from "@lodestar/db";
import {ssz} from "@lodestar/types";
import {Bucket, getBucketNameByValue} from "../buckets.js";

Expand All @@ -17,6 +17,20 @@ export type BackfillRangeWrapper = ValueOf<typeof backfillRangeSSZ>;
// bucket which stores Epoch -> EpochBackfillState key value pairs
export const BACKFILL_RANGE_KEY = -1;

// Note: intToBytes(-1) incorrectly encodes to 0x0000000000000001, so we manually
// encode as 0xFFFFFFFFFFFFFFFF (two's complement big-endian representation of -1)
// This ensures no collision with epoch keys
const BACKFILL_RANGE_KEY_BYTES = new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
const BUCKET_PREFIX_LEN = 1;
const KEY_LEN = 8;

function encodeBackfillRangeKey(bucket: number): Uint8Array {
const buf = new Uint8Array(BUCKET_PREFIX_LEN + KEY_LEN);
buf[0] = bucket;
buf.set(BACKFILL_RANGE_KEY_BYTES, BUCKET_PREFIX_LEN);
return buf;
}

export class BackfillRange {
private readonly bucket: Bucket;
private readonly db: Db;
Expand All @@ -27,7 +41,7 @@ export class BackfillRange {
constructor(_config: ChainForkConfig, db: Db) {
this.db = db;
this.bucket = Bucket.backfill_state;
this.key = _encodeKey(this.bucket, BACKFILL_RANGE_KEY);
this.key = encodeBackfillRangeKey(this.bucket);
this.type = backfillRangeSSZ;
this.dbReqOpts = {bucketId: getBucketNameByValue(this.bucket)};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/metrics/metrics/lodestar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {OpSource} from "../../chain/validatorMonitor.js";
import {ExecutionPayloadStatus} from "../../execution/index.js";
import {GossipType} from "../../network/index.js";
import {CannotAcceptWorkReason, ReprocessRejectReason} from "../../network/processor/index.js";
import {BackfillSyncMethod} from "../../sync/backfill/backfill.js";
import {BackfillSyncMethod} from "../../sync/backfill/index.js";
import {PendingBlockType} from "../../sync/types.js";
import {PeerSyncType, RangeSyncType} from "../../sync/utils/remoteSyncType.js";
import {AllocSource} from "../../util/bufferPool.js";
Expand Down
Loading