@aztec/archiver 0.0.1-commit.0dc957cde → 0.0.1-commit.0ec55a70b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dest/archiver.d.ts +15 -9
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +85 -53
  4. package/dest/config.d.ts +1 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +2 -2
  7. package/dest/errors.d.ts +16 -5
  8. package/dest/errors.d.ts.map +1 -1
  9. package/dest/errors.js +29 -6
  10. package/dest/factory.d.ts +4 -4
  11. package/dest/factory.d.ts.map +1 -1
  12. package/dest/factory.js +11 -9
  13. package/dest/index.d.ts +8 -2
  14. package/dest/index.d.ts.map +1 -1
  15. package/dest/index.js +7 -1
  16. package/dest/l1/calldata_retriever.d.ts +2 -1
  17. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  18. package/dest/l1/calldata_retriever.js +9 -4
  19. package/dest/modules/contract_data_source_adapter.d.ts +25 -0
  20. package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
  21. package/dest/modules/contract_data_source_adapter.js +42 -0
  22. package/dest/modules/data_source_base.d.ts +16 -10
  23. package/dest/modules/data_source_base.d.ts.map +1 -1
  24. package/dest/modules/data_source_base.js +71 -60
  25. package/dest/modules/data_store_updater.d.ts +6 -6
  26. package/dest/modules/data_store_updater.d.ts.map +1 -1
  27. package/dest/modules/data_store_updater.js +42 -40
  28. package/dest/modules/l1_synchronizer.d.ts +5 -4
  29. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  30. package/dest/modules/l1_synchronizer.js +79 -54
  31. package/dest/modules/validation.d.ts +4 -3
  32. package/dest/modules/validation.d.ts.map +1 -1
  33. package/dest/modules/validation.js +4 -4
  34. package/dest/store/block_store.d.ts +58 -27
  35. package/dest/store/block_store.d.ts.map +1 -1
  36. package/dest/store/block_store.js +152 -75
  37. package/dest/store/contract_class_store.d.ts +17 -3
  38. package/dest/store/contract_class_store.d.ts.map +1 -1
  39. package/dest/store/contract_class_store.js +17 -1
  40. package/dest/store/contract_instance_store.d.ts +28 -1
  41. package/dest/store/contract_instance_store.d.ts.map +1 -1
  42. package/dest/store/contract_instance_store.js +31 -0
  43. package/dest/store/data_stores.d.ts +68 -0
  44. package/dest/store/data_stores.d.ts.map +1 -0
  45. package/dest/store/data_stores.js +50 -0
  46. package/dest/store/function_names_cache.d.ts +17 -0
  47. package/dest/store/function_names_cache.d.ts.map +1 -0
  48. package/dest/store/function_names_cache.js +30 -0
  49. package/dest/store/l2_tips_cache.js +1 -1
  50. package/dest/test/fake_l1_state.d.ts +2 -1
  51. package/dest/test/fake_l1_state.d.ts.map +1 -1
  52. package/dest/test/fake_l1_state.js +25 -8
  53. package/dest/test/mock_l2_block_source.d.ts +12 -3
  54. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  55. package/dest/test/mock_l2_block_source.js +24 -2
  56. package/dest/test/noop_l1_archiver.d.ts +4 -4
  57. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  58. package/dest/test/noop_l1_archiver.js +5 -5
  59. package/package.json +13 -13
  60. package/src/archiver.ts +91 -49
  61. package/src/config.ts +2 -1
  62. package/src/errors.ts +41 -8
  63. package/src/factory.ts +10 -10
  64. package/src/index.ts +15 -1
  65. package/src/l1/calldata_retriever.ts +15 -4
  66. package/src/modules/contract_data_source_adapter.ts +59 -0
  67. package/src/modules/data_source_base.ts +75 -57
  68. package/src/modules/data_store_updater.ts +46 -38
  69. package/src/modules/l1_synchronizer.ts +92 -60
  70. package/src/modules/validation.ts +8 -7
  71. package/src/store/block_store.ts +159 -80
  72. package/src/store/contract_class_store.ts +28 -2
  73. package/src/store/contract_instance_store.ts +43 -0
  74. package/src/store/data_stores.ts +108 -0
  75. package/src/store/function_names_cache.ts +37 -0
  76. package/src/store/l2_tips_cache.ts +1 -1
  77. package/src/test/fake_l1_state.ts +24 -14
  78. package/src/test/mock_l2_block_source.ts +23 -2
  79. package/src/test/noop_l1_archiver.ts +6 -6
  80. package/dest/store/kv_archiver_store.d.ts +0 -383
  81. package/dest/store/kv_archiver_store.d.ts.map +0 -1
  82. package/dest/store/kv_archiver_store.js +0 -501
  83. package/src/store/kv_archiver_store.ts +0 -728
