Skip to content

feat(archiver): decouple calldata from blob fetching in L1 synchronizer#22472

Merged
spalladino merged 6 commits into
spl/fix-genesis-world-state-againfrom
spl/decouple-calldata-blobs
Apr 21, 2026
Merged

feat(archiver): decouple calldata from blob fetching in L1 synchronizer#22472
spalladino merged 6 commits into
spl/fix-genesis-world-state-againfrom
spl/decouple-calldata-blobs

Conversation

@spalladino

Copy link
Copy Markdown
Contributor

Motivation

When the node is a validator that already built a checkpoint locally (via addProposedBlock + setProposedCheckpoint), the blocks are already in the archiver store. Fetching blobs from the beacon chain is redundant and expensive, especially during sync. This decouples calldata and blob retrieval so we can skip blob fetching when the proposed checkpoint matches.

Approach

Split retrieveCheckpointsFromRollup into two phases: (1) fetch calldata only (header, attestations, archive root), (2) check the archiver store for a proposed checkpoint with matching header. If found, promote it to confirmed via a fast path. Otherwise, fetch blobs in parallel (same asyncPool(10, ...) concurrency as before) and store as normal.

Changes

  • archiver (l1/data_retrieval.ts): New CalldataOnlyCheckpoint type, retrieveCheckpointCalldataFromRollup (calldata-only fetch), and fetchBlobsAndBuildPublishedCheckpoint (deferred blob fetch). Existing functions left intact.
  • archiver (store/block_store.ts): New promoteProposedToCheckpointed method that reads existing blocks from store, writes a confirmed checkpoint entry with L1 metadata + attestations, and clears the proposed singleton.
  • archiver (store/kv_archiver_store.ts): Pass-through for promoteProposedToCheckpointed.
  • archiver (modules/data_store_updater.ts): New promoteProposedCheckpoint wrapper that handles validation status and tips cache refresh.
  • archiver (modules/l1_synchronizer.ts): handleCheckpoints now partitions calldata checkpoints into promote-vs-fetch-blobs, fetches blobs in parallel for non-matching ones, promotes the matched one, then merges results for validation.

Fixes A-877

@spalladino spalladino added ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure ci-draft Run CI on draft PRs. labels Apr 10, 2026
@spalladino

spalladino commented Apr 10, 2026

Copy link
Copy Markdown
Contributor Author

Got to consider race condition where a proposer pushes their proposed checkpoint to its archiver before the previous checkpoint is checkpointed on L1, but this requires a larger change on the block store.

@spalladino spalladino marked this pull request as ready for review April 13, 2026 21:33
@spalladino spalladino removed the ci-draft Run CI on draft PRs. label Apr 13, 2026
@spalladino spalladino requested a review from Maddiaa0 April 20, 2026 20:40
? undefined
: await this.store.getProposedCheckpointOnly();
const last = calldataCheckpoints[calldataCheckpoints.length - 1];
const toPromote =

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.

to Promote is only used as a boolean? The logs can just use last directly?

description: 'Skip validating checkpoint attestations (for testing purposes only)',
...booleanConfigHelper(false),
},
skipPromoteProposedCheckpointDuringL1Sync: {

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.

is it worth prefixing this option with test_

}

