@aztec/archiver 0.0.1-commit.b468ad8 → 0.0.1-commit.b64cb54f6

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 (86) hide show
  1. package/dest/archiver.d.ts +7 -4
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +62 -110
  4. package/dest/errors.d.ts +7 -9
  5. package/dest/errors.d.ts.map +1 -1
  6. package/dest/errors.js +9 -14
  7. package/dest/factory.d.ts +3 -4
  8. package/dest/factory.d.ts.map +1 -1
  9. package/dest/factory.js +15 -13
  10. package/dest/index.d.ts +2 -1
  11. package/dest/index.d.ts.map +1 -1
  12. package/dest/index.js +1 -0
  13. package/dest/l1/bin/retrieve-calldata.js +36 -33
  14. package/dest/l1/calldata_retriever.d.ts +73 -50
  15. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  16. package/dest/l1/calldata_retriever.js +190 -259
  17. package/dest/l1/data_retrieval.d.ts +9 -9
  18. package/dest/l1/data_retrieval.d.ts.map +1 -1
  19. package/dest/l1/data_retrieval.js +21 -19
  20. package/dest/l1/spire_proposer.d.ts +5 -5
  21. package/dest/l1/spire_proposer.d.ts.map +1 -1
  22. package/dest/l1/spire_proposer.js +9 -17
  23. package/dest/modules/data_source_base.d.ts +10 -5
  24. package/dest/modules/data_source_base.d.ts.map +1 -1
  25. package/dest/modules/data_source_base.js +29 -73
  26. package/dest/modules/data_store_updater.d.ts +22 -7
  27. package/dest/modules/data_store_updater.d.ts.map +1 -1
  28. package/dest/modules/data_store_updater.js +69 -29
  29. package/dest/modules/instrumentation.d.ts +15 -2
  30. package/dest/modules/instrumentation.d.ts.map +1 -1
  31. package/dest/modules/instrumentation.js +19 -2
  32. package/dest/modules/l1_synchronizer.d.ts +5 -8
  33. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  34. package/dest/modules/l1_synchronizer.js +41 -10
  35. package/dest/store/block_store.d.ts +29 -26
  36. package/dest/store/block_store.d.ts.map +1 -1
  37. package/dest/store/block_store.js +130 -78
  38. package/dest/store/kv_archiver_store.d.ts +34 -11
  39. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  40. package/dest/store/kv_archiver_store.js +39 -9
  41. package/dest/store/l2_tips_cache.d.ts +19 -0
  42. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  43. package/dest/store/l2_tips_cache.js +89 -0
  44. package/dest/store/log_store.d.ts +1 -1
  45. package/dest/store/log_store.d.ts.map +1 -1
  46. package/dest/store/log_store.js +55 -35
  47. package/dest/store/message_store.js +1 -1
  48. package/dest/test/fake_l1_state.d.ts +13 -1
  49. package/dest/test/fake_l1_state.d.ts.map +1 -1
  50. package/dest/test/fake_l1_state.js +84 -20
  51. package/dest/test/mock_archiver.d.ts +1 -1
  52. package/dest/test/mock_archiver.d.ts.map +1 -1
  53. package/dest/test/mock_archiver.js +3 -2
  54. package/dest/test/mock_l2_block_source.d.ts +21 -5
  55. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  56. package/dest/test/mock_l2_block_source.js +132 -86
  57. package/dest/test/mock_structs.d.ts +4 -1
  58. package/dest/test/mock_structs.d.ts.map +1 -1
  59. package/dest/test/mock_structs.js +13 -1
  60. package/dest/test/noop_l1_archiver.d.ts +4 -1
  61. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  62. package/dest/test/noop_l1_archiver.js +5 -1
  63. package/package.json +13 -13
  64. package/src/archiver.ts +74 -130
  65. package/src/errors.ts +10 -24
  66. package/src/factory.ts +29 -14
  67. package/src/index.ts +1 -0
  68. package/src/l1/README.md +25 -68
  69. package/src/l1/bin/retrieve-calldata.ts +46 -39
  70. package/src/l1/calldata_retriever.ts +249 -379
  71. package/src/l1/data_retrieval.ts +23 -25
  72. package/src/l1/spire_proposer.ts +7 -15
  73. package/src/modules/data_source_base.ts +56 -95
  74. package/src/modules/data_store_updater.ts +71 -30
  75. package/src/modules/instrumentation.ts +29 -2
  76. package/src/modules/l1_synchronizer.ts +46 -14
  77. package/src/store/block_store.ts +157 -105
  78. package/src/store/kv_archiver_store.ts +62 -12
  79. package/src/store/l2_tips_cache.ts +89 -0
  80. package/src/store/log_store.ts +93 -31
  81. package/src/store/message_store.ts +1 -1
  82. package/src/test/fake_l1_state.ts +110 -21
  83. package/src/test/mock_archiver.ts +3 -2
  84. package/src/test/mock_l2_block_source.ts +173 -81
  85. package/src/test/mock_structs.ts +20 -6
  86. package/src/test/noop_l1_archiver.ts +7 -1
@@ -9,6 +9,7 @@ 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
+ type BlockData,
12
13
  BlockHash,
13
14
  Body,
14
15
  CheckpointedL2Block,
@@ -18,8 +19,8 @@ import {
18
19
  deserializeValidateCheckpointResult,
19
20
  serializeValidateCheckpointResult,
20
21
  } from '@aztec/stdlib/block';
21
- import { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
22
- import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
22
+ import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
23
+ import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
23
24
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
24
25
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
25
26
  import {
@@ -34,15 +35,14 @@ import {
34
35
  } from '@aztec/stdlib/tx';
35
36
 
36
37
  import {
38
+ BlockAlreadyCheckpointedError,
37
39
  BlockArchiveNotConsistentError,
38
40
  BlockIndexNotSequentialError,
39
41
  BlockNotFoundError,
40
42
  BlockNumberNotSequentialError,
41
43
  CannotOverwriteCheckpointedBlockError,
42
44
  CheckpointNotFoundError,
43
- CheckpointNumberNotConsistentError,
44
45
  CheckpointNumberNotSequentialError,
45
- InitialBlockNumberNotSequentialError,
46
46
  InitialCheckpointNumberNotSequentialError,
47
47
  } from '../errors.js';
48
48
 
@@ -61,23 +61,14 @@ type BlockStorage = {
61
61
  type CheckpointStorage = {
62
62
  header: Buffer;
63
63
  archive: Buffer;
64
+ checkpointOutHash: Buffer;
64
65
  checkpointNumber: number;
65
66
  startBlock: number;
66
- numBlocks: number;
67
+ blockCount: number;
67
68
  l1: Buffer;
68
69
  attestations: Buffer[];
69
70
  };
70
71
 
71
- export type CheckpointData = {
72
- checkpointNumber: CheckpointNumber;
73
- header: CheckpointHeader;
74
- archive: AppendOnlyTreeSnapshot;
75
- startBlock: number;
76
- numBlocks: number;
77
- l1: L1PublishedData;
78
- attestations: Buffer[];
79
- };
80
-
81
72
  export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
82
73
 
83
74
  /**
@@ -90,6 +81,9 @@ export class BlockStore {
90
81
  /** Map checkpoint number to checkpoint data */
91
82
  #checkpoints: AztecAsyncMap<number, CheckpointStorage>;
92
83
 
84
+ /** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
85
+ #slotToCheckpoint: AztecAsyncMap<number, number>;
86
+
93
87
  /** Map block hash to list of tx hashes */
94
88
  #blockTxs: AztecAsyncMap<string, Buffer>;
95
89
 
@@ -102,6 +96,9 @@ export class BlockStore {
102
96
  /** Stores last proven checkpoint */
103
97
  #lastProvenCheckpoint: AztecAsyncSingleton<number>;
104
98
 
99
+ /** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
100
+ #lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
101
+
105
102
  /** Stores the pending chain validation status */
106
103
  #pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
107
104
 
@@ -116,10 +113,7 @@ export class BlockStore {
116
113
 
117
114
  #log = createLogger('archiver:block_store');
118
115
 
119
- constructor(
120
- private db: AztecAsyncKVStore,
121
- private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
122
- ) {
116
+ constructor(private db: AztecAsyncKVStore) {
123
117
  this.#blocks = db.openMap('archiver_blocks');
124
118
  this.#blockTxs = db.openMap('archiver_block_txs');
125
119
  this.#txEffects = db.openMap('archiver_tx_effects');
@@ -128,40 +122,42 @@ export class BlockStore {
128
122
  this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
129
123
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
130
124
  this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
125
+ this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
131
126
  this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
132
127
  this.#checkpoints = db.openMap('archiver_checkpoints');
128
+ this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
133
129
  }
134
130
 
135
131
  /**
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
132
+ * Returns the finalized L2 block number. An L2 block is finalized when it was proven
133
+ * in an L1 block that has itself been finalized on Ethereum.
140
134
  * @returns The finalized block number.
141
135
  */
142
136
  async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
143
- const provenBlockNumber = await this.getProvenBlockNumber();
144
- return BlockNumber(Math.max(provenBlockNumber - this.l1Constants.epochDuration * 2, 0));
137
+ const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
138
+ if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
139
+ return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
140
+ }
141
+ const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
142
+ if (!checkpointStorage) {
143
+ throw new CheckpointNotFoundError(finalizedCheckpointNumber);
144
+ }
145
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
145
146
  }
146
147
 
147
148
  /**
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.
149
+ * Append a new proposed block to the store.
150
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
150
151
  * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
151
- * @param blocks - The proposed L2 blocks to be added to the store.
152
+ * @param block - The proposed L2 block to be added to the store.
152
153
  * @returns True if the operation is successful.
153
154
  */
154
- async addProposedBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
155
- if (blocks.length === 0) {
156
- return true;
157
- }
158
-
155
+ async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
159
156
  return await this.db.transactionAsync(async () => {
160
- // Check that the block immediately before the first block to be added is present in the store.
161
- const firstBlockNumber = blocks[0].number;
162
- const firstBlockCheckpointNumber = blocks[0].checkpointNumber;
163
- const firstBlockIndex = blocks[0].indexWithinCheckpoint;
164
- const firstBlockLastArchive = blocks[0].header.lastArchive.root;
157
+ const blockNumber = block.number;
158
+ const blockCheckpointNumber = block.checkpointNumber;
159
+ const blockIndex = block.indexWithinCheckpoint;
160
+ const blockLastArchive = block.header.lastArchive.root;
165
161
 
166
162
  // Extract the latest block and checkpoint numbers
167
163
  const previousBlockNumber = await this.getLatestBlockNumber();
@@ -169,71 +165,52 @@ export class BlockStore {
169
165
 
170
166
  // Verify we're not overwriting checkpointed blocks
171
167
  const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
172
- if (!opts.force && firstBlockNumber <= lastCheckpointedBlockNumber) {
173
- throw new CannotOverwriteCheckpointedBlockError(firstBlockNumber, lastCheckpointedBlockNumber);
168
+ if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
169
+ // Check if the proposed block matches the already-checkpointed one
170
+ const existingBlock = await this.getBlock(BlockNumber(blockNumber));
171
+ if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
172
+ throw new BlockAlreadyCheckpointedError(blockNumber);
173
+ }
174
+ throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
174
175
  }
175
176
 
176
- // Check that the first block number is the expected one
177
- if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
178
- throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
177
+ // Check that the block number is the expected one
178
+ if (!opts.force && previousBlockNumber !== blockNumber - 1) {
179
+ throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
179
180
  }
180
181
 
181
182
  // The same check as above but for checkpoints
182
- if (!opts.force && previousCheckpointNumber !== firstBlockCheckpointNumber - 1) {
183
- throw new InitialCheckpointNumberNotSequentialError(firstBlockCheckpointNumber, previousCheckpointNumber);
183
+ if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
184
+ throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
184
185
  }
185
186
 
186
187
  // Extract the previous block if there is one and see if it is for the same checkpoint or not
187
188
  const previousBlockResult = await this.getBlock(previousBlockNumber);
188
189
 
189
- let expectedFirstblockIndex = 0;
190
+ let expectedBlockIndex = 0;
190
191
  let previousBlockIndex: number | undefined = undefined;
191
192
  if (previousBlockResult !== undefined) {
192
- if (previousBlockResult.checkpointNumber === firstBlockCheckpointNumber) {
193
+ if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
193
194
  // The previous block is for the same checkpoint, therefore our index should follow it
194
195
  previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
195
- expectedFirstblockIndex = previousBlockIndex + 1;
196
+ expectedBlockIndex = previousBlockIndex + 1;
196
197
  }
197
- if (!previousBlockResult.archive.root.equals(firstBlockLastArchive)) {
198
+ if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
198
199
  throw new BlockArchiveNotConsistentError(
199
- firstBlockNumber,
200
+ blockNumber,
200
201
  previousBlockResult.number,
201
- firstBlockLastArchive,
202
+ blockLastArchive,
202
203
  previousBlockResult.archive.root,
203
204
  );
204
205
  }
205
206
  }
206
207
 
207
- // Now check that the first block has the expected index value
208
- if (!opts.force && expectedFirstblockIndex !== firstBlockIndex) {
209
- throw new BlockIndexNotSequentialError(firstBlockIndex, previousBlockIndex);
208
+ // Now check that the block has the expected index value
209
+ if (!opts.force && expectedBlockIndex !== blockIndex) {
210
+ throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
210
211
  }
211
212
 
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.
213
- let previousBlock: L2Block | undefined = undefined;
214
- for (const block of blocks) {
215
- if (!opts.force && previousBlock) {
216
- if (previousBlock.number + 1 !== block.number) {
217
- throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
218
- }
219
- if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
220
- throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
221
- }
222
- if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
223
- throw new BlockArchiveNotConsistentError(
224
- block.number,
225
- previousBlock.number,
226
- block.header.lastArchive.root,
227
- previousBlock.archive.root,
228
- );
229
- }
230
- }
231
- if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
232
- throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
233
- }
234
- previousBlock = block;
235
- await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
236
- }
213
+ await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
237
214
 
238
215
  return true;
239
216
  });
@@ -273,7 +250,7 @@ export class BlockStore {
273
250
 
274
251
  // If we have a previous checkpoint then we need to get the previous block number
275
252
  if (previousCheckpointData !== undefined) {
276
- previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.numBlocks - 1);
253
+ previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
277
254
  previousBlock = await this.getBlock(previousBlockNumber);
278
255
  if (previousBlock === undefined) {
279
256
  // We should be able to get the required previous block
@@ -337,12 +314,16 @@ export class BlockStore {
337
314
  await this.#checkpoints.set(checkpoint.checkpoint.number, {
338
315
  header: checkpoint.checkpoint.header.toBuffer(),
339
316
  archive: checkpoint.checkpoint.archive.toBuffer(),
317
+ checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
340
318
  l1: checkpoint.l1.toBuffer(),
341
319
  attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
342
320
  checkpointNumber: checkpoint.checkpoint.number,
343
321
  startBlock: checkpoint.checkpoint.blocks[0].number,
344
- numBlocks: checkpoint.checkpoint.blocks.length,
322
+ blockCount: checkpoint.checkpoint.blocks.length,
345
323
  });
324
+
325
+ // Update slot-to-checkpoint index
326
+ await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
346
327
  }
347
328
 
348
329
  await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
@@ -425,7 +406,7 @@ export class BlockStore {
425
406
  if (!targetCheckpoint) {
426
407
  throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
427
408
  }
428
- lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.numBlocks - 1);
409
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
429
410
  }
430
411
 
431
412
  // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
@@ -433,6 +414,11 @@ export class BlockStore {
433
414
 
434
415
  // Remove all checkpoints after the target
435
416
  for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
417
+ const checkpointStorage = await this.#checkpoints.getAsync(c);
418
+ if (checkpointStorage) {
419
+ const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
420
+ await this.#slotToCheckpoint.delete(slotNumber);
421
+ }
436
422
  await this.#checkpoints.delete(c);
437
423
  this.#log.debug(`Removed checkpoint ${c}`);
438
424
  }
@@ -461,17 +447,32 @@ export class BlockStore {
461
447
  return checkpoints;
462
448
  }
463
449
 
464
- private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage) {
465
- const data: CheckpointData = {
450
+ /** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
451
+ async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
452
+ const result: CheckpointData[] = [];
453
+ for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
454
+ start: startSlot,
455
+ end: endSlot + 1,
456
+ })) {
457
+ const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
458
+ if (checkpointStorage) {
459
+ result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
460
+ }
461
+ }
462
+ return result;
463
+ }
464
+
465
+ private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
466
+ return {
466
467
  header: CheckpointHeader.fromBuffer(checkpointStorage.header),
467
468
  archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
469
+ checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
468
470
  checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
469
- startBlock: checkpointStorage.startBlock,
470
- numBlocks: checkpointStorage.numBlocks,
471
+ startBlock: BlockNumber(checkpointStorage.startBlock),
472
+ blockCount: checkpointStorage.blockCount,
471
473
  l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
472
- attestations: checkpointStorage.attestations,
474
+ attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
473
475
  };
474
- return data;
475
476
  }
476
477
 
477
478
  async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
@@ -483,7 +484,7 @@ export class BlockStore {
483
484
  const blocksForCheckpoint = await toArray(
484
485
  this.#blocks.entriesAsync({
485
486
  start: checkpoint.startBlock,
486
- end: checkpoint.startBlock + checkpoint.numBlocks,
487
+ end: checkpoint.startBlock + checkpoint.blockCount,
487
488
  }),
488
489
  );
489
490
 
@@ -556,7 +557,7 @@ export class BlockStore {
556
557
  if (!checkpointStorage) {
557
558
  throw new CheckpointNotFoundError(provenCheckpointNumber);
558
559
  } else {
559
- return BlockNumber(checkpointStorage.startBlock + checkpointStorage.numBlocks - 1);
560
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
560
561
  }
561
562
  }
562
563
 
@@ -655,6 +656,32 @@ export class BlockStore {
655
656
  }
656
657
  }
657
658
 
659
+ /**
660
+ * Gets block metadata (without tx data) by block number.
661
+ * @param blockNumber - The number of the block to return.
662
+ * @returns The requested block data.
663
+ */
664
+ async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
665
+ const blockStorage = await this.#blocks.getAsync(blockNumber);
666
+ if (!blockStorage || !blockStorage.header) {
667
+ return undefined;
668
+ }
669
+ return this.getBlockDataFromBlockStorage(blockStorage);
670
+ }
671
+
672
+ /**
673
+ * Gets block metadata (without tx data) by archive root.
674
+ * @param archive - The archive root of the block to return.
675
+ * @returns The requested block data.
676
+ */
677
+ async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
678
+ const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
679
+ if (blockNumber === undefined) {
680
+ return undefined;
681
+ }
682
+ return this.getBlockData(BlockNumber(blockNumber));
683
+ }
684
+
658
685
  /**
659
686
  * Gets an L2 block.
660
687
  * @param blockNumber - The number of the block to return.
@@ -759,15 +786,24 @@ export class BlockStore {
759
786
  }
760
787
  }
761
788
 
789
+ private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
790
+ return {
791
+ header: BlockHeader.fromBuffer(blockStorage.header),
792
+ archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
793
+ blockHash: Fr.fromBuffer(blockStorage.blockHash),
794
+ checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
795
+ indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
796
+ };
797
+ }
798
+
762
799
  private async getBlockFromBlockStorage(
763
800
  blockNumber: number,
764
801
  blockStorage: BlockStorage,
765
802
  ): Promise<L2Block | undefined> {
766
- const header = BlockHeader.fromBuffer(blockStorage.header);
767
- const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
768
- const blockHash = blockStorage.blockHash;
769
- header.setHash(Fr.fromBuffer(blockHash));
770
- const blockHashString = bufferToHex(blockHash);
803
+ const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
804
+ this.getBlockDataFromBlockStorage(blockStorage);
805
+ header.setHash(blockHash);
806
+ const blockHashString = bufferToHex(blockStorage.blockHash);
771
807
  const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
772
808
  if (blockTxsBuffer === undefined) {
773
809
  this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
@@ -786,13 +822,7 @@ export class BlockStore {
786
822
  txEffects.push(deserializeIndexedTxEffect(txEffect).data);
787
823
  }
788
824
  const body = new Body(txEffects);
789
- const block = new L2Block(
790
- archive,
791
- header,
792
- body,
793
- CheckpointNumber(blockStorage.checkpointNumber!),
794
- IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
795
- );
825
+ const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
796
826
 
797
827
  if (block.number !== blockNumber) {
798
828
  throw new Error(
@@ -822,7 +852,10 @@ export class BlockStore {
822
852
  * @param txHash - The hash of a tx we try to get the receipt for.
823
853
  * @returns The requested tx receipt (or undefined if not found).
824
854
  */
825
- async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
855
+ async getSettledTxReceipt(
856
+ txHash: TxHash,
857
+ l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
858
+ ): Promise<TxReceipt | undefined> {
826
859
  const txEffect = await this.getTxEffect(txHash);
827
860
  if (!txEffect) {
828
861
  return undefined;
@@ -831,10 +864,11 @@ export class BlockStore {
831
864
  const blockNumber = BlockNumber(txEffect.l2BlockNumber);
832
865
 
833
866
  // Use existing archiver methods to determine finalization level
834
- const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
867
+ const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
835
868
  this.getProvenBlockNumber(),
836
869
  this.getCheckpointedL2BlockNumber(),
837
870
  this.getFinalizedL2BlockNumber(),
871
+ this.getBlockData(blockNumber),
838
872
  ]);
839
873
 
840
874
  let status: TxStatus;
@@ -848,6 +882,9 @@ export class BlockStore {
848
882
  status = TxStatus.PROPOSED;
849
883
  }
850
884
 
885
+ const epochNumber =
886
+ blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
887
+
851
888
  return new TxReceipt(
852
889
  txHash,
853
890
  status,
@@ -856,6 +893,7 @@ export class BlockStore {
856
893
  txEffect.data.transactionFee.toBigInt(),
857
894
  txEffect.l2BlockHash,
858
895
  blockNumber,
896
+ epochNumber,
859
897
  );
860
898
  }
861
899
 
@@ -892,7 +930,7 @@ export class BlockStore {
892
930
  if (!checkpoint) {
893
931
  return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
894
932
  }
895
- return BlockNumber(checkpoint.startBlock + checkpoint.numBlocks - 1);
933
+ return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
896
934
  }
897
935
 
898
936
  async getLatestL2BlockNumber(): Promise<BlockNumber> {
@@ -927,6 +965,20 @@ export class BlockStore {
927
965
  return result;
928
966
  }
929
967
 
968
+ async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
969
+ const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
970
+ this.getLatestCheckpointNumber(),
971
+ this.#lastFinalizedCheckpoint.getAsync(),
972
+ ]);
973
+ return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
974
+ ? latestCheckpointNumber
975
+ : CheckpointNumber(finalizedCheckpointNumber ?? 0);
976
+ }
977
+
978
+ setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
979
+ return this.#lastFinalizedCheckpoint.set(checkpointNumber);
980
+ }
981
+
930
982
  #computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
