@aztec/archiver 0.0.1-commit.3469e52 → 0.0.1-commit.381b1a9

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 (97) hide show
  1. package/README.md +9 -0
  2. package/dest/archiver.d.ts +12 -8
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +81 -120
  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 +3 -1
  9. package/dest/factory.d.ts.map +1 -1
  10. package/dest/factory.js +14 -11
  11. package/dest/index.d.ts +2 -1
  12. package/dest/index.d.ts.map +1 -1
  13. package/dest/index.js +1 -0
  14. package/dest/l1/bin/retrieve-calldata.js +35 -32
  15. package/dest/l1/calldata_retriever.d.ts +73 -50
  16. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  17. package/dest/l1/calldata_retriever.js +190 -259
  18. package/dest/l1/data_retrieval.d.ts +4 -7
  19. package/dest/l1/data_retrieval.d.ts.map +1 -1
  20. package/dest/l1/data_retrieval.js +12 -16
  21. package/dest/l1/spire_proposer.d.ts +5 -5
  22. package/dest/l1/spire_proposer.d.ts.map +1 -1
  23. package/dest/l1/spire_proposer.js +9 -17
  24. package/dest/l1/validate_trace.d.ts +6 -3
  25. package/dest/l1/validate_trace.d.ts.map +1 -1
  26. package/dest/l1/validate_trace.js +13 -9
  27. package/dest/modules/data_source_base.d.ts +25 -21
  28. package/dest/modules/data_source_base.d.ts.map +1 -1
  29. package/dest/modules/data_source_base.js +45 -120
  30. package/dest/modules/data_store_updater.d.ts +40 -21
  31. package/dest/modules/data_store_updater.d.ts.map +1 -1
  32. package/dest/modules/data_store_updater.js +114 -64
  33. package/dest/modules/instrumentation.d.ts +6 -4
  34. package/dest/modules/instrumentation.d.ts.map +1 -1
  35. package/dest/modules/instrumentation.js +26 -12
  36. package/dest/modules/l1_synchronizer.d.ts +5 -8
  37. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  38. package/dest/modules/l1_synchronizer.js +46 -20
  39. package/dest/store/block_store.d.ts +49 -32
  40. package/dest/store/block_store.d.ts.map +1 -1
  41. package/dest/store/block_store.js +165 -54
  42. package/dest/store/contract_class_store.d.ts +1 -1
  43. package/dest/store/contract_class_store.d.ts.map +1 -1
  44. package/dest/store/contract_class_store.js +11 -7
  45. package/dest/store/kv_archiver_store.d.ts +53 -25
  46. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  47. package/dest/store/kv_archiver_store.js +50 -17
  48. package/dest/store/l2_tips_cache.d.ts +19 -0
  49. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  50. package/dest/store/l2_tips_cache.js +89 -0
  51. package/dest/store/log_store.d.ts +4 -4
  52. package/dest/store/log_store.d.ts.map +1 -1
  53. package/dest/store/log_store.js +105 -47
  54. package/dest/store/message_store.js +1 -1
  55. package/dest/test/fake_l1_state.d.ts +16 -4
  56. package/dest/test/fake_l1_state.d.ts.map +1 -1
  57. package/dest/test/fake_l1_state.js +84 -20
  58. package/dest/test/index.js +3 -1
  59. package/dest/test/mock_archiver.d.ts +1 -1
  60. package/dest/test/mock_archiver.d.ts.map +1 -1
  61. package/dest/test/mock_archiver.js +3 -2
  62. package/dest/test/mock_l2_block_source.d.ts +39 -23
  63. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  64. package/dest/test/mock_l2_block_source.js +157 -112
  65. package/dest/test/mock_structs.d.ts +6 -2
  66. package/dest/test/mock_structs.d.ts.map +1 -1
  67. package/dest/test/mock_structs.js +24 -10
  68. package/dest/test/noop_l1_archiver.d.ts +26 -0
  69. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  70. package/dest/test/noop_l1_archiver.js +72 -0
  71. package/package.json +14 -13
  72. package/src/archiver.ts +106 -149
  73. package/src/errors.ts +12 -0
  74. package/src/factory.ts +29 -12
  75. package/src/index.ts +1 -0
  76. package/src/l1/README.md +25 -68
  77. package/src/l1/bin/retrieve-calldata.ts +45 -33
  78. package/src/l1/calldata_retriever.ts +249 -379
  79. package/src/l1/data_retrieval.ts +10 -20
  80. package/src/l1/spire_proposer.ts +7 -15
  81. package/src/l1/validate_trace.ts +24 -6
  82. package/src/modules/data_source_base.ts +76 -166
  83. package/src/modules/data_store_updater.ts +130 -66
  84. package/src/modules/instrumentation.ts +26 -14
  85. package/src/modules/l1_synchronizer.ts +55 -26
  86. package/src/store/block_store.ts +216 -92
  87. package/src/store/contract_class_store.ts +11 -7
  88. package/src/store/kv_archiver_store.ts +88 -30
  89. package/src/store/l2_tips_cache.ts +89 -0
  90. package/src/store/log_store.ts +171 -55
  91. package/src/store/message_store.ts +1 -1
  92. package/src/test/fake_l1_state.ts +112 -23
  93. package/src/test/index.ts +3 -0
  94. package/src/test/mock_archiver.ts +3 -2
  95. package/src/test/mock_l2_block_source.ts +211 -129
  96. package/src/test/mock_structs.ts +45 -15
  97. package/src/test/noop_l1_archiver.ts +115 -0
@@ -9,16 +9,18 @@ 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
- L2BlockHash,
16
- L2BlockNew,
17
+ L2Block,
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 CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
23
+ import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
22
24
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
23
25
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
24
26
  import {
@@ -27,6 +29,7 @@ import {
27
29
  TxEffect,
28
30
  TxHash,
29
31
  TxReceipt,
32
+ TxStatus,
30
33
  deserializeIndexedTxEffect,
31
34
  serializeIndexedTxEffect,
32
35
  } from '@aztec/stdlib/tx';
@@ -36,6 +39,7 @@ import {
36
39
  BlockIndexNotSequentialError,
37
40
  BlockNotFoundError,
38
41
  BlockNumberNotSequentialError,
42
+ CannotOverwriteCheckpointedBlockError,
39
43
  CheckpointNotFoundError,
40
44
  CheckpointNumberNotConsistentError,
41
45
  CheckpointNumberNotSequentialError,
@@ -58,22 +62,15 @@ type BlockStorage = {
58
62
  type CheckpointStorage = {
59
63
  header: Buffer;
60
64
  archive: Buffer;
65
+ checkpointOutHash: Buffer;
61
66
  checkpointNumber: number;
62
67
  startBlock: number;
63
- numBlocks: number;
68
+ blockCount: number;
64
69
  l1: Buffer;
65
70
  attestations: Buffer[];
66
71
  };
67
72
 
68
- export type CheckpointData = {
69
- checkpointNumber: CheckpointNumber;
70
- header: CheckpointHeader;
71
- archive: AppendOnlyTreeSnapshot;
72
- startBlock: number;
73
- numBlocks: number;
74
- l1: L1PublishedData;
75
- attestations: Buffer[];
76
- };
73
+ export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
77
74
 
78
75
  /**
79
76
  * LMDB-based block storage for the archiver.
@@ -85,6 +82,9 @@ export class BlockStore {
85
82
  /** Map checkpoint number to checkpoint data */
86
83
  #checkpoints: AztecAsyncMap<number, CheckpointStorage>;
87
84
 
85
+ /** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
86
+ #slotToCheckpoint: AztecAsyncMap<number, number>;
87
+
88
88
  /** Map block hash to list of tx hashes */
89
89
  #blockTxs: AztecAsyncMap<string, Buffer>;
90
90
 
@@ -97,6 +97,9 @@ export class BlockStore {
97
97
  /** Stores last proven checkpoint */
98
98
  #lastProvenCheckpoint: AztecAsyncSingleton<number>;
99
99
 
100
+ /** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
101
+ #lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
102
+
100
103
  /** Stores the pending chain validation status */
101
104
  #pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
102
105
 
@@ -120,16 +123,37 @@ export class BlockStore {
120
123
  this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
121
124
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
122
125
  this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
126
+ this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
123
127
  this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
124
128
  this.#checkpoints = db.openMap('archiver_checkpoints');
129
+ this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
125
130
  }
126
131
 
127
132
  /**
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.
133
+ * Returns the finalized L2 block number. An L2 block is finalized when it was proven
134
+ * in an L1 block that has itself been finalized on Ethereum.
135
+ * @returns The finalized block number.
136
+ */
137
+ async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
138
+ const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
139
+ if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
140
+ return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
141
+ }
142
+ const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
143
+ if (!checkpointStorage) {
144
+ throw new CheckpointNotFoundError(finalizedCheckpointNumber);
145
+ }
146
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
147
+ }
148
+
149
+ /**
150
+ * Append new proposed blocks to the store's list. All blocks must be for the 'current' checkpoint.
151
+ * These are uncheckpointed blocks that have been proposed by the sequencer but not yet included in a checkpoint on L1.
152
+ * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
153
+ * @param blocks - The proposed L2 blocks to be added to the store.
130
154
  * @returns True if the operation is successful.
131
155
  */
132
- async addBlocks(blocks: L2BlockNew[], opts: { force?: boolean } = {}): Promise<boolean> {
156
+ async addProposedBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
133
157
  if (blocks.length === 0) {
134
158
  return true;
135
159
  }
@@ -145,6 +169,12 @@ export class BlockStore {
145
169
  const previousBlockNumber = await this.getLatestBlockNumber();
146
170
  const previousCheckpointNumber = await this.getLatestCheckpointNumber();
147
171
 
172
+ // Verify we're not overwriting checkpointed blocks
173
+ const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
174
+ if (!opts.force && firstBlockNumber <= lastCheckpointedBlockNumber) {
175
+ throw new CannotOverwriteCheckpointedBlockError(firstBlockNumber, lastCheckpointedBlockNumber);
176
+ }
177
+
148
178
  // Check that the first block number is the expected one
149
179
  if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
150
180
  throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
@@ -182,7 +212,7 @@ export class BlockStore {
182
212
  }
183
213
 
184
214
  // 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;
215
+ let previousBlock: L2Block | undefined = undefined;
186
216
  for (const block of blocks) {
187
217
  if (!opts.force && previousBlock) {
188
218
  if (previousBlock.number + 1 !== block.number) {
@@ -241,11 +271,11 @@ export class BlockStore {
241
271
  }
242
272
 
243
273
  let previousBlockNumber: BlockNumber | undefined = undefined;
244
- let previousBlock: L2BlockNew | undefined = undefined;
274
+ let previousBlock: L2Block | undefined = undefined;
245
275
 
246
276
  // If we have a previous checkpoint then we need to get the previous block number
247
277
  if (previousCheckpointData !== undefined) {
248
- previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.numBlocks - 1);
278
+ previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
249
279
  previousBlock = await this.getBlock(previousBlockNumber);
250
280
  if (previousBlock === undefined) {
251
281
  // We should be able to get the required previous block
@@ -309,12 +339,16 @@ export class BlockStore {
309
339
  await this.#checkpoints.set(checkpoint.checkpoint.number, {
310
340
  header: checkpoint.checkpoint.header.toBuffer(),
311
341
  archive: checkpoint.checkpoint.archive.toBuffer(),
342
+ checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
312
343
  l1: checkpoint.l1.toBuffer(),
313
344
  attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
314
345
  checkpointNumber: checkpoint.checkpoint.number,
315
346
  startBlock: checkpoint.checkpoint.blocks[0].number,
316
- numBlocks: checkpoint.checkpoint.blocks.length,
347
+ blockCount: checkpoint.checkpoint.blocks.length,
317
348
  });
349
+
350
+ // Update slot-to-checkpoint index
351
+ await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
318
352
  }
319
353
 
320
354
  await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
@@ -322,8 +356,8 @@ export class BlockStore {
322
356
  });
323
357
  }
324
358
 
325
- private async addBlockToDatabase(block: L2BlockNew, checkpointNumber: number, indexWithinCheckpoint: number) {
326
- const blockHash = L2BlockHash.fromField(await block.hash());
359
+ private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
360
+ const blockHash = await block.hash();
327
361
 
328
362
  await this.#blocks.set(block.number, {
329
363
  header: block.header.toBuffer(),
@@ -351,7 +385,7 @@ export class BlockStore {
351
385
  }
352
386
 
353
387
  /** Deletes a block and all associated data (tx effects, indices). */
354
- private async deleteBlock(block: L2BlockNew): Promise<void> {
388
+ private async deleteBlock(block: L2Block): Promise<void> {
355
389
  // Delete the block from the main blocks map
356
390
  await this.#blocks.delete(block.number);
357
391
 
@@ -368,51 +402,53 @@ export class BlockStore {
368
402
  }
369
403
 
370
404
  /**
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
405
+ * Removes all checkpoints with checkpoint number > checkpointNumber.
406
+ * Also removes ALL blocks (both checkpointed and uncheckpointed) after the last block of the given checkpoint.
407
+ * @param checkpointNumber - Remove all checkpoints strictly after this one.
376
408
  */
377
- async unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number) {
409
+ async removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
378
410
  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})`);
411
+ const latestCheckpointNumber = await this.getLatestCheckpointNumber();
412
+
413
+ if (checkpointNumber >= latestCheckpointNumber) {
414
+ this.#log.warn(`No checkpoints to remove after ${checkpointNumber} (latest is ${latestCheckpointNumber})`);
415
+ return { blocksRemoved: undefined };
382
416
  }
383
417
 
418
+ // If the proven checkpoint is beyond the target, update it
384
419
  const proven = await this.getProvenCheckpointNumber();
385
- if (from - checkpointsToUnwind < proven) {
386
- await this.setProvenCheckpointNumber(CheckpointNumber(from - checkpointsToUnwind));
420
+ if (proven > checkpointNumber) {
421
+ this.#log.warn(`Updating proven checkpoint ${proven} to last valid checkpoint ${checkpointNumber}`);
422
+ await this.setProvenCheckpointNumber(checkpointNumber);
387
423
  }
388
424
 
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;
425
+ // Find the last block number to keep (last block of the given checkpoint, or 0 if no checkpoint)
426
+ let lastBlockToKeep: BlockNumber;
427
+ if (checkpointNumber <= 0) {
428
+ lastBlockToKeep = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
429
+ } else {
430
+ const targetCheckpoint = await this.#checkpoints.getAsync(checkpointNumber);
431
+ if (!targetCheckpoint) {
432
+ throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
396
433
  }
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));
434
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
435
+ }
402
436
 
403
- if (block === undefined) {
404
- this.#log.warn(`Cannot remove block ${blockNumber} from the store since we don't have it`);
405
- continue;
406
- }
437
+ // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
438
+ const blocksRemoved = await this.removeBlocksAfter(lastBlockToKeep);
407
439
 
408
- await this.deleteBlock(block);
409
- this.#log.debug(
410
- `Unwound block ${blockNumber} ${(await block.hash()).toString()} for checkpoint ${checkpointNumber}`,
411
- );
440
+ // Remove all checkpoints after the target
441
+ for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
442
+ const checkpointStorage = await this.#checkpoints.getAsync(c);
443
+ if (checkpointStorage) {
444
+ const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
445
+ await this.#slotToCheckpoint.delete(slotNumber);
412
446
  }
447
+ await this.#checkpoints.delete(c);
448
+ this.#log.debug(`Removed checkpoint ${c}`);
413
449
  }
414
450
 
415
- return true;
451
+ return { blocksRemoved };
416
452
  });
417
453
  }
418
454
 
@@ -436,20 +472,35 @@ export class BlockStore {
436
472
  return checkpoints;
437
473
  }
438
474
 
439
- private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage) {
440
- const data: CheckpointData = {
475
+ /** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
476
+ async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
477
+ const result: CheckpointData[] = [];
478
+ for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
479
+ start: startSlot,
480
+ end: endSlot + 1,
481
+ })) {
482
+ const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
483
+ if (checkpointStorage) {
484
+ result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
485
+ }
486
+ }
487
+ return result;
488
+ }
489
+
490
+ private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
491
+ return {
441
492
  header: CheckpointHeader.fromBuffer(checkpointStorage.header),
442
493
  archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
494
+ checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
443
495
  checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
444
- startBlock: checkpointStorage.startBlock,
445
- numBlocks: checkpointStorage.numBlocks,
496
+ startBlock: BlockNumber(checkpointStorage.startBlock),
497
+ blockCount: checkpointStorage.blockCount,
446
498
  l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
447
- attestations: checkpointStorage.attestations,
499
+ attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
448
500
  };
449
- return data;
450
501
  }
451
502
 
452
- async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2BlockNew[] | undefined> {
503
+ async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
453
504
  const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
454
505
  if (!checkpoint) {
455
506
  return undefined;
@@ -458,7 +509,7 @@ export class BlockStore {
458
509
  const blocksForCheckpoint = await toArray(
459
510
  this.#blocks.entriesAsync({
460
511
  start: checkpoint.startBlock,
461
- end: checkpoint.startBlock + checkpoint.numBlocks,
512
+ end: checkpoint.startBlock + checkpoint.blockCount,
462
513
  }),
463
514
  );
464
515
 
@@ -472,8 +523,8 @@ export class BlockStore {
472
523
  * @param slotNumber - The slot number to search for.
473
524
  * @returns All blocks with the given slot number, in ascending block number order.
474
525
  */
475
- async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2BlockNew[]> {
476
- const blocks: L2BlockNew[] = [];
526
+ async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
527
+ const blocks: L2Block[] = [];
477
528
 
478
529
  // Iterate backwards through all blocks and filter by slot number
479
530
  // This is more efficient since we usually query for the most recent slot
@@ -493,12 +544,13 @@ export class BlockStore {
493
544
 
494
545
  /**
495
546
  * Removes all blocks with block number > blockNumber.
547
+ * Does not remove any associated checkpoints.
496
548
  * @param blockNumber - The block number to remove after.
497
549
  * @returns The removed blocks (for event emission).
498
550
  */
499
- async unwindBlocksAfter(blockNumber: BlockNumber): Promise<L2BlockNew[]> {
551
+ async removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
500
552
  return await this.db.transactionAsync(async () => {
501
- const removedBlocks: L2BlockNew[] = [];
553
+ const removedBlocks: L2Block[] = [];
502
554
 
503
555
  // Get the latest block number to determine the range
504
556
  const latestBlockNumber = await this.getLatestBlockNumber();
@@ -530,7 +582,7 @@ export class BlockStore {
530
582
  if (!checkpointStorage) {
531
583
  throw new CheckpointNotFoundError(provenCheckpointNumber);
532
584
  } else {
533
- return BlockNumber(checkpointStorage.startBlock + checkpointStorage.numBlocks - 1);
585
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
534
586
  }
535
587
  }
536
588
 
@@ -598,7 +650,7 @@ export class BlockStore {
598
650
  }
599
651
  }
600
652
 
601
- async getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
653
+ async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
602
654
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
603
655
  if (blockNumber === undefined) {
604
656
  return undefined;
@@ -620,7 +672,7 @@ export class BlockStore {
620
672
  * @param limit - The number of blocks to return.
621
673
  * @returns The requested L2 blocks
622
674
  */
623
- async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2BlockNew> {
675
+ async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2Block> {
624
676
  for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
625
677
  const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
626
678
  if (block) {
@@ -629,12 +681,38 @@ export class BlockStore {
629
681
  }
630
682
  }
631
683
 
684
+ /**
685
+ * Gets block metadata (without tx data) by block number.
686
+ * @param blockNumber - The number of the block to return.
687
+ * @returns The requested block data.
688
+ */
689
+ async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
690
+ const blockStorage = await this.#blocks.getAsync(blockNumber);
691
+ if (!blockStorage || !blockStorage.header) {
692
+ return undefined;
693
+ }
694
+ return this.getBlockDataFromBlockStorage(blockStorage);
695
+ }
696
+
697
+ /**
698
+ * Gets block metadata (without tx data) by archive root.
699
+ * @param archive - The archive root of the block to return.
700
+ * @returns The requested block data.
701
+ */
702
+ async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
703
+ const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
704
+ if (blockNumber === undefined) {
705
+ return undefined;
706
+ }
707
+ return this.getBlockData(BlockNumber(blockNumber));
708
+ }
709
+
632
710
  /**
633
711
  * Gets an L2 block.
634
712
  * @param blockNumber - The number of the block to return.
635
713
  * @returns The requested L2 block.
636
714
  */
637
- async getBlock(blockNumber: BlockNumber): Promise<L2BlockNew | undefined> {
715
+ async getBlock(blockNumber: BlockNumber): Promise<L2Block | undefined> {
638
716
  const blockStorage = await this.#blocks.getAsync(blockNumber);
639
717
  if (!blockStorage || !blockStorage.header) {
640
718
  return Promise.resolve(undefined);
@@ -647,7 +725,7 @@ export class BlockStore {
647
725
  * @param blockHash - The hash of the block to return.
648
726
  * @returns The requested L2 block.
649
727
  */
650
- async getBlockByHash(blockHash: L2BlockHash): Promise<L2BlockNew | undefined> {
728
+ async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
651
729
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
652
730
  if (blockNumber === undefined) {
653
731
  return undefined;
@@ -660,7 +738,7 @@ export class BlockStore {
660
738
  * @param archive - The archive root of the block to return.
661
739
  * @returns The requested L2 block.
662
740
  */
663
- async getBlockByArchive(archive: Fr): Promise<L2BlockNew | undefined> {
741
+ async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
664
742
  const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
665
743
  if (blockNumber === undefined) {
666
744
  return undefined;
@@ -673,7 +751,7 @@ export class BlockStore {
673
751
  * @param blockHash - The hash of the block to return.
674
752
  * @returns The requested block header.
675
753
  */
676
- async getBlockHeaderByHash(blockHash: L2BlockHash): Promise<BlockHeader | undefined> {
754
+ async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
677
755
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
678
756
  if (blockNumber === undefined) {
679
757
  return undefined;
@@ -733,15 +811,24 @@ export class BlockStore {
733
811
  }
734
812
  }
735
813
 
814
+ private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
815
+ return {
816
+ header: BlockHeader.fromBuffer(blockStorage.header),
817
+ archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
818
+ blockHash: Fr.fromBuffer(blockStorage.blockHash),
819
+ checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
820
+ indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
821
+ };
822
+ }
823
+
736
824
  private async getBlockFromBlockStorage(
737
825
  blockNumber: number,
738
826
  blockStorage: BlockStorage,
739
- ): Promise<L2BlockNew | undefined> {
740
- const header = BlockHeader.fromBuffer(blockStorage.header);
741
- const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
742
- const blockHash = blockStorage.blockHash;
743
- header.setHash(Fr.fromBuffer(blockHash));
744
- const blockHashString = bufferToHex(blockHash);
827
+ ): Promise<L2Block | undefined> {
828
+ const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
829
+ this.getBlockDataFromBlockStorage(blockStorage);
830
+ header.setHash(blockHash);
831
+ const blockHashString = bufferToHex(blockStorage.blockHash);
745
832
  const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
746
833
  if (blockTxsBuffer === undefined) {
747
834
  this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
@@ -760,13 +847,7 @@ export class BlockStore {
760
847
  txEffects.push(deserializeIndexedTxEffect(txEffect).data);
761
848
  }
762
849
  const body = new Body(txEffects);
763
- const block = new L2BlockNew(
764
- archive,
765
- header,
766
- body,
767
- CheckpointNumber(blockStorage.checkpointNumber!),
768
- IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
769
- );
850
+ const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
770
851
 
771
852
  if (block.number !== blockNumber) {
772
853
  throw new Error(
@@ -796,19 +877,48 @@ export class BlockStore {
796
877
  * @param txHash - The hash of a tx we try to get the receipt for.
797
878
  * @returns The requested tx receipt (or undefined if not found).
798
879
  */
799
- async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
880
+ async getSettledTxReceipt(
881
+ txHash: TxHash,
882
+ l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
883
+ ): Promise<TxReceipt | undefined> {
800
884
  const txEffect = await this.getTxEffect(txHash);
801
885
  if (!txEffect) {
802
886
  return undefined;
803
887
  }
804
888
 
889
+ const blockNumber = BlockNumber(txEffect.l2BlockNumber);
890
+
891
+ // Use existing archiver methods to determine finalization level
892
+ const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
893
+ this.getProvenBlockNumber(),
894
+ this.getCheckpointedL2BlockNumber(),
895
+ this.getFinalizedL2BlockNumber(),
896
+ this.getBlockData(blockNumber),
897
+ ]);
898
+
899
+ let status: TxStatus;
900
+ if (blockNumber <= finalizedBlockNumber) {
901
+ status = TxStatus.FINALIZED;
902
+ } else if (blockNumber <= provenBlockNumber) {
903
+ status = TxStatus.PROVEN;
904
+ } else if (blockNumber <= checkpointedBlockNumber) {
905
+ status = TxStatus.CHECKPOINTED;
906
+ } else {
907
+ status = TxStatus.PROPOSED;
908
+ }
909
+
910
+ const epochNumber =
911
+ blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
912
+
805
913
  return new TxReceipt(
806
914
  txHash,
807
- TxReceipt.statusFromRevertCode(txEffect.data.revertCode),
808
- '',
915
+ status,
916
+ TxReceipt.executionResultFromRevertCode(txEffect.data.revertCode),
917
+ undefined,
809
918
  txEffect.data.transactionFee.toBigInt(),
810
919
  txEffect.l2BlockHash,
811
- BlockNumber(txEffect.l2BlockNumber),
920
+ blockNumber,
921
+ epochNumber,
812
922
  );
813
923
  }
814
924
 
@@ -845,7 +955,7 @@ export class BlockStore {
845
955
  if (!checkpoint) {
846
956
  return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
847
957
  }
848
- return BlockNumber(checkpoint.startBlock + checkpoint.numBlocks - 1);
958
+ return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
849
959
  }
850
960
 
851
961
  async getLatestL2BlockNumber(): Promise<BlockNumber> {
@@ -880,6 +990,20 @@ export class BlockStore {
880
990
  return result;
881
991
  }
882
992
 
993
+ async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
994
+ const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
995
+ this.getLatestCheckpointNumber(),
996
+ this.#lastFinalizedCheckpoint.getAsync(),
997
+ ]);
998
+ return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
999
+ ? latestCheckpointNumber
1000
+ : CheckpointNumber(finalizedCheckpointNumber ?? 0);
1001
+ }
1002
+
1003
+ setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
1004
+ return this.#lastFinalizedCheckpoint.set(checkpointNumber);
1005
+ }
1006
+
883
1007
  #computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
884
1008
  if (limit < 1) {
885
1009
  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