@@ -10,6 +10,7 @@ import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } fro
10
10
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
11
  import {
12
12
  type BlockData,
13
+ type BlockDataWithCheckpointContext,
13
14
  BlockHash,
14
15
  Body,
15
16
  CheckpointedL2Block,
@@ -45,6 +46,7 @@ import {
45
46
  import {
46
47
  BlockAlreadyCheckpointedError,
47
48
  BlockArchiveNotConsistentError,
49
+ BlockCheckpointNumberNotSequentialError,
48
50
  BlockIndexNotSequentialError,
49
51
  BlockNotFoundError,
50
52
  BlockNumberNotSequentialError,
@@ -56,7 +58,6 @@ import {
56
58
  ProposedCheckpointArchiveRootMismatchError,
57
59
  ProposedCheckpointNotSequentialError,
58
60
  ProposedCheckpointPromotionNotSequentialError,
59
- ProposedCheckpointStaleError,
60
61
  } from '../errors.js';
61
62
 
62
63
  export { TxReceipt, type TxEffect, type TxHash } from '@aztec/stdlib/tx';
@@ -84,6 +85,7 @@ type CommonCheckpointStorage = {
84
85
  type CheckpointStorage = CommonCheckpointStorage & {
85
86
  l1: Buffer;
86
87
  attestations: Buffer[];
88
+ feeAssetPriceModifier: string;
87
89
  };
88
90
 
89
91
  /** Storage format for a proposed checkpoint (attested but not yet L1-confirmed). */
@@ -101,7 +103,10 @@ export class BlockStore {
101
103
  /** Map block number to block data */
102
104
  #blocks: AztecAsyncMap<number, BlockStorage>;
103
105
 
104
- /** Map checkpoint number to checkpoint data */
106
+ /** Map keyed by checkpoint number holding proposed (locally-validated, not yet L1-confirmed) checkpoints. */
107
+ #proposedCheckpoints: AztecAsyncMap<number, ProposedCheckpointStorage>;
108
+
109
+ /** Map checkpoint number to checkpoint data for mined checkpoints only */
105
110
  #checkpoints: AztecAsyncMap<number, CheckpointStorage>;
106
111
 
107
112
  /** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
@@ -134,9 +139,6 @@ export class BlockStore {
134
139
  /** Index mapping block archive to block number */
135
140
  #blockArchiveIndex: AztecAsyncMap<string, number>;
136
141
 
137
- /** Singleton: assumes max 1-deep pipeline. For deeper pipelining, replace with a map keyed by checkpoint number. */
138
- #proposedCheckpoint: AztecAsyncSingleton<ProposedCheckpointStorage>;
139
-
140
142
  #log = createLogger('archiver:block_store');
141
143
 
142
144
  constructor(private db: AztecAsyncKVStore) {
@@ -152,7 +154,7 @@ export class BlockStore {
152
154
  this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
153
155
  this.#checkpoints = db.openMap('archiver_checkpoints');
154
156
  this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
155
- this.#proposedCheckpoint = db.openSingleton('proposed_checkpoint_data');
157
+ this.#proposedCheckpoints = db.openMap('archiver_proposed_checkpoints');
156
158
  }
157
159
 
158
160
  /**
@@ -188,8 +190,7 @@ export class BlockStore {
188
190
 
189
191
  // Extract the latest block and checkpoint numbers
190
192
  const previousBlockNumber = await this.getLatestL2BlockNumber();
191
- const proposedCheckpointNumber = await this.getProposedCheckpointNumber();
192
- const previousCheckpointNumber = await this.getLatestCheckpointNumber();
193
+ const latestCheckpointNumber = await this.getLatestCheckpointNumber();
193
194
 
194
195
  // Verify we're not overwriting checkpointed blocks
195
196
  const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
@@ -207,19 +208,14 @@ export class BlockStore {
207
208
  throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
208
209
  }
209
210
 
210
- // The same check as above but for checkpoints. Accept the block if either the confirmed
211
- // checkpoint or the pending (locally validated but not yet confirmed) checkpoint matches.
211
+ // Accept the block if either the confirmed checkpoint or a pending checkpoint matches
212
+ // the expected predecessor. We look for a pending entry at exactly blockCheckpointNumber - 1.
212
213
  const expectedCheckpointNumber = blockCheckpointNumber - 1;
213
- if (
214
- !opts.force &&
215
- previousCheckpointNumber !== expectedCheckpointNumber &&
216
- proposedCheckpointNumber !== expectedCheckpointNumber
217
- ) {
218
- const [reported, source]: [CheckpointNumber, 'confirmed' | 'proposed'] =
219
- proposedCheckpointNumber > previousCheckpointNumber
220
- ? [proposedCheckpointNumber, 'proposed']
221
- : [previousCheckpointNumber, 'confirmed'];
222
- throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, reported, source);
214
+ const hasPendingAtExpected = await this.#proposedCheckpoints.hasAsync(expectedCheckpointNumber);
215
+ if (!opts.force && latestCheckpointNumber !== expectedCheckpointNumber && !hasPendingAtExpected) {
216
+ const [latestPendingKey] = await toArray(this.#proposedCheckpoints.keysAsync({ reverse: true, limit: 1 }));
217
+ const previous = CheckpointNumber(Math.max(latestCheckpointNumber, latestPendingKey ?? 0));
218
+ throw new BlockCheckpointNumberNotSequentialError(blockNumber, blockCheckpointNumber, previous);
223
219
  }
224
220
 
225
221
  // Extract the previous block if there is one and see if it is for the same checkpoint or not
@@ -322,15 +318,15 @@ export class BlockStore {
322
318
  checkpointNumber: checkpoint.checkpoint.number,
323
319
  startBlock: checkpoint.checkpoint.blocks[0].number,
324
320
  blockCount: checkpoint.checkpoint.blocks.length,
321
+ feeAssetPriceModifier: checkpoint.checkpoint.feeAssetPriceModifier.toString(),
325
322
  });
326
323
 
327
324
  // Update slot-to-checkpoint index
328
325
  await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
329
- }
330
326
 
331
- // Clear the proposed checkpoint if any of the confirmed checkpoints match or supersede it
332
- const lastConfirmedCheckpointNumber = checkpoints[checkpoints.length - 1].checkpoint.number;
333
- await this.clearProposedCheckpointIfSuperseded(lastConfirmedCheckpointNumber);
327
+ // Remove proposed checkpoint if it exists, since L1 is authoritative
328
+ await this.#proposedCheckpoints.delete(checkpoint.checkpoint.number);
329
+ }
334
330
 
335
331
  await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
336
332
  return true;
@@ -374,6 +370,7 @@ export class BlockStore {
374
370
  checkpointNumber: incoming.checkpoint.number,
375
371
  startBlock: incoming.checkpoint.blocks[0].number,
376
372
  blockCount: incoming.checkpoint.blocks.length,
373
+ feeAssetPriceModifier: incoming.checkpoint.feeAssetPriceModifier.toString(),
377
374
  });
378
375
  // Update the sync point to reflect the new L1 block
379
376
  await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
@@ -391,24 +388,27 @@ export class BlockStore {
391
388
  return undefined;
392
389
  }
393
390
 
394
- const previousCheckpointData = await this.getCheckpointData(previousCheckpointNumber);
395
- if (previousCheckpointData === undefined) {
391
+ // Check across both proposed and mined checkpoints
392
+ const predecessor =
393
+ (await this.getProposedCheckpointByNumber(previousCheckpointNumber)) ??
394
+ (await this.getCheckpointData(previousCheckpointNumber));
395
+
396
+ if (!predecessor) {
396
397
  throw new CheckpointNotFoundError(previousCheckpointNumber);
397
398
  }
398
399
 
399
- const previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
400
+ const previousBlockNumber = BlockNumber(predecessor.startBlock + predecessor.blockCount - 1);
400
401
  const previousBlock = await this.getBlock(previousBlockNumber);
401
402
  if (previousBlock === undefined) {
402
403
  throw new BlockNotFoundError(previousBlockNumber);
403
404
  }
404
-
405
405
  return previousBlock;
406
406
  }
407
407
 
408
408
  /**
409
409
  * Validates that blocks are sequential, have correct indexes, and chain via archive roots.
410
410
  * This is the same validation used for both confirmed checkpoints (addCheckpoints) and
411
- * proposed checkpoints (setProposedCheckpoint).
411
+ * proposed checkpoints (addProposedCheckpoint).
412
412
  */
413
413
  private validateCheckpointBlocks(blocks: L2Block[], previousBlock: L2Block | undefined): void {
414
414
  for (const block of blocks) {
@@ -535,11 +535,8 @@ export class BlockStore {
535
535
  this.#log.debug(`Removed checkpoint ${c}`);
536
536
  }
537
537
 
538
- // Clear any proposed checkpoint that was orphaned by the removal (its base chain no longer exists)
539
- const proposedCheckpointNumber = await this.getProposedCheckpointNumber();
540
- if (proposedCheckpointNumber > checkpointNumber) {
541
- await this.#proposedCheckpoint.delete();
542
- }
538
+ // Evict all pending checkpoints > checkpointNumber (their base chain no longer exists)
539
+ await this.evictProposedCheckpointsFrom(CheckpointNumber(checkpointNumber + 1));
543
540
 
544
541
  return { blocksRemoved };
545
542
  });
@@ -588,6 +585,7 @@ export class BlockStore {
588
585
  checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
589
586
  startBlock: BlockNumber(checkpointStorage.startBlock),
590
587
  blockCount: checkpointStorage.blockCount,
588
+ feeAssetPriceModifier: BigInt(checkpointStorage.feeAssetPriceModifier),
591
589
  l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
592
590
  attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
593
591
  };
@@ -688,28 +686,34 @@ export class BlockStore {
688
686
  }
689
687
 
690
688
  async hasProposedCheckpoint(): Promise<boolean> {
691
- const proposed = await this.#proposedCheckpoint.getAsync();
692
- return proposed !== undefined;
689
+ const [key] = await toArray(this.#proposedCheckpoints.keysAsync({ limit: 1 }));
690
+ return key !== undefined;
693
691
  }
694
692
 
695
- /** Deletes the proposed checkpoint from storage. */
696
- async deleteProposedCheckpoint(): Promise<void> {
697
- await this.#proposedCheckpoint.delete();
693
+ /** Deletes all pending proposed checkpoints from storage. */
694
+ async deleteProposedCheckpoints(): Promise<void> {
695
+ for await (const key of this.#proposedCheckpoints.keysAsync()) {
696
+ await this.#proposedCheckpoints.delete(key);
697
+ }
698
698
  }
699
699
 
700
700
  /**
701
- * Promotes the proposed checkpoint singleton to a confirmed checkpoint entry.
702
- * This persists the checkpoint to the store, clears the proposed singleton, and updates the L1 sync point.
703
- * Should only be called after the checkpoint has been validated.
704
- * @param expectedArchiveRoot - The archive root to match against the proposed checkpoint, to guard against races.
701
+ * Promotes a specific pending checkpoint to a confirmed checkpoint entry.
702
+ * This persists the checkpoint to the store, removes only that pending entry, and updates the L1 sync point.
703
+ * Remaining pending entries (e.g. N+1, N+2) are left intact — they chain off the just-promoted one.
704
+ * @param checkpointNumber - The checkpoint number to promote.
705
+ * @param l1 - L1 published data for the checkpoint.
706
+ * @param attestations - Committee attestations.
707
+ * @param expectedArchiveRoot - Archive root guard against races.
705
708
  */
706
709
  async promoteProposedToCheckpointed(
710
+ checkpointNumber: CheckpointNumber,
707
711
  l1: L1PublishedData,
708
712
  attestations: CommitteeAttestation[],
709
713
  expectedArchiveRoot: Fr,
710
714
  ): Promise<void> {
711
715
  return await this.db.transactionAsync(async () => {
712
- const proposed = await this.getProposedCheckpointOnly();
716
+ const proposed = await this.getProposedCheckpointByNumber(checkpointNumber);
713
717
  if (!proposed) {
714
718
  throw new NoProposedCheckpointToPromoteError();
715
719
  }
@@ -733,48 +737,76 @@ export class BlockStore {
733
737
  checkpointNumber: proposed.checkpointNumber,
734
738
  startBlock: proposed.startBlock,
735
739
  blockCount: proposed.blockCount,
740
+ feeAssetPriceModifier: proposed.feeAssetPriceModifier.toString(),
736
741
  });
737
742
 
738
743
  // Update the slot-to-checkpoint index
739
744
  await this.#slotToCheckpoint.set(proposed.header.slotNumber, proposed.checkpointNumber);
740
745
 
741
- // Clear the proposed checkpoint singleton
742
- await this.#proposedCheckpoint.delete();
746
+ // Remove only this pending entry — remaining entries N+1, N+2, ... stay valid
747
+ await this.#proposedCheckpoints.delete(proposed.checkpointNumber);
743
748
 
744
749
  // Update the last synced L1 block
745
750
  await this.#lastSynchedL1Block.set(l1.blockNumber);
746
751
  });
747
752
  }
748
753
 
749
- /** Clears the proposed checkpoint if the given confirmed checkpoint number supersedes it. */
750
- async clearProposedCheckpointIfSuperseded(confirmedCheckpointNumber: CheckpointNumber): Promise<void> {
751
- const proposedCheckpointNumber = await this.getProposedCheckpointNumber();
752
- if (proposedCheckpointNumber <= confirmedCheckpointNumber) {
753
- await this.#proposedCheckpoint.delete();
754
+ /**
755
+ * Returns the latest pending checkpoint (highest-numbered entry), or undefined if none.
756
+ * No fallback to confirmed.
757
+ */
758
+ async getLastProposedCheckpoint(): Promise<ProposedCheckpointData | undefined> {
759
+ const [key] = await toArray(this.#proposedCheckpoints.keysAsync({ reverse: true, limit: 1 }));
760
+ if (key === undefined) {
761
+ return undefined;
754
762
  }
763
+ const stored = await this.#proposedCheckpoints.getAsync(key);
764
+ return stored ? this.convertToProposedCheckpointData(stored) : undefined;
755
765
  }
756
766
 
757
- /** Returns the proposed checkpoint data, or undefined if no proposed checkpoint exists. No fallback to confirmed. */
758
- async getProposedCheckpointOnly(): Promise<ProposedCheckpointData | undefined> {
759
- const stored = await this.#proposedCheckpoint.getAsync();
760
- if (!stored) {
761
- return undefined;
767
+ /** Returns the pending checkpoint for a specific checkpoint number, or undefined if not found. */
768
+ async getProposedCheckpointByNumber(n: CheckpointNumber): Promise<ProposedCheckpointData | undefined> {
769
+ const stored = await this.#proposedCheckpoints.getAsync(n);
770
+ return stored ? this.convertToProposedCheckpointData(stored) : undefined;
771
+ }
772
+
773
+ /** Returns all pending checkpoints in ascending checkpoint-number order. */
774
+ async getProposedCheckpoints(): Promise<ProposedCheckpointData[]> {
775
+ const results: ProposedCheckpointData[] = [];
776
+ for await (const [, stored] of this.#proposedCheckpoints.entriesAsync()) {
777
+ results.push(this.convertToProposedCheckpointData(stored));
778
+ }
779
+ return results;
780
+ }
781
+
782
+ /**
783
+ * Evicts all pending checkpoints with checkpoint number >= fromNumber.
784
+ * Used for divergent-mined-checkpoint cleanup: when L1 mines checkpoint N with a different archive,
785
+ * all pending >= N must be evicted since they chain off the now-invalid pending N.
786
+ */
787
+ async evictProposedCheckpointsFrom(fromNumber: CheckpointNumber): Promise<void> {
788
+ const keysToDelete: number[] = [];
789
+ for await (const key of this.#proposedCheckpoints.keysAsync()) {
790
+ if (key >= fromNumber) {
791
+ keysToDelete.push(key);
792
+ }
793
+ }
794
+ for (const key of keysToDelete) {
795
+ await this.#proposedCheckpoints.delete(key);
762
796
  }
763
- return this.convertToProposedCheckpointData(stored);
764
797
  }
765
798
 
766
799
  /**
767
- * Gets the checkpoint at the proposed tip
768
- * - pending checkpoint if it exists
800
+ * Gets the checkpoint at the proposed tip:
801
+ * - latest pending checkpoint if any exist
769
802
  * - fallsback to latest confirmed checkpoint otherwise
770
- * @returns CommonCheckpointData
771
803
  */
772
- async getProposedCheckpoint(): Promise<CommonCheckpointData | undefined> {
773
- const stored = await this.#proposedCheckpoint.getAsync();
774
- if (!stored) {
804
+ async getLastCheckpoint(): Promise<CommonCheckpointData | undefined> {
805
+ const latest = await this.getLastProposedCheckpoint();
806
+ if (!latest) {
775
807
  return this.getCheckpointData(await this.getLatestCheckpointNumber());
776
808
  }
777
- return this.convertToProposedCheckpointData(stored);
809
+ return latest;
778
810
  }
779
811
 
780
812
  private convertToProposedCheckpointData(stored: ProposedCheckpointStorage): ProposedCheckpointData {
@@ -795,7 +827,7 @@ export class BlockStore {
795
827
  * @returns CheckpointNumber
796
828
  */
797
829
  async getProposedCheckpointNumber(): Promise<CheckpointNumber> {
798
- const proposed = await this.getProposedCheckpoint();
830
+ const proposed = await this.getLastCheckpoint();
799
831
  if (!proposed) {
800
832
  return await this.getLatestCheckpointNumber();
801
833
  }
@@ -807,7 +839,7 @@ export class BlockStore {
807
839
  * @returns BlockNumber
808
840
  */
809
841
  async getProposedCheckpointL2BlockNumber(): Promise<BlockNumber> {
810
- const proposed = await this.getProposedCheckpoint();
842
+ const proposed = await this.getLastCheckpoint();
811
843
  if (!proposed) {
812
844
  return await this.getCheckpointedL2BlockNumber();
813
845
  }
@@ -841,7 +873,12 @@ export class BlockStore {
841
873
  * @param limit - The number of blocks to return.
842
874
  * @returns The requested L2 blocks
843
875
  */
844
- async *getCheckpointedBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<CheckpointedL2Block> {
876
+ getCheckpointedBlocks(start: BlockNumber, limit: number): Promise<CheckpointedL2Block[]> {
877
+ return toArray(this.iterateCheckpointedBlocks(start, limit));
878
+ }
879
+
880
+ /** Async iterator variant of {@link getCheckpointedBlocks}. */
881
+ async *iterateCheckpointedBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<CheckpointedL2Block> {
845
882
  const checkpointCache = new Map<CheckpointNumber, CheckpointStorage>();
846
883
  for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
847
884
  const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
@@ -885,7 +922,12 @@ export class BlockStore {
885
922
  * @param limit - The number of blocks to return.
886
923
  * @returns The requested L2 blocks
887
924
  */
888
- async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2Block> {
925
+ getBlocks(start: BlockNumber, limit: number): Promise<L2Block[]> {
926
+ return toArray(this.iterateBlocks(start, limit));
927
+ }
928
+
929
+ /** Async iterator variant of {@link getBlocks}. */
930
+ async *iterateBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2Block> {
889
931
  for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
890
932
  const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
891
933
  if (block) {
@@ -907,6 +949,33 @@ export class BlockStore {
907
949
  return this.getBlockDataFromBlockStorage(blockStorage);
908
950
  }
909
951
 
952
+ /**
953
+ * Gets block metadata plus checkpoint-derived context (L1 publish info, attestations) without
954
+ * deserializing tx bodies. When the block's containing checkpoint has not yet been L1-confirmed,
955
+ * `checkpoint` and `l1` are `undefined` and `attestations` is empty.
956
+ */
957
+ async getBlockDataWithCheckpointContext(
958
+ blockNumber: BlockNumber,
959
+ ): Promise<BlockDataWithCheckpointContext | undefined> {
960
+ const blockStorage = await this.#blocks.getAsync(blockNumber);
961
+ if (!blockStorage || !blockStorage.header) {
962
+ return undefined;
963
+ }
964
+ const data = this.getBlockDataFromBlockStorage(blockStorage);
965
+ const checkpointStorage = await this.#checkpoints.getAsync(blockStorage.checkpointNumber);
966
+ if (!checkpointStorage) {
967
+ return { data, checkpoint: undefined, l1: undefined, attestations: [] };
968
+ }
969
+ const checkpoint = this.checkpointDataFromCheckpointStorage(checkpointStorage);
970
+ return { data, checkpoint, l1: checkpoint.l1, attestations: checkpoint.attestations };
971
+ }
972
+
973
+ /** Returns the checkpoint number that contains the given slot (or undefined if not found). */
974
+ async getCheckpointNumberBySlot(slot: SlotNumber): Promise<CheckpointNumber | undefined> {
975
+ const checkpointNumber = await this.#slotToCheckpoint.getAsync(slot);
976
+ return checkpointNumber === undefined ? undefined : CheckpointNumber(checkpointNumber);
977
+ }
978
+
910
979
  /**
911
980
  * Gets block metadata (without tx data) by archive root.
912
981
  * @param archive - The archive root of the block to return.
@@ -999,7 +1068,12 @@ export class BlockStore {
999
1068
  * @param limit - The number of blocks to return.
1000
1069
  * @returns The requested L2 block headers
1001
1070
  */
1002
- async *getBlockHeaders(start: BlockNumber, limit: number): AsyncIterableIterator<BlockHeader> {
1071
+ getBlockHeaders(start: BlockNumber, limit: number): Promise<BlockHeader[]> {
1072
+ return toArray(this.iterateBlockHeaders(start, limit));
1073
+ }
1074
+
1075
+ /** Async iterator variant of {@link getBlockHeaders}. */
1076
+ async *iterateBlockHeaders(start: BlockNumber, limit: number): AsyncIterableIterator<BlockHeader> {
1003
1077
  for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
1004
1078
  const header = BlockHeader.fromBuffer(blockStorage.header);
1005
1079
  if (header.getBlockNumber() !== blockNumber) {
@@ -1188,20 +1262,25 @@ export class BlockStore {
1188
1262
  return this.#lastSynchedL1Block.set(l1BlockNumber);
1189
1263
  }
1190
1264
 
1191
- /** Sets the proposed checkpoint (not yet L1-confirmed). Only accepts confirmed + 1.
1192
- * Computes archive and checkpointOutHash from the stored blocks. */
1193
- async setProposedCheckpoint(proposed: ProposedCheckpointInput) {
1265
+ /**
1266
+ * Adds a proposed checkpoint to the pending queue.
1267
+ * Accepts proposed.checkpointNumber === latestTip + 1, where latestTip is the highest of
1268
+ * confirmed and the highest pending checkpoint number.
1269
+ * Computes archive and checkpointOutHash from the stored blocks.
1270
+ */
1271
+ async addProposedCheckpoint(proposed: ProposedCheckpointInput) {
1194
1272
  return await this.db.transactionAsync(async () => {
1195
- const current = await this.getProposedCheckpointNumber();
1196
- if (proposed.checkpointNumber <= current) {
1197
- throw new ProposedCheckpointStaleError(proposed.checkpointNumber, current);
1198
- }
1199
1273
  const confirmed = await this.getLatestCheckpointNumber();
1200
- if (proposed.checkpointNumber !== confirmed + 1) {
1201
- throw new ProposedCheckpointNotSequentialError(proposed.checkpointNumber, confirmed);
1274
+ const [latestPendingKey] = await toArray(this.#proposedCheckpoints.keysAsync({ reverse: true, limit: 1 }));
1275
+ const latestTip = CheckpointNumber(
1276
+ latestPendingKey !== undefined ? Math.max(latestPendingKey, confirmed) : confirmed,
1277
+ );
1278
+
1279
+ if (proposed.checkpointNumber !== latestTip + 1) {
1280
+ throw new ProposedCheckpointNotSequentialError(proposed.checkpointNumber, latestTip);
1202
1281
  }
1203
1282
 
1204
- // Ensure the previous checkpoint + blocks exist
1283
+ // Ensure the predecessor block (from pending or confirmed chain) exists
1205
1284
  const previousBlock = await this.getPreviousCheckpointBlock(proposed.checkpointNumber);
1206
1285
  const blocks: L2Block[] = [];
1207
1286
  for (let i = 0; i < proposed.blockCount; i++) {
@@ -1216,7 +1295,7 @@ export class BlockStore {
1216
1295
  const archive = blocks[blocks.length - 1].archive;
1217
1296
  const checkpointOutHash = Checkpoint.getCheckpointOutHash(blocks);
1218
1297
 
1219
- await this.#proposedCheckpoint.set({
1298
+ await this.#proposedCheckpoints.set(proposed.checkpointNumber, {
1220
1299
  header: proposed.header.toBuffer(),
1221
1300
  archive: archive.toBuffer(),
1222
1301
  checkpointOutHash: checkpointOutHash.toBuffer(),
@@ -2,7 +2,11 @@ import { Fr } from '@aztec/foundation/curves/bn254';
2
2
  import { toArray } from '@aztec/foundation/iterable';
3
3
  import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize';
4
4
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
5
- import type { ContractClassPublic, ContractClassPublicWithBlockNumber } from '@aztec/stdlib/contract';
5
+ import type {
6
+ ContractClassPublic,
7
+ ContractClassPublicWithBlockNumber,
8
+ ContractClassPublicWithCommitment,
9
+ } from '@aztec/stdlib/contract';
6
10
 
7
11
  /**
8
12
  * LMDB-based contract class storage for the archiver.
@@ -16,6 +20,28 @@ export class ContractClassStore {
16
20
  this.#bytecodeCommitments = db.openMap('archiver_bytecode_commitments');
17
21
  }
18
22
 
23
+ /**
24
+ * Adds multiple contract classes to the store.
25
+ * @param data - Contract classes (with bytecode commitments) to add.
26
+ * @param blockNumber - L2 block number where the classes were registered.
27
+ * @returns True if every insert succeeded.
28
+ */
29
+ async addContractClasses(data: ContractClassPublicWithCommitment[], blockNumber: number): Promise<boolean> {
30
+ return (await Promise.all(data.map(c => this.addContractClass(c, c.publicBytecodeCommitment, blockNumber)))).every(
31
+ Boolean,
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Removes multiple contract classes from the store, but only if they were registered at or after the given block.
37
+ * @param data - Contract classes to delete.
38
+ * @param blockNumber - Lower bound on the block number at which the classes were registered.
39
+ * @returns True if every delete succeeded.
40
+ */
41
+ async deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean> {
42
+ return (await Promise.all(data.map(c => this.deleteContractClass(c, blockNumber)))).every(Boolean);
43
+ }
44
+
19
45
  async addContractClass(
20
46
  contractClass: ContractClassPublic,
21
47
  bytecodeCommitment: Fr,
@@ -34,7 +60,7 @@ export class ContractClassStore {
34
60
  });
35
61
  }
36
62
 
37
- async deleteContractClasses(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
63
+ async deleteContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
38
64
  const restoredContractClass = await this.#contractClasses.getAsync(contractClass.id.toString());
39
65
  if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
40
66
  await this.db.transactionAsync(async () => {
@@ -25,6 +25,49 @@ export class ContractInstanceStore {
25
25
  this.#contractInstanceUpdates = db.openMap('archiver_contract_instance_updates');
26
26
  }
27
27
 
28
+ /**
29
+ * Adds multiple contract instances to the store.
30
+ * @param data - Contract instances to add.
31
+ * @param blockNumber - L2 block number where the instances were deployed.
32
+ * @returns True if every insert succeeded.
33
+ */
34
+ async addContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean> {
35
+ return (await Promise.all(data.map(c => this.addContractInstance(c, blockNumber)))).every(Boolean);
36
+ }
37
+
38
+ /**
39
+ * Removes multiple contract instances from the store.
40
+ * @param data - Contract instances to delete.
41
+ * @returns True if every delete succeeded.
42
+ */
43
+ async deleteContractInstances(data: ContractInstanceWithAddress[]): Promise<boolean> {
44
+ return (await Promise.all(data.map(c => this.deleteContractInstance(c)))).every(Boolean);
45
+ }
46
+
47
+ /**
48
+ * Adds multiple contract instance updates to the store.
49
+ * @param data - Contract instance updates to add.
50
+ * @param timestamp - Timestamp at which the updates were scheduled.
51
+ * @returns True if every insert succeeded.
52
+ */
53
+ async addContractInstanceUpdates(data: ContractInstanceUpdateWithAddress[], timestamp: UInt64): Promise<boolean> {
54
+ return (
55
+ await Promise.all(data.map((update, logIndex) => this.addContractInstanceUpdate(update, timestamp, logIndex)))
56
+ ).every(Boolean);
57
+ }
58
+
59
+ /**
60
+ * Removes multiple contract instance updates from the store.
61
+ * @param data - Contract instance updates to delete.
62
+ * @param timestamp - Timestamp at which the updates were scheduled.
63
+ * @returns True if every delete succeeded.
64
+ */
65
+ async deleteContractInstanceUpdates(data: ContractInstanceUpdateWithAddress[], timestamp: UInt64): Promise<boolean> {
66
+ return (
67
+ await Promise.all(data.map((update, logIndex) => this.deleteContractInstanceUpdate(update, timestamp, logIndex)))
68
+ ).every(Boolean);
69
+ }
70
+
28
71
  addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise<void> {
29
72
  return this.db.transactionAsync(async () => {
30
73
  const key = contractInstance.address.toString();
@@ -0,0 +1,108 @@
1
+ import type { L1BlockId } from '@aztec/ethereum/l1-types';
2
+ import type { AztecAsyncKVStore } from '@aztec/kv-store';
3
+ import type { ContractDataSource } from '@aztec/stdlib/contract';
4
+
5
+ import { join } from 'path';
6
+
7
+ import { ArchiverContractDataSourceAdapter } from '../modules/contract_data_source_adapter.js';
8
+ import { BlockStore } from './block_store.js';
9
+ import { ContractClassStore } from './contract_class_store.js';
10
+ import { ContractInstanceStore } from './contract_instance_store.js';
11
+ import { FunctionNamesCache } from './function_names_cache.js';
12
+ import { LogStore } from './log_store.js';
13
+ import { MessageStore } from './message_store.js';
14
+
15
+ export const ARCHIVER_DB_VERSION = 6;
16
+
17
+ /**
18
+ * Represents the latest L1 block processed by the archiver for various objects in L2.
19
+ */
20
+ export type ArchiverL1SynchPoint = {
21
+ /** Number of the last L1 block that added a new L2 checkpoint metadata. */
22
+ blocksSynchedTo?: bigint;
23
+ /** Last L1 block checked for L1 to L2 messages. */
24
+ messagesSynchedTo?: L1BlockId;
25
+ };
26
+
27
+ /**
28
+ * Bundle of archiver-owned LMDB substores plus the in-memory caches that span them.
29
+ *
30
+ * Replaces the former `KVArchiverDataStore` pass-through wrapper. Callers reach into
31
+ * the relevant substore directly (e.g. `stores.blocks.getBlock`) and use
32
+ * {@link createArchiverDataStores} to wire them up against a shared KV store.
33
+ */
34
+ export type ArchiverDataStores = {
35
+ /** The underlying key-value store. Use {@link AztecAsyncKVStore.transactionAsync} to compose updates atomically. */
36
+ db: AztecAsyncKVStore;
37
+ /** Blocks, checkpoints, tx effects, proven/finalized state. */
38
+ blocks: BlockStore;
39
+ /** Public, private and contract class logs. */
40
+ logs: LogStore;
41
+ /** L1 to L2 messages and message sync state. */
42
+ messages: MessageStore;
43
+ /** Contract classes (with bytecode commitments). */
44
+ contractClasses: ContractClassStore;
45
+ /** Contract instances and contract instance updates. */
46
+ contractInstances: ContractInstanceStore;
47
+ /** In-memory cache of public function selectors -> names. */
48
+ functionNames: FunctionNamesCache;
49
+ };
50
+
51
+ /** Options used by {@link createArchiverDataStores}. */
52
+ export type CreateArchiverDataStoresOptions = {
53
+ /** Maximum number of logs returned per page when paginating tagged log queries. */
54
+ logsMaxPageSize?: number;
55
+ };
56
+
57
+ /**
58
+ * Wires up the archiver substores against a shared KV store and returns the
59
+ * {@link ArchiverDataStores} bundle.
60
+ */
61
+ export function createArchiverDataStores(
62
+ db: AztecAsyncKVStore,
63
+ opts: CreateArchiverDataStoresOptions = {},
64
+ ): ArchiverDataStores {
65
+ const blocks = new BlockStore(db);
66
+ return {
67
+ db,
68
+ blocks,
69
+ logs: new LogStore(db, blocks, opts.logsMaxPageSize ?? 1000),
70
+ messages: new MessageStore(db),
71
+ contractClasses: new ContractClassStore(db),
72
+ contractInstances: new ContractInstanceStore(db),
73
+ functionNames: new FunctionNamesCache(),
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Returns the L1 sync point of the archiver, combining the block sync point from {@link BlockStore}
79
+ * and the message sync point from {@link MessageStore}.
80
+ */
81
+ export async function getArchiverSynchPoint(stores: ArchiverDataStores): Promise<ArchiverL1SynchPoint> {
82
+ const [blocksSynchedTo, messagesSynchedTo] = await Promise.all([
83
+ stores.blocks.getSynchedL1BlockNumber(),
84
+ stores.messages.getSynchedL1Block(),
85
+ ]);
86
+ return { blocksSynchedTo, messagesSynchedTo };
87
+ }
88
+
89
+ /**
90
+ * Backs up the underlying KV store to the given folder. Returns the path to the resulting db file.
91
+ */
92
+ export async function backupArchiverDataStores(
93
+ stores: ArchiverDataStores,
94
+ path: string,
95
+ compress = true,
96
+ ): Promise<string> {
97
+ await stores.db.backupTo(path, compress);
98
+ return join(path, 'data.mdb');
99
+ }
100
+
101
+ /**
102
+ * Returns a {@link ContractDataSource} adapter over {@link ArchiverDataStores}.
103
+ * Used by contexts (e.g. offline epoch re-prover tools) that need a ContractDataSource
104
+ * but do not need a full archiver instance.
105
+ */
106
+ export function createContractDataSource(stores: ArchiverDataStores): ContractDataSource {
107
+ return new ArchiverContractDataSourceAdapter(stores);
108
+ }