@aztec/archiver 0.0.1-commit.2ed92850 → 0.0.1-commit.343b43af6

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 (96) hide show
  1. package/dest/archiver.d.ts +9 -5
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +76 -111
  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 +5 -4
  8. package/dest/factory.d.ts.map +1 -1
  9. package/dest/factory.js +17 -16
  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 +22 -20
  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/l1/validate_trace.d.ts +6 -3
  24. package/dest/l1/validate_trace.d.ts.map +1 -1
  25. package/dest/l1/validate_trace.js +13 -9
  26. package/dest/modules/data_source_base.d.ts +13 -8
  27. package/dest/modules/data_source_base.d.ts.map +1 -1
  28. package/dest/modules/data_source_base.js +28 -72
  29. package/dest/modules/data_store_updater.d.ts +22 -7
  30. package/dest/modules/data_store_updater.d.ts.map +1 -1
  31. package/dest/modules/data_store_updater.js +69 -29
  32. package/dest/modules/instrumentation.d.ts +15 -2
  33. package/dest/modules/instrumentation.d.ts.map +1 -1
  34. package/dest/modules/instrumentation.js +36 -12
  35. package/dest/modules/l1_synchronizer.d.ts +5 -8
  36. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  37. package/dest/modules/l1_synchronizer.js +43 -13
  38. package/dest/store/block_store.d.ts +30 -28
  39. package/dest/store/block_store.d.ts.map +1 -1
  40. package/dest/store/block_store.js +125 -76
  41. package/dest/store/contract_class_store.d.ts +1 -1
  42. package/dest/store/contract_class_store.d.ts.map +1 -1
  43. package/dest/store/contract_class_store.js +11 -7
  44. package/dest/store/kv_archiver_store.d.ts +36 -14
  45. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  46. package/dest/store/kv_archiver_store.js +39 -10
  47. package/dest/store/l2_tips_cache.d.ts +19 -0
  48. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  49. package/dest/store/l2_tips_cache.js +89 -0
  50. package/dest/store/log_store.d.ts +1 -1
  51. package/dest/store/log_store.d.ts.map +1 -1
  52. package/dest/store/log_store.js +57 -37
  53. package/dest/store/message_store.js +1 -1
  54. package/dest/test/fake_l1_state.d.ts +13 -1
  55. package/dest/test/fake_l1_state.d.ts.map +1 -1
  56. package/dest/test/fake_l1_state.js +84 -20
  57. package/dest/test/index.js +3 -1
  58. package/dest/test/mock_archiver.d.ts +1 -1
  59. package/dest/test/mock_archiver.d.ts.map +1 -1
  60. package/dest/test/mock_archiver.js +3 -2
  61. package/dest/test/mock_l2_block_source.d.ts +25 -9
  62. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  63. package/dest/test/mock_l2_block_source.js +132 -86
  64. package/dest/test/mock_structs.d.ts +6 -2
  65. package/dest/test/mock_structs.d.ts.map +1 -1
  66. package/dest/test/mock_structs.js +20 -6
  67. package/dest/test/noop_l1_archiver.d.ts +26 -0
  68. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  69. package/dest/test/noop_l1_archiver.js +72 -0
  70. package/package.json +14 -13
  71. package/src/archiver.ts +96 -132
  72. package/src/errors.ts +10 -24
  73. package/src/factory.ts +32 -17
  74. package/src/index.ts +1 -0
  75. package/src/l1/README.md +25 -68
  76. package/src/l1/bin/retrieve-calldata.ts +46 -39
  77. package/src/l1/calldata_retriever.ts +249 -379
  78. package/src/l1/data_retrieval.ts +24 -26
  79. package/src/l1/spire_proposer.ts +7 -15
  80. package/src/l1/validate_trace.ts +24 -6
  81. package/src/modules/data_source_base.ts +58 -97
  82. package/src/modules/data_store_updater.ts +71 -30
  83. package/src/modules/instrumentation.ts +44 -12
  84. package/src/modules/l1_synchronizer.ts +48 -17
  85. package/src/store/block_store.ts +151 -108
  86. package/src/store/contract_class_store.ts +11 -7
  87. package/src/store/kv_archiver_store.ts +62 -16
  88. package/src/store/l2_tips_cache.ts +89 -0
  89. package/src/store/log_store.ts +98 -36
  90. package/src/store/message_store.ts +1 -1
  91. package/src/test/fake_l1_state.ts +110 -21
  92. package/src/test/index.ts +3 -0
  93. package/src/test/mock_archiver.ts +3 -2
  94. package/src/test/mock_l2_block_source.ts +172 -86
  95. package/src/test/mock_structs.ts +42 -12
  96. package/src/test/noop_l1_archiver.ts +115 -0
@@ -9,17 +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
+ type BlockData,
13
+ BlockHash,
12
14
  Body,
13
15
  CheckpointedL2Block,
14
16
  CommitteeAttestation,
15
17
  L2Block,
16
- L2BlockHash,
17
18
  type ValidateCheckpointResult,
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
23
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
24
24
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
25
25
  import {
@@ -34,15 +34,14 @@ import {
34
34
  } from '@aztec/stdlib/tx';
35
35
 
36
36
  import {
37
+ BlockAlreadyCheckpointedError,
37
38
  BlockArchiveNotConsistentError,
38
39
  BlockIndexNotSequentialError,
39
40
  BlockNotFoundError,
40
41
  BlockNumberNotSequentialError,
41
42
  CannotOverwriteCheckpointedBlockError,
42
43
  CheckpointNotFoundError,
43
- CheckpointNumberNotConsistentError,
44
44
  CheckpointNumberNotSequentialError,
45
- InitialBlockNumberNotSequentialError,
46
45
  InitialCheckpointNumberNotSequentialError,
47
46
  } from '../errors.js';
48
47
 
@@ -61,23 +60,14 @@ type BlockStorage = {
61
60
  type CheckpointStorage = {
62
61
  header: Buffer;
63
62
  archive: Buffer;
63
+ checkpointOutHash: Buffer;
64
64
  checkpointNumber: number;
65
65
  startBlock: number;
66
- numBlocks: number;
66
+ blockCount: number;
67
67
  l1: Buffer;
68
68
  attestations: Buffer[];
69
69
  };
70
70
 
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
71
  export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
82
72
 
83
73
  /**
@@ -90,6 +80,9 @@ export class BlockStore {
90
80
  /** Map checkpoint number to checkpoint data */
91
81
  #checkpoints: AztecAsyncMap<number, CheckpointStorage>;
92
82
 
83
+ /** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
84
+ #slotToCheckpoint: AztecAsyncMap<number, number>;
85
+
93
86
  /** Map block hash to list of tx hashes */
94
87
  #blockTxs: AztecAsyncMap<string, Buffer>;
95
88
 
@@ -102,6 +95,9 @@ export class BlockStore {
102
95
  /** Stores last proven checkpoint */
103
96
  #lastProvenCheckpoint: AztecAsyncSingleton<number>;
104
97
 
98
+ /** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
99
+ #lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
100
+
105
101
  /** Stores the pending chain validation status */
106
102
  #pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
107
103
 
@@ -116,10 +112,7 @@ export class BlockStore {
116
112
 
117
113
  #log = createLogger('archiver:block_store');
118
114
 
119
- constructor(
120
- private db: AztecAsyncKVStore,
121
- private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
122
- ) {
115
+ constructor(private db: AztecAsyncKVStore) {
123
116
  this.#blocks = db.openMap('archiver_blocks');
124
117
  this.#blockTxs = db.openMap('archiver_block_txs');
125
118
  this.#txEffects = db.openMap('archiver_tx_effects');
@@ -128,40 +121,42 @@ export class BlockStore {
128
121
  this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
129
122
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
130
123
  this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
124
+ this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
131
125
  this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
132
126
  this.#checkpoints = db.openMap('archiver_checkpoints');
127
+ this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
133
128
  }
134
129
 
135
130
  /**
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
131
+ * Returns the finalized L2 block number. An L2 block is finalized when it was proven
132
+ * in an L1 block that has itself been finalized on Ethereum.
140
133
  * @returns The finalized block number.
141
134
  */
142
135
  async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
143
- const provenBlockNumber = await this.getProvenBlockNumber();
144
- return BlockNumber(Math.max(provenBlockNumber - this.l1Constants.epochDuration * 2, 0));
136
+ const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
137
+ if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
138
+ return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
139
+ }
140
+ const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
141
+ if (!checkpointStorage) {
142
+ throw new CheckpointNotFoundError(finalizedCheckpointNumber);
143
+ }
144
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
145
145
  }
146
146
 
147
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.
148
+ * Append a new proposed block to the store.
149
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
150
150
  * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
151
- * @param blocks - The proposed L2 blocks to be added to the store.
151
+ * @param block - The proposed L2 block to be added to the store.
152
152
  * @returns True if the operation is successful.
153
153
  */
154
- async addProposedBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
155
- if (blocks.length === 0) {
156
- return true;
157
- }
158
-
154
+ async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
159
155
  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;
156
+ const blockNumber = block.number;
157
+ const blockCheckpointNumber = block.checkpointNumber;
158
+ const blockIndex = block.indexWithinCheckpoint;
159
+ const blockLastArchive = block.header.lastArchive.root;
165
160
 
166
161
  // Extract the latest block and checkpoint numbers
167
162
  const previousBlockNumber = await this.getLatestBlockNumber();
@@ -169,71 +164,52 @@ export class BlockStore {
169
164
 
170
165
  // Verify we're not overwriting checkpointed blocks
171
166
  const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
172
- if (!opts.force && firstBlockNumber <= lastCheckpointedBlockNumber) {
173
- throw new CannotOverwriteCheckpointedBlockError(firstBlockNumber, lastCheckpointedBlockNumber);
167
+ if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
168
+ // Check if the proposed block matches the already-checkpointed one
169
+ const existingBlock = await this.getBlock(BlockNumber(blockNumber));
170
+ if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
171
+ throw new BlockAlreadyCheckpointedError(blockNumber);
172
+ }
173
+ throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
174
174
  }
175
175
 
176
- // Check that the first block number is the expected one
177
- if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
178
- throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
176
+ // Check that the block number is the expected one
177
+ if (!opts.force && previousBlockNumber !== blockNumber - 1) {
178
+ throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
179
179
  }
180
180
 
181
181
  // The same check as above but for checkpoints
182
- if (!opts.force && previousCheckpointNumber !== firstBlockCheckpointNumber - 1) {
183
- throw new InitialCheckpointNumberNotSequentialError(firstBlockCheckpointNumber, previousCheckpointNumber);
182
+ if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
183
+ throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
184
184
  }
185
185
 
186
186
  // Extract the previous block if there is one and see if it is for the same checkpoint or not
187
187
  const previousBlockResult = await this.getBlock(previousBlockNumber);
188
188
 
189
- let expectedFirstblockIndex = 0;
189
+ let expectedBlockIndex = 0;
190
190
  let previousBlockIndex: number | undefined = undefined;
191
191
  if (previousBlockResult !== undefined) {
192
- if (previousBlockResult.checkpointNumber === firstBlockCheckpointNumber) {
192
+ if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
193
193
  // The previous block is for the same checkpoint, therefore our index should follow it
194
194
  previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
195
- expectedFirstblockIndex = previousBlockIndex + 1;
195
+ expectedBlockIndex = previousBlockIndex + 1;
196
196
  }
197
- if (!previousBlockResult.archive.root.equals(firstBlockLastArchive)) {
197
+ if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
198
198
  throw new BlockArchiveNotConsistentError(
199
- firstBlockNumber,
199
+ blockNumber,
200
200
  previousBlockResult.number,
201
- firstBlockLastArchive,
201
+ blockLastArchive,
202
202
  previousBlockResult.archive.root,
203
203
  );
204
204
  }
205
205
  }
206
206
 
207
- // Now check that the first block has the expected index value
208
- if (!opts.force && expectedFirstblockIndex !== firstBlockIndex) {
209
- throw new BlockIndexNotSequentialError(firstBlockIndex, previousBlockIndex);
207
+ // Now check that the block has the expected index value
208
+ if (!opts.force && expectedBlockIndex !== blockIndex) {
209
+ throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
210
210
  }
211
211
 
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
- }
212
+ await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
237
213
 
238
214
  return true;
239
215
  });
@@ -273,7 +249,7 @@ export class BlockStore {
273
249
 
274
250
  // If we have a previous checkpoint then we need to get the previous block number
275
251
  if (previousCheckpointData !== undefined) {
276
- previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.numBlocks - 1);
252
+ previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
277
253
  previousBlock = await this.getBlock(previousBlockNumber);
278
254
  if (previousBlock === undefined) {
279
255
  // We should be able to get the required previous block
@@ -337,12 +313,16 @@ export class BlockStore {
337
313
  await this.#checkpoints.set(checkpoint.checkpoint.number, {
338
314
  header: checkpoint.checkpoint.header.toBuffer(),
339
315
  archive: checkpoint.checkpoint.archive.toBuffer(),
316
+ checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
340
317
  l1: checkpoint.l1.toBuffer(),
341
318
  attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
342
319
  checkpointNumber: checkpoint.checkpoint.number,
343
320
  startBlock: checkpoint.checkpoint.blocks[0].number,
344
- numBlocks: checkpoint.checkpoint.blocks.length,
321
+ blockCount: checkpoint.checkpoint.blocks.length,
345
322
  });
323
+
324
+ // Update slot-to-checkpoint index
325
+ await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
346
326
  }
347
327
 
348
328
  await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
@@ -351,7 +331,7 @@ export class BlockStore {
351
331
  }
352
332
 
353
333
  private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
354
- const blockHash = L2BlockHash.fromField(await block.hash());
334
+ const blockHash = await block.hash();
355
335
 
356
336
  await this.#blocks.set(block.number, {
357
337
  header: block.header.toBuffer(),
@@ -425,7 +405,7 @@ export class BlockStore {
425
405
  if (!targetCheckpoint) {
426
406
  throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
427
407
  }
428
- lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.numBlocks - 1);
408
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
429
409
  }
430
410
 
431
411
  // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
@@ -433,6 +413,11 @@ export class BlockStore {
433
413
 
434
414
  // Remove all checkpoints after the target
435
415
  for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
416
+ const checkpointStorage = await this.#checkpoints.getAsync(c);
417
+ if (checkpointStorage) {
418
+ const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
419
+ await this.#slotToCheckpoint.delete(slotNumber);
420
+ }
436
421
  await this.#checkpoints.delete(c);
437
422
  this.#log.debug(`Removed checkpoint ${c}`);
438
423
  }
@@ -461,17 +446,32 @@ export class BlockStore {
461
446
  return checkpoints;
462
447
  }
463
448
 
464
- private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage) {
465
- const data: CheckpointData = {
449
+ /** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
450
+ async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
451
+ const result: CheckpointData[] = [];
452
+ for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
453
+ start: startSlot,
454
+ end: endSlot + 1,
455
+ })) {
456
+ const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
457
+ if (checkpointStorage) {
458
+ result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
459
+ }
460
+ }
461
+ return result;
462
+ }
463
+
464
+ private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
465
+ return {
466
466
  header: CheckpointHeader.fromBuffer(checkpointStorage.header),
467
467
  archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
468
+ checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
468
469
  checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
469
- startBlock: checkpointStorage.startBlock,
470
- numBlocks: checkpointStorage.numBlocks,
470
+ startBlock: BlockNumber(checkpointStorage.startBlock),
471
+ blockCount: checkpointStorage.blockCount,
471
472
  l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
472
- attestations: checkpointStorage.attestations,
473
+ attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
473
474
  };
474
- return data;
475
475
  }
476
476
 
477
477
  async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
@@ -483,7 +483,7 @@ export class BlockStore {
483
483
  const blocksForCheckpoint = await toArray(
484
484
  this.#blocks.entriesAsync({
485
485
  start: checkpoint.startBlock,
486
- end: checkpoint.startBlock + checkpoint.numBlocks,
486
+ end: checkpoint.startBlock + checkpoint.blockCount,
487
487
  }),
488
488
  );
489
489
 
@@ -556,7 +556,7 @@ export class BlockStore {
556
556
  if (!checkpointStorage) {
557
557
  throw new CheckpointNotFoundError(provenCheckpointNumber);
558
558
  } else {
559
- return BlockNumber(checkpointStorage.startBlock + checkpointStorage.numBlocks - 1);
559
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
560
560
  }
561
561
  }
562
562
 
@@ -624,7 +624,7 @@ export class BlockStore {
624
624
  }
625
625
  }
626
626
 
627
- async getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
627
+ async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
628
628
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
629
629
  if (blockNumber === undefined) {
630
630
  return undefined;
@@ -655,6 +655,32 @@ export class BlockStore {
655
655
  }
656
656
  }
657
657
 
658
+ /**
659
+ * Gets block metadata (without tx data) by block number.
660
+ * @param blockNumber - The number of the block to return.
661
+ * @returns The requested block data.
662
+ */
663
+ async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
664
+ const blockStorage = await this.#blocks.getAsync(blockNumber);
665
+ if (!blockStorage || !blockStorage.header) {
666
+ return undefined;
667
+ }
668
+ return this.getBlockDataFromBlockStorage(blockStorage);
669
+ }
670
+
671
+ /**
672
+ * Gets block metadata (without tx data) by archive root.
673
+ * @param archive - The archive root of the block to return.
674
+ * @returns The requested block data.
675
+ */
676
+ async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
677
+ const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
678
+ if (blockNumber === undefined) {
679
+ return undefined;
680
+ }
681
+ return this.getBlockData(BlockNumber(blockNumber));
682
+ }
683
+
658
684
  /**
659
685
  * Gets an L2 block.
660
686
  * @param blockNumber - The number of the block to return.
@@ -673,7 +699,7 @@ export class BlockStore {
673
699
  * @param blockHash - The hash of the block to return.
674
700
  * @returns The requested L2 block.
675
701
  */
676
- async getBlockByHash(blockHash: L2BlockHash): Promise<L2Block | undefined> {
702
+ async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
677
703
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
678
704
  if (blockNumber === undefined) {
679
705
  return undefined;
@@ -699,7 +725,7 @@ export class BlockStore {
699
725
  * @param blockHash - The hash of the block to return.
700
726
  * @returns The requested block header.
701
727
  */
702
- async getBlockHeaderByHash(blockHash: L2BlockHash): Promise<BlockHeader | undefined> {
728
+ async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
703
729
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
704
730
  if (blockNumber === undefined) {
705
731
  return undefined;
@@ -759,15 +785,24 @@ export class BlockStore {
759
785
  }
760
786
  }
761
787
 
788
+ private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
789
+ return {
790
+ header: BlockHeader.fromBuffer(blockStorage.header),
791
+ archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
792
+ blockHash: Fr.fromBuffer(blockStorage.blockHash),
793
+ checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
794
+ indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
795
+ };
796
+ }
797
+
762
798
  private async getBlockFromBlockStorage(
763
799
  blockNumber: number,
764
800
  blockStorage: BlockStorage,
765
801
  ): 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);
802
+ const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
803
+ this.getBlockDataFromBlockStorage(blockStorage);
804
+ header.setHash(blockHash);
805
+ const blockHashString = bufferToHex(blockStorage.blockHash);
771
806
  const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
772
807
  if (blockTxsBuffer === undefined) {
773
808
  this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
@@ -786,13 +821,7 @@ export class BlockStore {
786
821
  txEffects.push(deserializeIndexedTxEffect(txEffect).data);
787
822
  }
788
823
  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
- );
824
+ const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
796
825
 
797
826
  if (block.number !== blockNumber) {
798
827
  throw new Error(
@@ -892,7 +921,7 @@ export class BlockStore {
892
921
  if (!checkpoint) {
893
922
  return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
894
923
  }
895
- return BlockNumber(checkpoint.startBlock + checkpoint.numBlocks - 1);
924
+ return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
896
925
  }
897
926
 
898
927
  async getLatestL2BlockNumber(): Promise<BlockNumber> {
@@ -927,6 +956,20 @@ export class BlockStore {
927
956
  return result;
928
957
  }
929
958
 
959
+ async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
960
+ const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
961
+ this.getLatestCheckpointNumber(),
962
+ this.#lastFinalizedCheckpoint.getAsync(),
963
+ ]);
964
+ return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
965
+ ? latestCheckpointNumber
966
+ : CheckpointNumber(finalizedCheckpointNumber ?? 0);
967
+ }
968
+
969
+ setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
970
+ return this.#lastFinalizedCheckpoint.set(checkpointNumber);
971
+ }
972
+
930
973
  #computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
931
974
  if (limit < 1) {
932
975
  throw new Error(`Invalid limit: ${limit}`);
@@ -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