// And persist the promoted checkpoint if it passed validation
if (toPromote && validCheckpoints.some(c => c.checkpoint.number === toPromote.checkpointNumber)) {

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.

this .some and .find below are doing the same search. could probably extract it

const [processDuration, result] = await elapsed(() =>
execInSpan(this.tracer, 'Archiver.addCheckpoints', () =>
this.updater.addCheckpoints(validCheckpoints, updatedValidationResult),
this.updater.addCheckpoints(checkpointsToAdd, updatedValidationResult),

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.

should addCheckpoints + promoteProposed be able to be updated within the same call? Ideally these should update atomically? could have some kind of queue + flush

this.log.debug(
`Building published checkpoint from proposed ${toPromote.checkpointNumber} (skipping blob fetch)`,
);
const blocks = await this.store.getBlocks(BlockNumber(proposed.startBlock), proposed.blockCount);

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.

just realised getBlocks does not assert that blockCount blocks are returned

return await this.db.transactionAsync(async () => {
const proposed = await this.getProposedCheckpointOnly();
if (!proposed) {
throw new Error('Cannot promote proposed checkpoint: no proposed checkpoint exists');

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.

not for now but this file has a big variance in custom error / text error

@Maddiaa0 Maddiaa0 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.

my comments are mainly nits

@spalladino spalladino force-pushed the spl/decouple-calldata-blobs branch 2 times, most recently from 6e9e318 to 52250fe Compare April 21, 2026 18:34
@spalladino spalladino enabled auto-merge (squash) April 21, 2026 18:34
spalladino and others added 6 commits April 21, 2026 18:16
Splits checkpoint retrieval into two phases: first fetch calldata only
(header, attestations, archive root), then check if a proposed checkpoint
with matching header already exists in the store. If so, skip blob fetching
and promote the proposed checkpoint to confirmed. Otherwise, fetch blobs in
parallel as before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move promote to after attestation validation to avoid persisting
  invalid checkpoints (split into build + persist steps)
- Add validateCheckpoint call in promote path
- Add archive root comparison to proposed checkpoint match condition
- Remove dead retrieveCheckpointsFromRollup and processCheckpointProposedLogs
- Pass pendingChainValidationStatus to promote path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@spalladino spalladino force-pushed the spl/decouple-calldata-blobs branch from 52250fe to 20c5f7c Compare April 21, 2026 21:16
@spalladino spalladino changed the base branch from merge-train/spartan to spl/fix-genesis-world-state-again April 21, 2026 21:16
@spalladino spalladino merged commit 48236ce into spl/fix-genesis-world-state-again Apr 21, 2026
9 of 10 checks passed
@spalladino spalladino deleted the spl/decouple-calldata-blobs branch April 21, 2026 21:16
@spalladino spalladino restored the spl/decouple-calldata-blobs branch April 21, 2026 21:18
spalladino added a commit that referenced this pull request Apr 22, 2026
…er (#22716)

Reopening #22472 which was accidentally merged

## Motivation

When the node is a validator that already built a checkpoint locally
(via `addProposedBlock` + `setProposedCheckpoint`), the blocks are
already in the archiver store. Fetching blobs from the beacon chain is
redundant and expensive, especially during sync. This decouples calldata
and blob retrieval so we can skip blob fetching when the proposed
checkpoint matches.

## Approach

Split `retrieveCheckpointsFromRollup` into two phases: (1) fetch
calldata only (header, attestations, archive root), (2) check the
archiver store for a proposed checkpoint with matching header. If found,
promote it to confirmed via a fast path. Otherwise, fetch blobs in
parallel (same `asyncPool(10, ...)` concurrency as before) and store as
normal.

## Changes

- **archiver (l1/data_retrieval.ts)**: New `CalldataOnlyCheckpoint`
type, `retrieveCheckpointCalldataFromRollup` (calldata-only fetch), and
`fetchBlobsAndBuildPublishedCheckpoint` (deferred blob fetch). Existing
functions left intact.
- **archiver (store/block_store.ts)**: New
`promoteProposedToCheckpointed` method that reads existing blocks from
store, writes a confirmed checkpoint entry with L1 metadata +
attestations, and clears the proposed singleton.
- **archiver (store/kv_archiver_store.ts)**: Pass-through for
`promoteProposedToCheckpointed`.
- **archiver (modules/data_store_updater.ts)**: New
`promoteProposedCheckpoint` wrapper that handles validation status and
tips cache refresh.
- **archiver (modules/l1_synchronizer.ts)**: `handleCheckpoints` now
partitions calldata checkpoints into promote-vs-fetch-blobs, fetches
blobs in parallel for non-matching ones, promotes the matched one, then
merges results for validation.

Fixes A-877

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants