Skip to content

refactor(archiver)!: data-store, block API, and genesis cleanup#22891

Closed
spalladino wants to merge 3 commits into
merge-train/spartanfrom
spl/archiver-refactors-getblocks
Closed

refactor(archiver)!: data-store, block API, and genesis cleanup#22891
spalladino wants to merge 3 commits into
merge-train/spartanfrom
spl/archiver-refactors-getblocks

Conversation

@spalladino

Copy link
Copy Markdown
Contributor

Bundles three stacked archiver/node refactor PRs into a single PR for ease of merging into merge-train/spartan. Each commit is the rebased, squashed equivalent of one of the original PRs.

Reviewers should review each commit individually — they were originally separate PRs and the changes within each are tightly scoped.

Commits

1. refactor(archiver): remove KVArchiverDataStore pass-through#22818

Replaces the KVArchiverDataStore wrapper with a plain ArchiverDataStores bundle that exposes substores directly. Cross-store helpers move to free functions; FunctionNamesCache and ArchiverContractDataSourceAdapter become named classes; per-substore tests split out from the monolithic kv_archiver_store.test.ts.

2. refactor(node-rpc)!: simplify L2BlockSource block lookups#22809

Consolidates L2BlockSource from ~17 narrow methods returning ~9 shapes down to 4 query-object methods returning 2 shapes (L2Block / BlockData). Deletes CheckpointedL2Block and BlockDataWithCheckpointContext; AztecNode.getCheckpointedBlocks element type goes CheckpointedL2Block[]BlockResponse[] (wire-level breaking change). On-disk format unchanged.

3. refactor: centralize block-zero handling in archiver#22870

Threads the dynamic initial block header from NativeWorldStateService through the archiver. The archiver now returns a synthetic L2Block.empty(initialHeader) for genesis single-block queries; consumers (aztec-node, prover-node, p2p, sequencer, PXE, sentinel) drop their genesis special-casing and use the dynamic genesis hash uniformly. Eliminates divergence between worldState.getInitialHeader().hash() callers and GENESIS_BLOCK_HEADER_HASH callers.

Reviewing the originals

For convenience, reviewers who prefer the per-PR view can read the individual stacked PRs:

The originals will be closed once this PR merges.

Rebase notes

Only one rebase fixup vs. merge-train/spartan was needed: epochs_high_tps_block_building.test.ts had a new assertion added on merge-train/spartan (in #22846) that used the old b.block.body shape — migrated to b.body! to match the new BlockResponse type from commit 2.

Replace the KVArchiverDataStore wrapper with a plain ArchiverDataStores
bundle exposing substores directly. Cross-store helpers move to free
functions. Function-name caching becomes its own class. The
ContractDataSource adapter is now a named class.

- archiver: delete KVArchiverDataStore; add ArchiverDataStores bundle
  (blocks/logs/messages/contractClasses/contractInstances/db/function-name
  cache) and createArchiverDataStores. getArchiverSynchPoint and
  backupArchiverDataStores move to free functions.
- archiver (function names): extract FunctionNamesCache class.
- archiver (contract data source): extract ArchiverContractDataSourceAdapter
  class; createContractDataSource is a thin factory.
- archiver (substores): BlockStore.getBlocks/getCheckpointedBlocks/
  getBlockHeaders return arrays (iterator variants renamed iterate*);
  contract substores expose batch add*/delete* helpers.
- archiver (tests): split kv_archiver_store.test.ts into per-substore
  test files.
- node-lib, prover-node, txe, validator-client, end-to-end: update
  call sites to reach for the relevant substore directly.
Consolidate the block-related lookup surface on L2BlockSource from
~17 narrow methods returning ~9 different shapes down to 4 methods
returning 2 shapes (L2Block and BlockData). Replace the per-shape
getters with discriminated query objects that carry both the lookup
discriminant and a single onlyCheckpointed filter, removing the
parallel Checkpointed* API and the throwaway wrapper types.

L2BlockSource exposes 4 methods that take query objects:

  getBlock(query: BlockQuery): Promise<L2Block | undefined>
  getBlocks(query: BlocksQuery): Promise<L2Block[]>
  getBlockData(query: BlockQuery): Promise<BlockData | undefined>
  getBlocksData(query: BlocksQuery): Promise<BlockData[]>

  type BlockQuery  = ({number} | {hash} | {archive}) & { onlyCheckpointed?: boolean }
  type BlocksQuery = ({from, limit} | {epoch})       & { onlyCheckpointed?: boolean }

On-disk format is unchanged.

Wire-level: AztecNode.getCheckpointedBlocks element type goes
CheckpointedL2Block[] -> BlockResponse[]. CheckpointedL2Block and
BlockDataWithCheckpointContext are deleted entirely. Callers that
previously read .l1 / .attestations off these now do
getBlockData(...) followed by getCheckpointData(blockData.checkpointNumber)
and read those fields off CheckpointData.

- stdlib: BlockQuery / BlocksQuery types + Zod schemas next to
  L2BlockSource. CheckpointedL2Block file deleted;
  BlockDataWithCheckpointContext removed from block_data.ts.
  ArchiverApiSchema and MockArchiver shrunk.
- archiver: BlockStore consolidates to four query-object reads plus
  iterators. data_source_base.ts adds resolveBlocksQuery that
  translates {epoch} -> {from, limit}. Mocks honor onlyCheckpointed.
- aztec-node: server.ts keeps the public RPC method names but
  delegates to the new query methods. getCheckpointedBlocks adds a
  per-call cache to avoid an N+1.
- consumer migrations: world-state, txe, p2p block-txs handler,
  validator-client, pxe block-stream source, prover-node,
  sequencer-client, telemetry-client, aztec/testing, L2BlockStream
  in stdlib.
The Aztec rollup has an implicit "block zero" whose state is captured
in an initial block header computed by NativeWorldStateService.
Previously the archiver returned undefined for any query that
resolved to genesis, forcing every consumer (aztec-node, prover-node,
p2p, sequencer, PXE, sentinel) to reach into
worldStateSynchronizer.getCommitted().getInitialHeader() and synthesize
a fake block 0 themselves. Worse, components disagreed on the genesis
hash whenever genesisTimestamp or prefilled state diverged from the
default, because some used worldState.getInitialHeader().hash()
(dynamic) and others used the protocol constant
GENESIS_BLOCK_HEADER_HASH (static).

Construct world-state first, capture nativeWs.getInitialHeader(), and
pass it into the archiver at construction. The archiver returns a
synthetic L2Block.empty(initialHeader) for single-block queries that
resolve to genesis - by number, hash, archive, or tag. Range queries
explicitly do not prepend. L2TipsCache, world-state synchronizer's
getL2Tips, stdlib's L2TipsStoreBase, P2PClient/L2TipsKVStore, PXE,
and sentinel all switched to the dynamic initialHeader.hash().
Genesis special-casing was deleted from aztec-node, prover-node,
p2p_client, sequencer (the all-zeros escape hatch), and stdlib's
areBlockHashesEqualAt.

- archiver: synthetic genesis block in data_source_base (with archive
  set to new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1)),
  initialHeader plumbing through factory / constructor, L2TipsCache
  uses dynamic genesis hash, MockL2BlockSource synthesizes block 0
  + exposes getInitialHeader/setInitialHeader/setGenesisArchiveRoot,
  NoopL1Archiver accepts initialHeader.
- world-state: createWorldState exported as a public factory, new
  createWorldStateSynchronizerOverNative that wraps a pre-built native
  instance, getL2Tips reports initialHeader.hash() and BlockNumber.ZERO
  for genesis tips.
- aztec-node: server.ts reorders wiring so world-state is built first;
  getBlock, getBlockHeader, resolveBlockNumber, getPrivateLogsByTags,
  getPublicLogsByTagsFromContract no longer special-case genesis.
  buildGenesisBlockResponse deleted. Sentinel re-creates its
  L2TipsMemoryStore in init() with the archiver's block-0 hash.