931
983
  if (limit < 1) {
932
984
  throw new Error(`Invalid limit: ${limit}`);
@@ -6,8 +6,14 @@ 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 { BlockHash, CheckpointedL2Block, L2Block, type ValidateCheckpointResult } from '@aztec/stdlib/block';
10
- import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
9
+ import {
10
+ type BlockData,
11
+ BlockHash,
12
+ CheckpointedL2Block,
13
+ L2Block,
14
+ type ValidateCheckpointResult,
15
+ } from '@aztec/stdlib/block';
16
+ import type { CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
11
17
  import type {
12
18
  ContractClassPublic,
13
19
  ContractDataSource,
@@ -25,7 +31,7 @@ import type { UInt64 } from '@aztec/stdlib/types';
25
31
  import { join } from 'path';
26
32
 
27
33
  import type { InboxMessage } from '../structs/inbox_message.js';
28
- import { BlockStore, type CheckpointData, type RemoveCheckpointsResult } from './block_store.js';
34
+ import { BlockStore, type RemoveCheckpointsResult } from './block_store.js';
29
35
  import { ContractClassStore } from './contract_class_store.js';
30
36
  import { ContractInstanceStore } from './contract_instance_store.js';
31
37
  import { LogStore } from './log_store.js';
@@ -65,15 +71,19 @@ export class KVArchiverDataStore implements ContractDataSource {
65
71
  constructor(
66
72
  private db: AztecAsyncKVStore,
67
73
  logsMaxPageSize: number = 1000,
68
- l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
69
74
  ) {
70
- this.#blockStore = new BlockStore(db, l1Constants);
75
+ this.#blockStore = new BlockStore(db);
71
76
  this.#logStore = new LogStore(db, this.#blockStore, logsMaxPageSize);
72
77
  this.#messageStore = new MessageStore(db);
73
78
  this.#contractClassStore = new ContractClassStore(db);
74
79
  this.#contractInstanceStore = new ContractInstanceStore(db);
75
80
  }
76
81
 
82
+ /** Returns the underlying block store. Used by L2TipsCache. */
83
+ get blockStore(): BlockStore {
84
+ return this.#blockStore;
85
+ }
86
+
77
87
  /** Opens a new transaction to the underlying store and runs all operations within it. */
78
88
  public transactionAsync<T>(callback: () => Promise<T>): Promise<T> {
79
89
  return this.db.transactionAsync(callback);
@@ -235,14 +245,14 @@ export class KVArchiverDataStore implements ContractDataSource {
235
245
  }
236
246
 
237
247
  /**
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.
248
+ * Append a new proposed block to the store.
249
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
240
250
  * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
241
- * @param blocks - The proposed L2 blocks to be added to the store.
251
+ * @param block - The proposed L2 block to be added to the store.
242
252
  * @returns True if the operation is successful.
243
253
  */
244
- addProposedBlocks(blocks: L2Block[], opts: { force?: boolean; checkpointNumber?: number } = {}): Promise<boolean> {
245
- return this.#blockStore.addProposedBlocks(blocks, opts);
254
+ addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
255
+ return this.#blockStore.addProposedBlock(block, opts);
246
256
  }
247
257
 
248
258
  /**
@@ -369,6 +379,22 @@ export class KVArchiverDataStore implements ContractDataSource {
369
379
  return this.#blockStore.getBlockHeaderByArchive(archive);
370
380
  }
371
381
 
382
+ /**
383
+ * Gets block metadata (without tx data) by block number.
384
+ * @param blockNumber - The block number to return.
385
+ */
386
+ getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
387
+ return this.#blockStore.getBlockData(blockNumber);
388
+ }
389
+
390
+ /**
391
+ * Gets block metadata (without tx data) by archive root.
392
+ * @param archive - The archive root to return.
393
+ */
394
+ getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
395
+ return this.#blockStore.getBlockDataByArchive(archive);
396
+ }
397
+
372
398
  /**
373
399
  * Gets a tx effect.
374
400
  * @param txHash - The hash of the tx corresponding to the tx effect.
@@ -383,8 +409,11 @@ export class KVArchiverDataStore implements ContractDataSource {
383
409
  * @param txHash - The hash of a tx we try to get the receipt for.
384
410
  * @returns The requested tx receipt (or undefined if not found).
385
411
  */
386
- getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
387
- return this.#blockStore.getSettledTxReceipt(txHash);
412
+ getSettledTxReceipt(
413
+ txHash: TxHash,
414
+ l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
415
+ ): Promise<TxReceipt | undefined> {
416
+ return this.#blockStore.getSettledTxReceipt(txHash, l1Constants);
388
417
  }
389
418
 
390
419
  /**
@@ -515,6 +544,22 @@ export class KVArchiverDataStore implements ContractDataSource {
515
544
  await this.#blockStore.setProvenCheckpointNumber(checkpointNumber);
516
545
  }
517
546
 
547
+ /**
548
+ * Gets the number of the latest finalized checkpoint processed.
549
+ * @returns The number of the latest finalized checkpoint processed.
550
+ */
551
+ getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
552
+ return this.#blockStore.getFinalizedCheckpointNumber();
553
+ }
554
+
555
+ /**
556
+ * Stores the number of the latest finalized checkpoint processed.
557
+ * @param checkpointNumber - The number of the latest finalized checkpoint processed.
558
+ */
559
+ async setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
560
+ await this.#blockStore.setFinalizedCheckpointNumber(checkpointNumber);
561
+ }
562
+
518
563
  async setBlockSynchedL1BlockNumber(l1BlockNumber: bigint) {
519
564
  await this.#blockStore.setSynchedL1BlockNumber(l1BlockNumber);
520
565
  }
@@ -618,6 +663,11 @@ export class KVArchiverDataStore implements ContractDataSource {
618
663
  return this.#blockStore.getCheckpointData(checkpointNumber);
619
664
  }
620
665
 
666
+ /** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
667
+ getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
668
+ return this.#blockStore.getCheckpointDataForSlotRange(startSlot, endSlot);
669
+ }
670
+
621
671
  /**
622
672
  * Gets all blocks that have the given slot number.
623
673
  * @param slotNumber - The slot number to search for.