@aztec/archiver 0.0.1-commit.f295ac2 → 0.0.1-commit.f2ce05ee

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 (75) hide show
  1. package/README.md +9 -0
  2. package/dest/archiver.d.ts +6 -5
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +34 -22
  5. package/dest/errors.d.ts +6 -1
  6. package/dest/errors.d.ts.map +1 -1
  7. package/dest/errors.js +8 -0
  8. package/dest/factory.d.ts +5 -2
  9. package/dest/factory.d.ts.map +1 -1
  10. package/dest/factory.js +11 -8
  11. package/dest/l1/bin/retrieve-calldata.js +17 -18
  12. package/dest/l1/data_retrieval.d.ts +1 -1
  13. package/dest/l1/data_retrieval.d.ts.map +1 -1
  14. package/dest/l1/data_retrieval.js +3 -3
  15. package/dest/l1/validate_trace.d.ts +6 -3
  16. package/dest/l1/validate_trace.d.ts.map +1 -1
  17. package/dest/l1/validate_trace.js +13 -9
  18. package/dest/modules/data_source_base.d.ts +19 -20
  19. package/dest/modules/data_source_base.d.ts.map +1 -1
  20. package/dest/modules/data_source_base.js +25 -56
  21. package/dest/modules/data_store_updater.d.ts +23 -19
  22. package/dest/modules/data_store_updater.d.ts.map +1 -1
  23. package/dest/modules/data_store_updater.js +47 -49
  24. package/dest/modules/instrumentation.d.ts +3 -3
  25. package/dest/modules/instrumentation.d.ts.map +1 -1
  26. package/dest/modules/instrumentation.js +17 -10
  27. package/dest/modules/l1_synchronizer.d.ts +1 -1
  28. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  29. package/dest/modules/l1_synchronizer.js +9 -10
  30. package/dest/store/block_store.d.ts +35 -21
  31. package/dest/store/block_store.d.ts.map +1 -1
  32. package/dest/store/block_store.js +81 -40
  33. package/dest/store/contract_class_store.d.ts +1 -1
  34. package/dest/store/contract_class_store.d.ts.map +1 -1
  35. package/dest/store/contract_class_store.js +11 -7
  36. package/dest/store/kv_archiver_store.d.ts +37 -28
  37. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  38. package/dest/store/kv_archiver_store.js +31 -23
  39. package/dest/store/log_store.d.ts +17 -8
  40. package/dest/store/log_store.d.ts.map +1 -1
  41. package/dest/store/log_store.js +77 -43
  42. package/dest/test/fake_l1_state.d.ts +4 -4
  43. package/dest/test/fake_l1_state.d.ts.map +1 -1
  44. package/dest/test/index.js +3 -1
  45. package/dest/test/mock_archiver.js +1 -1
  46. package/dest/test/mock_l2_block_source.d.ts +20 -20
  47. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  48. package/dest/test/mock_l2_block_source.js +40 -41
  49. package/dest/test/mock_structs.d.ts +3 -2
  50. package/dest/test/mock_structs.d.ts.map +1 -1
  51. package/dest/test/mock_structs.js +11 -9
  52. package/dest/test/noop_l1_archiver.d.ts +23 -0
  53. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  54. package/dest/test/noop_l1_archiver.js +68 -0
  55. package/package.json +14 -13
  56. package/src/archiver.ts +46 -28
  57. package/src/errors.ts +12 -0
  58. package/src/factory.ts +23 -13
  59. package/src/l1/bin/retrieve-calldata.ts +16 -17
  60. package/src/l1/data_retrieval.ts +4 -4
  61. package/src/l1/validate_trace.ts +24 -6
  62. package/src/modules/data_source_base.ts +34 -81
  63. package/src/modules/data_store_updater.ts +59 -55
  64. package/src/modules/instrumentation.ts +17 -12
  65. package/src/modules/l1_synchronizer.ts +11 -12
  66. package/src/store/block_store.ts +107 -60
  67. package/src/store/contract_class_store.ts +11 -7
  68. package/src/store/kv_archiver_store.ts +52 -35
  69. package/src/store/log_store.ts +134 -49
  70. package/src/test/fake_l1_state.ts +2 -2
  71. package/src/test/index.ts +3 -0
  72. package/src/test/mock_archiver.ts +1 -1
  73. package/src/test/mock_l2_block_source.ts +54 -64
  74. package/src/test/mock_structs.ts +26 -10
  75. package/src/test/noop_l1_archiver.ts +109 -0
@@ -9,16 +9,17 @@ import { isDefined } from '@aztec/foundation/types';
9
9
  import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } from '@aztec/kv-store';
10
10
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
11
  import {
12
+ BlockHash,
12
13
  Body,
13
14
  CheckpointedL2Block,
14
15
  CommitteeAttestation,
15
- L2BlockHash,
16
- L2BlockNew,
16
+ L2Block,
17
17
  type ValidateCheckpointResult,
18
18
  deserializeValidateCheckpointResult,
19
19
  serializeValidateCheckpointResult,
20
20
  } from '@aztec/stdlib/block';
21
21
  import { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
22
+ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
22
23
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
23
24
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
24
25
  import {
@@ -27,6 +28,7 @@ import {
27
28
  TxEffect,
28
29
  TxHash,
29
30
  TxReceipt,
31
+ TxStatus,
30
32
  deserializeIndexedTxEffect,
31
33
  serializeIndexedTxEffect,
32
34
  } from '@aztec/stdlib/tx';
@@ -36,6 +38,7 @@ import {
36
38
  BlockIndexNotSequentialError,
37
39
  BlockNotFoundError,
38
40
  BlockNumberNotSequentialError,
41
+ CannotOverwriteCheckpointedBlockError,
39
42
  CheckpointNotFoundError,
40
43
  CheckpointNumberNotConsistentError,
41
44
  CheckpointNumberNotSequentialError,
@@ -75,6 +78,8 @@ export type CheckpointData = {
75
78
  attestations: Buffer[];
76
79
  };
77
80
 
81
+ export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
82
+
78
83
  /**
79
84
  * LMDB-based block storage for the archiver.
80
85
  */
@@ -111,7 +116,10 @@ export class BlockStore {
111
116
 
112
117
  #log = createLogger('archiver:block_store');
113
118
 
114
- constructor(private db: AztecAsyncKVStore) {
119
+ constructor(
120
+ private db: AztecAsyncKVStore,
121
+ private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
122
+ ) {
115
123
  this.#blocks = db.openMap('archiver_blocks');
116
124
  this.#blockTxs = db.openMap('archiver_block_txs');
117
125
  this.#txEffects = db.openMap('archiver_tx_effects');
@@ -125,11 +133,25 @@ export class BlockStore {
125
133
  }
126
134
 
127
135
  /**
128
- * Append new blocks to the store's list. All blocks must be for the 'current' checkpoint
129
- * @param blocks - The L2 blocks to be added to the store.
136
+ * Computes the finalized block number based on the proven block number.
137
+ * A block is considered finalized when it's 2 epochs behind the proven block.
138
+ * TODO(#13569): Compute proper finalized block number based on L1 finalized block.
139
+ * TODO(palla/mbps): Even the provisional computation is wrong, since it should subtract checkpoints, not blocks
140
+ * @returns The finalized block number.
141
+ */
142
+ async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
143
+ const provenBlockNumber = await this.getProvenBlockNumber();
144
+ return BlockNumber(Math.max(provenBlockNumber - this.l1Constants.epochDuration * 2, 0));
145
+ }
146
+
147
+ /**
148
+ * Append new proposed blocks to the store's list. All blocks must be for the 'current' checkpoint.
149
+ * These are uncheckpointed blocks that have been proposed by the sequencer but not yet included in a checkpoint on L1.
150
+ * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
151
+ * @param blocks - The proposed L2 blocks to be added to the store.
130
152
  * @returns True if the operation is successful.
131
153
  */
132
- async addBlocks(blocks: L2BlockNew[], opts: { force?: boolean } = {}): Promise<boolean> {
154
+ async addProposedBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
133
155
  if (blocks.length === 0) {
134
156
  return true;
135
157
  }
@@ -145,6 +167,12 @@ export class BlockStore {
145
167
  const previousBlockNumber = await this.getLatestBlockNumber();
146
168
  const previousCheckpointNumber = await this.getLatestCheckpointNumber();
147
169
 
170
+ // Verify we're not overwriting checkpointed blocks
171
+ const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
172
+ if (!opts.force && firstBlockNumber <= lastCheckpointedBlockNumber) {
173
+ throw new CannotOverwriteCheckpointedBlockError(firstBlockNumber, lastCheckpointedBlockNumber);
174
+ }
175
+
148
176
  // Check that the first block number is the expected one
149
177
  if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
150
178
  throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
@@ -182,7 +210,7 @@ export class BlockStore {
182
210
  }
183
211
 
184
212
  // Iterate over blocks array and insert them, checking that the block numbers and indexes are sequential. Also check they are for the correct checkpoint.
185
- let previousBlock: L2BlockNew | undefined = undefined;
213
+ let previousBlock: L2Block | undefined = undefined;
186
214
  for (const block of blocks) {
187
215
  if (!opts.force && previousBlock) {
188
216
  if (previousBlock.number + 1 !== block.number) {
@@ -241,7 +269,7 @@ export class BlockStore {
241
269
  }
242
270
 
243
271
  let previousBlockNumber: BlockNumber | undefined = undefined;
244
- let previousBlock: L2BlockNew | undefined = undefined;
272
+ let previousBlock: L2Block | undefined = undefined;
245
273
 
246
274
  // If we have a previous checkpoint then we need to get the previous block number
247
275
  if (previousCheckpointData !== undefined) {
@@ -322,8 +350,8 @@ export class BlockStore {
322
350
  });
323
351
  }
324
352
 
325
- private async addBlockToDatabase(block: L2BlockNew, checkpointNumber: number, indexWithinCheckpoint: number) {
326
- const blockHash = L2BlockHash.fromField(await block.hash());
353
+ private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
354
+ const blockHash = await block.hash();
327
355
 
328
356
  await this.#blocks.set(block.number, {
329
357
  header: block.header.toBuffer(),
@@ -351,7 +379,7 @@ export class BlockStore {
351
379
  }
352
380
 
353
381
  /** Deletes a block and all associated data (tx effects, indices). */
354
- private async deleteBlock(block: L2BlockNew): Promise<void> {
382
+ private async deleteBlock(block: L2Block): Promise<void> {
355
383
  // Delete the block from the main blocks map
356
384
  await this.#blocks.delete(block.number);
357
385
 
@@ -368,51 +396,48 @@ export class BlockStore {
368
396
  }
369
397
 
370
398
  /**
371
- * Unwinds checkpoints from the database
372
- * @param from - The tip of the chain, passed for verification purposes,
373
- * ensuring that we don't end up deleting something we did not intend
374
- * @param checkpointsToUnwind - The number of checkpoints we are to unwind
375
- * @returns True if the operation is successful
399
+ * Removes all checkpoints with checkpoint number > checkpointNumber.
400
+ * Also removes ALL blocks (both checkpointed and uncheckpointed) after the last block of the given checkpoint.
401
+ * @param checkpointNumber - Remove all checkpoints strictly after this one.
376
402
  */
377
- async unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number) {
403
+ async removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
378
404
  return await this.db.transactionAsync(async () => {
379
- const last = await this.getLatestCheckpointNumber();
380
- if (from !== last) {
381
- throw new Error(`Can only unwind checkpoints from the tip (requested ${from} but current tip is ${last})`);
405
+ const latestCheckpointNumber = await this.getLatestCheckpointNumber();
406
+
407
+ if (checkpointNumber >= latestCheckpointNumber) {
408
+ this.#log.warn(`No checkpoints to remove after ${checkpointNumber} (latest is ${latestCheckpointNumber})`);
409
+ return { blocksRemoved: undefined };
382
410
  }
383
411
 
412
+ // If the proven checkpoint is beyond the target, update it
384
413
  const proven = await this.getProvenCheckpointNumber();
385
- if (from - checkpointsToUnwind < proven) {
386
- await this.setProvenCheckpointNumber(CheckpointNumber(from - checkpointsToUnwind));
414
+ if (proven > checkpointNumber) {
415
+ this.#log.warn(`Updating proven checkpoint ${proven} to last valid checkpoint ${checkpointNumber}`);
416
+ await this.setProvenCheckpointNumber(checkpointNumber);
387
417
  }
388
418
 
389
- for (let i = 0; i < checkpointsToUnwind; i++) {
390
- const checkpointNumber = from - i;
391
- const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
392
-
393
- if (checkpoint === undefined) {
394
- this.#log.warn(`Cannot remove checkpoint ${checkpointNumber} from the store since we don't have it`);
395
- continue;
419
+ // Find the last block number to keep (last block of the given checkpoint, or 0 if no checkpoint)
420
+ let lastBlockToKeep: BlockNumber;
421
+ if (checkpointNumber <= 0) {
422
+ lastBlockToKeep = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
423
+ } else {
424
+ const targetCheckpoint = await this.#checkpoints.getAsync(checkpointNumber);
425
+ if (!targetCheckpoint) {
426
+ throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
396
427
  }
397
- await this.#checkpoints.delete(checkpointNumber);
398
- const maxBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
399
-
400
- for (let blockNumber = checkpoint.startBlock; blockNumber <= maxBlock; blockNumber++) {
401
- const block = await this.getBlock(BlockNumber(blockNumber));
428
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.numBlocks - 1);
429
+ }
402
430
 
403
- if (block === undefined) {
404
- this.#log.warn(`Cannot remove block ${blockNumber} from the store since we don't have it`);
405
- continue;
406
- }
431
+ // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
432
+ const blocksRemoved = await this.removeBlocksAfter(lastBlockToKeep);
407
433
 
408
- await this.deleteBlock(block);
409
- this.#log.debug(
410
- `Unwound block ${blockNumber} ${(await block.hash()).toString()} for checkpoint ${checkpointNumber}`,
411
- );
412
- }
434
+ // Remove all checkpoints after the target
435
+ for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
436
+ await this.#checkpoints.delete(c);
437
+ this.#log.debug(`Removed checkpoint ${c}`);
413
438
  }
414
439
 
415
- return true;
440
+ return { blocksRemoved };
416
441
  });
417
442
  }
418
443
 
@@ -449,7 +474,7 @@ export class BlockStore {
449
474
  return data;
450
475
  }
451
476
 
452
- async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2BlockNew[] | undefined> {
477
+ async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
453
478
  const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
454
479
  if (!checkpoint) {
455
480
  return undefined;
@@ -472,8 +497,8 @@ export class BlockStore {
472
497
  * @param slotNumber - The slot number to search for.
473
498
  * @returns All blocks with the given slot number, in ascending block number order.
474
499
  */
475
- async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2BlockNew[]> {
476
- const blocks: L2BlockNew[] = [];
500
+ async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
501
+ const blocks: L2Block[] = [];
477
502
 
478
503
  // Iterate backwards through all blocks and filter by slot number
479
504
  // This is more efficient since we usually query for the most recent slot
@@ -493,12 +518,13 @@ export class BlockStore {
493
518
 
494
519
  /**
495
520
  * Removes all blocks with block number > blockNumber.
521
+ * Does not remove any associated checkpoints.
496
522
  * @param blockNumber - The block number to remove after.
497
523
  * @returns The removed blocks (for event emission).
498
524
  */
499
- async unwindBlocksAfter(blockNumber: BlockNumber): Promise<L2BlockNew[]> {
525
+ async removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
500
526
  return await this.db.transactionAsync(async () => {
501
- const removedBlocks: L2BlockNew[] = [];
527
+ const removedBlocks: L2Block[] = [];
502
528
 
503
529
  // Get the latest block number to determine the range
504
530
  const latestBlockNumber = await this.getLatestBlockNumber();
@@ -598,7 +624,7 @@ export class BlockStore {
598
624
  }
599
625
  }
600
626
 
601
- async getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
627
+ async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
602
628
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
603
629
  if (blockNumber === undefined) {
604
630
  return undefined;
@@ -620,7 +646,7 @@ export class BlockStore {
620
646
  * @param limit - The number of blocks to return.
621
647
  * @returns The requested L2 blocks
622
648
  */
623
- async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2BlockNew> {
649
+ async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2Block> {
624
650
  for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
625
651
  const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
626
652
  if (block) {
@@ -634,7 +660,7 @@ export class BlockStore {
634
660
  * @param blockNumber - The number of the block to return.
635
661
  * @returns The requested L2 block.
636
662
  */
637
- async getBlock(blockNumber: BlockNumber): Promise<L2BlockNew | undefined> {
663
+ async getBlock(blockNumber: BlockNumber): Promise<L2Block | undefined> {
638
664
  const blockStorage = await this.#blocks.getAsync(blockNumber);
639
665
  if (!blockStorage || !blockStorage.header) {
640
666
  return Promise.resolve(undefined);
@@ -647,7 +673,7 @@ export class BlockStore {
647
673
  * @param blockHash - The hash of the block to return.
648
674
  * @returns The requested L2 block.
649
675
  */
650
- async getBlockByHash(blockHash: L2BlockHash): Promise<L2BlockNew | undefined> {
676
+ async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
651
677
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
652
678
  if (blockNumber === undefined) {
653
679
  return undefined;
@@ -660,7 +686,7 @@ export class BlockStore {
660
686
  * @param archive - The archive root of the block to return.
661
687
  * @returns The requested L2 block.
662
688
  */
663
- async getBlockByArchive(archive: Fr): Promise<L2BlockNew | undefined> {
689
+ async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
664
690
  const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
665
691
  if (blockNumber === undefined) {
666
692
  return undefined;
@@ -673,7 +699,7 @@ export class BlockStore {
673
699
  * @param blockHash - The hash of the block to return.
674
700
  * @returns The requested block header.
675
701
  */
676
- async getBlockHeaderByHash(blockHash: L2BlockHash): Promise<BlockHeader | undefined> {
702
+ async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
677
703
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
678
704
  if (blockNumber === undefined) {
679
705
  return undefined;
@@ -736,7 +762,7 @@ export class BlockStore {
736
762
  private async getBlockFromBlockStorage(
737
763
  blockNumber: number,
738
764
  blockStorage: BlockStorage,
739
- ): Promise<L2BlockNew | undefined> {
765
+ ): Promise<L2Block | undefined> {
740
766
  const header = BlockHeader.fromBuffer(blockStorage.header);
741
767
  const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
742
768
  const blockHash = blockStorage.blockHash;
@@ -760,7 +786,7 @@ export class BlockStore {
760
786
  txEffects.push(deserializeIndexedTxEffect(txEffect).data);
761
787
  }
762
788
  const body = new Body(txEffects);
763
- const block = new L2BlockNew(
789
+ const block = new L2Block(
764
790
  archive,
765
791
  header,
766
792
  body,
@@ -802,13 +828,34 @@ export class BlockStore {
802
828
  return undefined;
803
829
  }
804
830
 
831
+ const blockNumber = BlockNumber(txEffect.l2BlockNumber);
832
+
833
+ // Use existing archiver methods to determine finalization level
834
+ const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
835
+ this.getProvenBlockNumber(),
836
+ this.getCheckpointedL2BlockNumber(),
837
+ this.getFinalizedL2BlockNumber(),
838
+ ]);
839
+
840
+ let status: TxStatus;
841
+ if (blockNumber <= finalizedBlockNumber) {
842
+ status = TxStatus.FINALIZED;
843
+ } else if (blockNumber <= provenBlockNumber) {
844
+ status = TxStatus.PROVEN;
845
+ } else if (blockNumber <= checkpointedBlockNumber) {
846
+ status = TxStatus.CHECKPOINTED;
847
+ } else {
848
+ status = TxStatus.PROPOSED;
849
+ }
850
+
805
851
  return new TxReceipt(
806
852
  txHash,
807
- TxReceipt.statusFromRevertCode(txEffect.data.revertCode),
808
- '',
853
+ status,
854
+ TxReceipt.executionResultFromRevertCode(txEffect.data.revertCode),
855
+ undefined,
809
856
  txEffect.data.transactionFee.toBigInt(),
810
857
  txEffect.l2BlockHash,
811
- BlockNumber(txEffect.l2BlockNumber),
858
+ blockNumber,
812
859
  );
813
860
  }
814
861
 
@@ -28,18 +28,22 @@ export class ContractClassStore {
28
28
  bytecodeCommitment: Fr,
29
29
  blockNumber: number,
30
30
  ): Promise<void> {
31
- await this.#contractClasses.setIfNotExists(
32
- contractClass.id.toString(),
33
- serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
34
- );
35
- await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer());
31
+ await this.db.transactionAsync(async () => {
32
+ await this.#contractClasses.setIfNotExists(
33
+ contractClass.id.toString(),
34
+ serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
35
+ );
36
+ await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer());
37
+ });
36
38
  }
37
39
 
38
40
  async deleteContractClasses(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
39
41
  const restoredContractClass = await this.#contractClasses.getAsync(contractClass.id.toString());
40
42
  if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
41
- await this.#contractClasses.delete(contractClass.id.toString());
42
- await this.#bytecodeCommitments.delete(contractClass.id.toString());
43
+ await this.db.transactionAsync(async () => {
44
+ await this.#contractClasses.delete(contractClass.id.toString());
45
+ await this.#bytecodeCommitments.delete(contractClass.id.toString());
46
+ });
43
47
  }
44
48
  }
45
49
 
@@ -6,7 +6,7 @@ import { createLogger } from '@aztec/foundation/log';
6
6
  import type { AztecAsyncKVStore, CustomRange, StoreSize } from '@aztec/kv-store';
7
7
  import { FunctionSelector } from '@aztec/stdlib/abi';
8
8
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
9
- import { CheckpointedL2Block, L2BlockHash, L2BlockNew, type ValidateCheckpointResult } from '@aztec/stdlib/block';
9
+ import { BlockHash, CheckpointedL2Block, L2Block, type ValidateCheckpointResult } from '@aztec/stdlib/block';
10
10
  import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
11
11
  import type {
12
12
  ContractClassPublic,
@@ -16,6 +16,7 @@ import type {
16
16
  ExecutablePrivateFunctionWithMembershipProof,
17
17
  UtilityFunctionWithMembershipProof,
18
18
  } from '@aztec/stdlib/contract';
19
+ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
19
20
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
20
21
  import type { LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
21
22
  import type { BlockHeader, TxHash, TxReceipt } from '@aztec/stdlib/tx';
@@ -24,7 +25,7 @@ import type { UInt64 } from '@aztec/stdlib/types';
24
25
  import { join } from 'path';
25
26
 
26
27
  import type { InboxMessage } from '../structs/inbox_message.js';
27
- import { BlockStore, type CheckpointData } from './block_store.js';
28
+ import { BlockStore, type CheckpointData, type RemoveCheckpointsResult } from './block_store.js';
28
29
  import { ContractClassStore } from './contract_class_store.js';
29
30
  import { ContractInstanceStore } from './contract_instance_store.js';
30
31
  import { LogStore } from './log_store.js';
@@ -64,8 +65,9 @@ export class KVArchiverDataStore implements ContractDataSource {
64
65
  constructor(
65
66
  private db: AztecAsyncKVStore,
66
67
  logsMaxPageSize: number = 1000,
68
+ l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
67
69
  ) {
68
- this.#blockStore = new BlockStore(db);
70
+ this.#blockStore = new BlockStore(db, l1Constants);
69
71
  this.#logStore = new LogStore(db, this.#blockStore, logsMaxPageSize);
70
72
  this.#messageStore = new MessageStore(db);
71
73
  this.#contractClassStore = new ContractClassStore(db);
@@ -101,6 +103,11 @@ export class KVArchiverDataStore implements ContractDataSource {
101
103
  return this.db.close();
102
104
  }
103
105
 
106
+ /** Computes the finalized block number based on the proven block number. */
107
+ getFinalizedL2BlockNumber(): Promise<BlockNumber> {
108
+ return this.#blockStore.getFinalizedL2BlockNumber();
109
+ }
110
+
104
111
  /** Looks up a public function name given a selector. */
105
112
  getDebugFunctionName(_address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
106
113
  return Promise.resolve(this.functionNames.get(selector.toString()));
@@ -228,12 +235,14 @@ export class KVArchiverDataStore implements ContractDataSource {
228
235
  }
229
236
 
230
237
  /**
231
- * Append new blocks to the store's list.
232
- * @param blocks - The L2 blocks to be added to the store and the last processed L1 block.
238
+ * Append new proposed blocks to the store's list.
239
+ * These are uncheckpointed blocks that have been proposed by the sequencer but not yet included in a checkpoint on L1.
240
+ * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
241
+ * @param blocks - The proposed L2 blocks to be added to the store.
233
242
  * @returns True if the operation is successful.
234
243
  */
235
- addBlocks(blocks: L2BlockNew[], opts: { force?: boolean; checkpointNumber?: number } = {}): Promise<boolean> {
236
- return this.#blockStore.addBlocks(blocks, opts);
244
+ addProposedBlocks(blocks: L2Block[], opts: { force?: boolean; checkpointNumber?: number } = {}): Promise<boolean> {
245
+ return this.#blockStore.addProposedBlocks(blocks, opts);
237
246
  }
238
247
 
239
248
  /**
@@ -254,14 +263,12 @@ export class KVArchiverDataStore implements ContractDataSource {
254
263
  }
255
264
 
256
265
  /**
257
- * Unwinds checkpoints from the database
258
- * @param from - The tip of the chain, passed for verification purposes,
259
- * ensuring that we don't end up deleting something we did not intend
260
- * @param checkpointsToUnwind - The number of checkpoints we are to unwind
261
- * @returns True if the operation is successful
266
+ * Removes all checkpoints with checkpoint number > checkpointNumber.
267
+ * Also removes ALL blocks (both checkpointed and uncheckpointed) after the last block of the given checkpoint.
268
+ * @param checkpointNumber - Remove all checkpoints strictly after this one.
262
269
  */
263
- unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
264
- return this.#blockStore.unwindCheckpoints(from, checkpointsToUnwind);
270
+ removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
271
+ return this.#blockStore.removeCheckpointsAfter(checkpointNumber);
265
272
  }
266
273
 
267
274
  /**
@@ -284,7 +291,7 @@ export class KVArchiverDataStore implements ContractDataSource {
284
291
  * Returns the block for the given hash, or undefined if not exists.
285
292
  * @param blockHash - The block hash to return.
286
293
  */
287
- getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
294
+ getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
288
295
  return this.#blockStore.getCheckpointedBlockByHash(blockHash);
289
296
  }
290
297
  /**
@@ -298,21 +305,21 @@ export class KVArchiverDataStore implements ContractDataSource {
298
305
  * Returns the block for the given number, or undefined if not exists.
299
306
  * @param number - The block number to return.
300
307
  */
301
- getBlock(number: BlockNumber): Promise<L2BlockNew | undefined> {
308
+ getBlock(number: BlockNumber): Promise<L2Block | undefined> {
302
309
  return this.#blockStore.getBlock(number);
303
310
  }
304
311
  /**
305
312
  * Returns the block for the given hash, or undefined if not exists.
306
313
  * @param blockHash - The block hash to return.
307
314
  */
308
- getBlockByHash(blockHash: Fr): Promise<L2BlockNew | undefined> {
309
- return this.#blockStore.getBlockByHash(L2BlockHash.fromField(blockHash));
315
+ getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
316
+ return this.#blockStore.getBlockByHash(blockHash);
310
317
  }
311
318
  /**
312
319
  * Returns the block for the given archive root, or undefined if not exists.
313
320
  * @param archive - The archive root to return.
314
321
  */
315
- getBlockByArchive(archive: Fr): Promise<L2BlockNew | undefined> {
322
+ getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
316
323
  return this.#blockStore.getBlockByArchive(archive);
317
324
  }
318
325
 
@@ -322,7 +329,7 @@ export class KVArchiverDataStore implements ContractDataSource {
322
329
  * @param limit - The number of blocks to return.
323
330
  * @returns The requested L2 blocks.
324
331
  */
325
- getBlocks(from: BlockNumber, limit: number): Promise<L2BlockNew[]> {
332
+ getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
326
333
  return toArray(this.#blockStore.getBlocks(from, limit));
327
334
  }
328
335
 
@@ -350,8 +357,8 @@ export class KVArchiverDataStore implements ContractDataSource {
350
357
  * Returns the block header for the given hash, or undefined if not exists.
351
358
  * @param blockHash - The block hash to return.
352
359
  */
353
- getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
354
- return this.#blockStore.getBlockHeaderByHash(L2BlockHash.fromField(blockHash));
360
+ getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
361
+ return this.#blockStore.getBlockHeaderByHash(blockHash);
355
362
  }
356
363
 
357
364
  /**
@@ -385,11 +392,11 @@ export class KVArchiverDataStore implements ContractDataSource {
385
392
  * @param blocks - The blocks for which to add the logs.
386
393
  * @returns True if the operation is successful.
387
394
  */
388
- addLogs(blocks: L2BlockNew[]): Promise<boolean> {
395
+ addLogs(blocks: L2Block[]): Promise<boolean> {
389
396
  return this.#logStore.addLogs(blocks);
390
397
  }
391
398
 
392
- deleteLogs(blocks: L2BlockNew[]): Promise<boolean> {
399
+ deleteLogs(blocks: L2Block[]): Promise<boolean> {
393
400
  return this.#logStore.deleteLogs(blocks);
394
401
  }
395
402
 
@@ -434,24 +441,33 @@ export class KVArchiverDataStore implements ContractDataSource {
434
441
  }
435
442
 
436
443
  /**
437
- * Gets all private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty
444
+ * Gets private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty
438
445
  * array implies no logs match that tag.
446
+ * @param tags - The tags to search for.
447
+ * @param page - The page number (0-indexed) for pagination. Returns at most 10 logs per tag per page.
439
448
  */
440
- getPrivateLogsByTags(tags: SiloedTag[]): Promise<TxScopedL2Log[][]> {
449
+ getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
441
450
  try {
442
- return this.#logStore.getPrivateLogsByTags(tags);
451
+ return this.#logStore.getPrivateLogsByTags(tags, page);
443
452
  } catch (err) {
444
453
  return Promise.reject(err);
445
454
  }
446
455
  }
447
456
 
448
457
  /**
449
- * Gets all public logs that match any of the `tags` from the specified contract. For each tag, an array of matching
458
+ * Gets public logs that match any of the `tags` from the specified contract. For each tag, an array of matching
450
459
  * logs is returned. An empty array implies no logs match that tag.
451
- */
452
- getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise<TxScopedL2Log[][]> {
460
+ * @param contractAddress - The contract address to search logs for.
461
+ * @param tags - The tags to search for.
462
+ * @param page - The page number (0-indexed) for pagination. Returns at most 10 logs per tag per page.
463
+ */
464
+ getPublicLogsByTagsFromContract(
465
+ contractAddress: AztecAddress,
466
+ tags: Tag[],
467
+ page?: number,
468
+ ): Promise<TxScopedL2Log[][]> {
453
469
  try {
454
- return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags);
470
+ return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags, page);
455
471
  } catch (err) {
456
472
  return Promise.reject(err);
457
473
  }
@@ -589,7 +605,7 @@ export class KVArchiverDataStore implements ContractDataSource {
589
605
  * @param checkpointNumber Retrieves all blocks for the given checkpoint
590
606
  * @returns The collection of blocks for the requested checkpoint if available (undefined otherwise)
591
607
  */
592
- getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2BlockNew[] | undefined> {
608
+ getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
593
609
  return this.#blockStore.getBlocksForCheckpoint(checkpointNumber);
594
610
  }
595
611
 
@@ -607,16 +623,17 @@ export class KVArchiverDataStore implements ContractDataSource {
607
623
  * @param slotNumber - The slot number to search for.
608
624
  * @returns All blocks with the given slot number.
609
625
  */
610
- getBlocksForSlot(slotNumber: SlotNumber): Promise<L2BlockNew[]> {
626
+ getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
611
627
  return this.#blockStore.getBlocksForSlot(slotNumber);
612
628
  }
613
629
 
614
630
  /**
615
631
  * Removes all blocks with block number > blockNumber.
632
+ * Does not remove any associated checkpoints.
616
633
  * @param blockNumber - The block number to remove after.
617
634
  * @returns The removed blocks (for event emission).
618
635
  */
619
- removeBlocksAfter(blockNumber: BlockNumber): Promise<L2BlockNew[]> {
620
- return this.#blockStore.unwindBlocksAfter(blockNumber);
636
+ removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
637
+ return this.#blockStore.removeBlocksAfter(blockNumber);
621
638
  }
622
639
  }