- stdlib: L2TipsStoreBase accepts initialBlockHash (default
  GENESIS_BLOCK_HEADER_HASH for back-compat). areBlockHashesEqualAt
  no longer short-circuits at block 0. L2BlockStream's reorg-search
  loop refuses to walk past block 0.
- p2p: P2PClient / createP2PClient accept and forward
  initialBlockHash; aztec-node passes the archiver's genesis hash.
- pxe: fetches node.getBlock(0) at startup and seeds L2TipsKVStore
  with that hash.
- sequencer-client: deleted the all-zeros escape hatch in getStatus.
- prover-node: gatherPreviousBlockHeader calls
  l2BlockSource.getBlockData({number:0}) uniformly.
- tests: new archiver/src/modules/data_source_base.test.ts covering
  genesis-query semantics; world-state and validator-client integration
  tests thread db.getInitialHeader() and genesisArchiveRoot to the
  archiver/mock; new l2_block_stream.test.ts case asserting the
  genesis-hash-mismatch error path.
@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-no-squash labels Apr 30, 2026
PhilWindle pushed a commit that referenced this pull request May 1, 2026
> **Note:** This PR is stacked together with #22818, #22809, and #22870
into combined PR #22891 (targeting `merge-train/spartan`) for easier
merging. The combined PR has each of these as a separate commit, so
reviewers can either review here or on the combined PR.

## Motivation

`KVArchiverDataStore` was a thick pass-through wrapper that re-exported
the same methods as the substores it owned (`BlockStore`, `LogStore`,
`MessageStore`, `ContractClassStore`, `ContractInstanceStore`), forcing
every API change to be plumbed through three layers. Removing it brings
the archiver one step closer to a clean, query-object-based data source
API and makes substore boundaries explicit at every call site.

## Approach

Replace the wrapper with a plain `ArchiverDataStores` bundle that
exposes the substores directly, and move cross-store helpers to free
functions on the bundle. Substores absorb the array/iterator helpers
that previously lived on the wrapper. Function-name caching becomes its
own small class, and the `ContractDataSource` adapter is now a named
class instead of an inline object literal so that re-prover tools and
tests can reach for it without depending on `data_stores.ts` internals.

## Changes

- **archiver**: Delete `KVArchiverDataStore`; add `ArchiverDataStores`
bundle (`BlockStore`, `LogStore`, `MessageStore`, `ContractClassStore`,
`ContractInstanceStore`, db, function-name cache) and
`createArchiverDataStores`. Cross-store helpers
(`getArchiverSynchPoint`, `backupArchiverDataStores`) move to free
functions.
- **archiver (renames)**: `ArchiverDataStores` fields use plural form:
`blocks`, `logs`, `messages`, `contractClasses`, `contractInstances`.
Substore class names and individual store classes are unchanged.
- **archiver (function names)**: Extract `FunctionNamesCache` class
(replaces `Map<string, string>` plus the
`registerContractFunctionSignatures`/`getDebugFunctionName` free
functions).
- **archiver (contract data source)**: Extract
`ArchiverContractDataSourceAdapter` class implementing
`ContractDataSource`; `createContractDataSource` is now a thin factory.
- **archiver (substores)**:
`BlockStore.getBlocks`/`getCheckpointedBlocks`/`getBlockHeaders` return
arrays (iterator variants renamed `iterate*`); contract substores expose
batch `add*`/`delete*` helpers.
- **archiver (tests)**: Split the 4286-line `kv_archiver_store.test.ts`
into per-substore test files (`block_store.test.ts`,
`log_store.test.ts`, `message_store.test.ts`,
`contract_class_store.test.ts`, `contract_instance_store.test.ts`).
- **node-lib, prover-node, txe, validator-client, end-to-end**: Update
call sites to reach for the relevant substore on `ArchiverDataStores`
directly.
@spalladino

Copy link
Copy Markdown
Contributor Author

Closing as the first PR in the stack (#22818) was merged individually.

@spalladino spalladino closed this May 4, 2026
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 ci-no-squash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant