@aztec/archiver 0.0.1-commit.9d2bcf6d → 0.0.1-commit.9ef841308

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 (100) hide show
  1. package/dest/archiver.d.ts +7 -5
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +73 -111
  4. package/dest/config.d.ts +3 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +2 -1
  7. package/dest/errors.d.ts +21 -9
  8. package/dest/errors.d.ts.map +1 -1
  9. package/dest/errors.js +27 -14
  10. package/dest/factory.d.ts +4 -5
  11. package/dest/factory.d.ts.map +1 -1
  12. package/dest/factory.js +31 -26
  13. package/dest/index.d.ts +2 -1
  14. package/dest/index.d.ts.map +1 -1
  15. package/dest/index.js +1 -0
  16. package/dest/l1/bin/retrieve-calldata.js +36 -33
  17. package/dest/l1/calldata_retriever.d.ts +73 -50
  18. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  19. package/dest/l1/calldata_retriever.js +191 -259
  20. package/dest/l1/data_retrieval.d.ts +9 -9
  21. package/dest/l1/data_retrieval.d.ts.map +1 -1
  22. package/dest/l1/data_retrieval.js +21 -19
  23. package/dest/l1/spire_proposer.d.ts +5 -5
  24. package/dest/l1/spire_proposer.d.ts.map +1 -1
  25. package/dest/l1/spire_proposer.js +9 -17
  26. package/dest/modules/data_source_base.d.ts +12 -7
  27. package/dest/modules/data_source_base.d.ts.map +1 -1
  28. package/dest/modules/data_source_base.js +33 -77
  29. package/dest/modules/data_store_updater.d.ts +24 -12
  30. package/dest/modules/data_store_updater.d.ts.map +1 -1
  31. package/dest/modules/data_store_updater.js +116 -94
  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 +19 -2
  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 +50 -15
  38. package/dest/store/block_store.d.ts +29 -26
  39. package/dest/store/block_store.d.ts.map +1 -1
  40. package/dest/store/block_store.js +130 -78
  41. package/dest/store/contract_class_store.d.ts +2 -3
  42. package/dest/store/contract_class_store.d.ts.map +1 -1
  43. package/dest/store/contract_class_store.js +7 -67
  44. package/dest/store/contract_instance_store.d.ts +1 -1
  45. package/dest/store/contract_instance_store.d.ts.map +1 -1
  46. package/dest/store/contract_instance_store.js +6 -2
  47. package/dest/store/kv_archiver_store.d.ts +45 -21
  48. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  49. package/dest/store/kv_archiver_store.js +52 -21
  50. package/dest/store/l2_tips_cache.d.ts +19 -0
  51. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  52. package/dest/store/l2_tips_cache.js +89 -0
  53. package/dest/store/log_store.d.ts +6 -3
  54. package/dest/store/log_store.d.ts.map +1 -1
  55. package/dest/store/log_store.js +148 -51
  56. package/dest/store/message_store.d.ts +5 -1
  57. package/dest/store/message_store.d.ts.map +1 -1
  58. package/dest/store/message_store.js +14 -1
  59. package/dest/test/fake_l1_state.d.ts +13 -1
  60. package/dest/test/fake_l1_state.d.ts.map +1 -1
  61. package/dest/test/fake_l1_state.js +95 -23
  62. package/dest/test/mock_archiver.d.ts +1 -1
  63. package/dest/test/mock_archiver.d.ts.map +1 -1
  64. package/dest/test/mock_archiver.js +3 -2
  65. package/dest/test/mock_l2_block_source.d.ts +21 -5
  66. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  67. package/dest/test/mock_l2_block_source.js +132 -86
  68. package/dest/test/mock_structs.d.ts +4 -1
  69. package/dest/test/mock_structs.d.ts.map +1 -1
  70. package/dest/test/mock_structs.js +13 -1
  71. package/dest/test/noop_l1_archiver.d.ts +4 -1
  72. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  73. package/dest/test/noop_l1_archiver.js +5 -1
  74. package/package.json +13 -13
  75. package/src/archiver.ts +88 -131
  76. package/src/config.ts +8 -1
  77. package/src/errors.ts +40 -24
  78. package/src/factory.ts +46 -24
  79. package/src/index.ts +1 -0
  80. package/src/l1/README.md +25 -68
  81. package/src/l1/bin/retrieve-calldata.ts +46 -39
  82. package/src/l1/calldata_retriever.ts +250 -379
  83. package/src/l1/data_retrieval.ts +23 -25
  84. package/src/l1/spire_proposer.ts +7 -15
  85. package/src/modules/data_source_base.ts +64 -98
  86. package/src/modules/data_store_updater.ts +125 -124
  87. package/src/modules/instrumentation.ts +29 -2
  88. package/src/modules/l1_synchronizer.ts +61 -24
  89. package/src/store/block_store.ts +157 -105
  90. package/src/store/contract_class_store.ts +8 -106
  91. package/src/store/contract_instance_store.ts +8 -5
  92. package/src/store/kv_archiver_store.ts +78 -35
  93. package/src/store/l2_tips_cache.ts +89 -0
  94. package/src/store/log_store.ts +219 -58
  95. package/src/store/message_store.ts +20 -1
  96. package/src/test/fake_l1_state.ts +125 -26
  97. package/src/test/mock_archiver.ts +3 -2
  98. package/src/test/mock_l2_block_source.ts +173 -81
  99. package/src/test/mock_structs.ts +20 -6
  100. 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}`);
@@ -2,14 +2,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
2
2
  import { toArray } from '@aztec/foundation/iterable';
3
3
  import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize';
4
4
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
5
- import { FunctionSelector } from '@aztec/stdlib/abi';
6
- import type {
7
- ContractClassPublic,
8
- ContractClassPublicWithBlockNumber,
9
- ExecutablePrivateFunctionWithMembershipProof,
10
- UtilityFunctionWithMembershipProof,
11
- } from '@aztec/stdlib/contract';
12
- import { Vector } from '@aztec/stdlib/types';
5
+ import type { ContractClassPublic, ContractClassPublicWithBlockNumber } from '@aztec/stdlib/contract';
13
6
 
14
7
  /**
15
8
  * LMDB-based contract class storage for the archiver.
@@ -29,11 +22,15 @@ export class ContractClassStore {
29
22
  blockNumber: number,
30
23
  ): Promise<void> {
31
24
  await this.db.transactionAsync(async () => {
32
- await this.#contractClasses.setIfNotExists(
33
- contractClass.id.toString(),
25
+ const key = contractClass.id.toString();
26
+ if (await this.#contractClasses.hasAsync(key)) {
27
+ throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`);
28
+ }
29
+ await this.#contractClasses.set(
30
+ key,
34
31
  serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
35
32
  );
36
- await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer());
33
+ await this.#bytecodeCommitments.set(key, bytecodeCommitment.toBuffer());
37
34
  });
38
35
  }
39
36
 
@@ -60,37 +57,6 @@ export class ContractClassStore {
60
57
  async getContractClassIds(): Promise<Fr[]> {
61
58
  return (await toArray(this.#contractClasses.keysAsync())).map(key => Fr.fromHexString(key));
62
59
  }
63
-
64
- async addFunctions(
65
- contractClassId: Fr,
66
- newPrivateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
67
- newUtilityFunctions: UtilityFunctionWithMembershipProof[],
68
- ): Promise<boolean> {
69
- await this.db.transactionAsync(async () => {
70
- const existingClassBuffer = await this.#contractClasses.getAsync(contractClassId.toString());
71
- if (!existingClassBuffer) {
72
- throw new Error(`Unknown contract class ${contractClassId} when adding private functions to store`);
73
- }
74
-
75
- const existingClass = deserializeContractClassPublic(existingClassBuffer);
76
- const { privateFunctions: existingPrivateFns, utilityFunctions: existingUtilityFns } = existingClass;
77
-
78
- const updatedClass: Omit<ContractClassPublicWithBlockNumber, 'id'> = {
79
- ...existingClass,
80
- privateFunctions: [
81
- ...existingPrivateFns,
82
- ...newPrivateFunctions.filter(newFn => !existingPrivateFns.some(f => f.selector.equals(newFn.selector))),
83
- ],
84
- utilityFunctions: [
85
- ...existingUtilityFns,
86
- ...newUtilityFunctions.filter(newFn => !existingUtilityFns.some(f => f.selector.equals(newFn.selector))),
87
- ],
88
- };
89
- await this.#contractClasses.set(contractClassId.toString(), serializeContractClassPublic(updatedClass));
90
- });
91
-
92
- return true;
93
- }
94
60
  }
95
61
 
96
62
  function serializeContractClassPublic(contractClass: Omit<ContractClassPublicWithBlockNumber, 'id'>): Buffer {
@@ -98,83 +64,19 @@ function serializeContractClassPublic(contractClass: Omit<ContractClassPublicWit
98
64
  contractClass.l2BlockNumber,
99
65
  numToUInt8(contractClass.version),
100
66
  contractClass.artifactHash,
101
- contractClass.privateFunctions.length,
102
- contractClass.privateFunctions.map(serializePrivateFunction),
103
- contractClass.utilityFunctions.length,
104
- contractClass.utilityFunctions.map(serializeUtilityFunction),
105
67
  contractClass.packedBytecode.length,
106
68
  contractClass.packedBytecode,
107
69
  contractClass.privateFunctionsRoot,
108
70
  );
109
71
  }
110
72
 
111
- function serializePrivateFunction(fn: ExecutablePrivateFunctionWithMembershipProof): Buffer {
112
- return serializeToBuffer(
113
- fn.selector,
114
- fn.vkHash,
115
- fn.bytecode.length,
116
- fn.bytecode,
117
- fn.functionMetadataHash,
118
- fn.artifactMetadataHash,
119
- fn.utilityFunctionsTreeRoot,
120
- new Vector(fn.privateFunctionTreeSiblingPath),
121
- fn.privateFunctionTreeLeafIndex,
122
- new Vector(fn.artifactTreeSiblingPath),
123
- fn.artifactTreeLeafIndex,
124
- );
125
- }
126
-
127
- function serializeUtilityFunction(fn: UtilityFunctionWithMembershipProof): Buffer {
128
- return serializeToBuffer(
129
- fn.selector,
130
- fn.bytecode.length,
131
- fn.bytecode,
132
- fn.functionMetadataHash,
133
- fn.artifactMetadataHash,
134
- fn.privateFunctionsArtifactTreeRoot,
135
- new Vector(fn.artifactTreeSiblingPath),
136
- fn.artifactTreeLeafIndex,
137
- );
138
- }
139
-
140
73
  function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublicWithBlockNumber, 'id'> {
141
74
  const reader = BufferReader.asReader(buffer);
142
75
  return {
143
76
  l2BlockNumber: reader.readNumber(),
144
77
  version: reader.readUInt8() as 1,
145
78
  artifactHash: reader.readObject(Fr),
146
- privateFunctions: reader.readVector({ fromBuffer: deserializePrivateFunction }),
147
- utilityFunctions: reader.readVector({ fromBuffer: deserializeUtilityFunction }),
148
79
  packedBytecode: reader.readBuffer(),
149
80
  privateFunctionsRoot: reader.readObject(Fr),
150
81
  };
151
82
  }
152
-
153
- function deserializePrivateFunction(buffer: Buffer | BufferReader): ExecutablePrivateFunctionWithMembershipProof {
154
- const reader = BufferReader.asReader(buffer);
155
- return {
156
- selector: reader.readObject(FunctionSelector),
157
- vkHash: reader.readObject(Fr),
158
- bytecode: reader.readBuffer(),
159
- functionMetadataHash: reader.readObject(Fr),
160
- artifactMetadataHash: reader.readObject(Fr),
161
- utilityFunctionsTreeRoot: reader.readObject(Fr),
162
- privateFunctionTreeSiblingPath: reader.readVector(Fr),
163
- privateFunctionTreeLeafIndex: reader.readNumber(),
164
- artifactTreeSiblingPath: reader.readVector(Fr),
165
- artifactTreeLeafIndex: reader.readNumber(),
166
- };
167
- }
168
-
169
- function deserializeUtilityFunction(buffer: Buffer | BufferReader): UtilityFunctionWithMembershipProof {
170
- const reader = BufferReader.asReader(buffer);
171
- return {
172
- selector: reader.readObject(FunctionSelector),
173
- bytecode: reader.readBuffer(),
174
- functionMetadataHash: reader.readObject(Fr),
175
- artifactMetadataHash: reader.readObject(Fr),
176
- privateFunctionsArtifactTreeRoot: reader.readObject(Fr),
177
- artifactTreeSiblingPath: reader.readVector(Fr),
178
- artifactTreeLeafIndex: reader.readNumber(),
179
- };
180
- }
@@ -27,11 +27,14 @@ export class ContractInstanceStore {
27
27
 
28
28
  addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise<void> {
29
29
  return this.db.transactionAsync(async () => {
30
- await this.#contractInstances.set(
31
- contractInstance.address.toString(),
32
- new SerializableContractInstance(contractInstance).toBuffer(),
33
- );
34
- await this.#contractInstancePublishedAt.set(contractInstance.address.toString(), blockNumber);
30
+ const key = contractInstance.address.toString();
31
+ if (await this.#contractInstances.hasAsync(key)) {
32
+ throw new Error(
33
+ `Contract instance at ${key} already exists (deployed at block ${await this.#contractInstancePublishedAt.getAsync(key)}), cannot add again at block ${blockNumber}`,
34
+ );
35
+ }
36
+ await this.#contractInstances.set(key, new SerializableContractInstance(contractInstance).toBuffer());
37
+ await this.#contractInstancePublishedAt.set(key, blockNumber);
35
38
  });
36
39
  }
37
40