@aztec/archiver 4.1.2 → 4.2.0-aztecnr-rc.2

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.
@@ -14,10 +14,11 @@ import {
14
14
  import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
15
15
  import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
16
16
  import {
17
+ type ContractClassPublicWithCommitment,
17
18
  type ExecutablePrivateFunctionWithMembershipProof,
18
19
  type UtilityFunctionWithMembershipProof,
19
20
  computeContractAddressFromInstance,
20
- computePublicBytecodeCommitment,
21
+ computeContractClassId,
21
22
  isValidPrivateFunctionMembershipProof,
22
23
  isValidUtilityFunctionMembershipProof,
23
24
  } from '@aztec/stdlib/contract';
@@ -54,29 +55,29 @@ export class ArchiverDataStoreUpdater {
54
55
  ) {}
55
56
 
56
57
  /**
57
- * Adds proposed blocks to the store with contract class/instance extraction from logs.
58
- * These are uncheckpointed blocks that have been proposed by the sequencer but not yet included in a checkpoint on L1.
58
+ * Adds a proposed block to the store with contract class/instance extraction from logs.
59
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
59
60
  * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
60
61
  * and individually broadcasted functions from the block logs.
61
62
  *
62
- * @param blocks - The proposed L2 blocks to add.
63
+ * @param block - The proposed L2 block to add.
63
64
  * @param pendingChainValidationStatus - Optional validation status to set.
64
65
  * @returns True if the operation is successful.
65
66
  */
66
- public async addProposedBlocks(
67
- blocks: L2Block[],
67
+ public async addProposedBlock(
68
+ block: L2Block,
68
69
  pendingChainValidationStatus?: ValidateCheckpointResult,
69
70
  ): Promise<boolean> {
70
71
  const result = await this.store.transactionAsync(async () => {
71
- await this.store.addProposedBlocks(blocks);
72
+ await this.store.addProposedBlock(block);
72
73
 
73
74
  const opResults = await Promise.all([
74
75
  // Update the pending chain validation status if provided
75
76
  pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
76
- // Add any logs emitted during the retrieved blocks
77
- this.store.addLogs(blocks),
78
- // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
79
- ...blocks.map(block => this.addContractDataToDb(block)),
77
+ // Add any logs emitted during the retrieved block
78
+ this.store.addLogs([block]),
79
+ // Unroll all logs emitted during the retrieved block and extract any contract classes and instances from it
80
+ this.addContractDataToDb(block),
80
81
  ]);
81
82
 
82
83
  await this.l2TipsCache?.refresh();
@@ -110,7 +111,7 @@ export class ArchiverDataStoreUpdater {
110
111
 
111
112
  await this.store.addCheckpoints(checkpoints);
112
113
 
113
- // Filter out blocks that were already inserted via addProposedBlocks() to avoid duplicating logs/contract data
114
+ // Filter out blocks that were already inserted via addProposedBlock() to avoid duplicating logs/contract data
114
115
  const newBlocks = checkpoints
115
116
  .flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks)
116
117
  .filter(b => lastAlreadyInsertedBlockNumber === undefined || b.number > lastAlreadyInsertedBlockNumber);
@@ -334,18 +335,37 @@ export class ArchiverDataStoreUpdater {
334
335
  .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
335
336
  .map(log => ContractClassPublishedEvent.fromLog(log));
336
337
 
337
- const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic()));
338
- if (contractClasses.length > 0) {
339
- contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
340
- if (operation == Operation.Store) {
341
- // TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive
342
- const commitments = await Promise.all(
343
- contractClasses.map(c => computePublicBytecodeCommitment(c.packedBytecode)),
344
- );
345
- return await this.store.addContractClasses(contractClasses, commitments, blockNum);
346
- } else if (operation == Operation.Delete) {
338
+ if (operation == Operation.Delete) {
339
+ const contractClasses = contractClassPublishedEvents.map(e => e.toContractClassPublic());
340
+ if (contractClasses.length > 0) {
341
+ contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
347
342
  return await this.store.deleteContractClasses(contractClasses, blockNum);
348
343
  }
344
+ return true;
345
+ }
346
+
347
+ // Compute bytecode commitments and validate class IDs in a single pass.
348
+ const contractClasses: ContractClassPublicWithCommitment[] = [];
349
+ for (const event of contractClassPublishedEvents) {
350
+ const contractClass = await event.toContractClassPublicWithBytecodeCommitment();
351
+ const computedClassId = await computeContractClassId({
352
+ artifactHash: contractClass.artifactHash,
353
+ privateFunctionsRoot: contractClass.privateFunctionsRoot,
354
+ publicBytecodeCommitment: contractClass.publicBytecodeCommitment,
355
+ });
356
+ if (!computedClassId.equals(contractClass.id)) {
357
+ this.log.warn(
358
+ `Skipping contract class with mismatched id at block ${blockNum}. Claimed ${contractClass.id}, computed ${computedClassId}`,
359
+ { blockNum, contractClassId: event.contractClassId.toString() },
360
+ );
361
+ continue;
362
+ }
363
+ contractClasses.push(contractClass);
364
+ }
365
+
366
+ if (contractClasses.length > 0) {
367
+ contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
368
+ return await this.store.addContractClasses(contractClasses, blockNum);
349
369
  }
350
370
  return true;
351
371
  }
@@ -35,15 +35,14 @@ import {
35
35
  } from '@aztec/stdlib/tx';
36
36
 
37
37
  import {
38
+ BlockAlreadyCheckpointedError,
38
39
  BlockArchiveNotConsistentError,
39
40
  BlockIndexNotSequentialError,
40
41
  BlockNotFoundError,
41
42
  BlockNumberNotSequentialError,
42
43
  CannotOverwriteCheckpointedBlockError,
43
44
  CheckpointNotFoundError,
44
- CheckpointNumberNotConsistentError,
45
45
  CheckpointNumberNotSequentialError,
46
- InitialBlockNumberNotSequentialError,
47
46
  InitialCheckpointNumberNotSequentialError,
48
47
  } from '../errors.js';
49
48
 
@@ -147,23 +146,18 @@ export class BlockStore {
147
146
  }
148
147
 
149
148
  /**
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.
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.
152
151
  * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
153
- * @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.
154
153
  * @returns True if the operation is successful.
155
154
  */
156
- async addProposedBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
157
- if (blocks.length === 0) {
158
- return true;
159
- }
160
-
155
+ async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
161
156
  return await this.db.transactionAsync(async () => {
162
- // Check that the block immediately before the first block to be added is present in the store.
163
- const firstBlockNumber = blocks[0].number;
164
- const firstBlockCheckpointNumber = blocks[0].checkpointNumber;
165
- const firstBlockIndex = blocks[0].indexWithinCheckpoint;
166
- 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;
167
161
 
168
162
  // Extract the latest block and checkpoint numbers
169
163
  const previousBlockNumber = await this.getLatestBlockNumber();
@@ -171,71 +165,52 @@ export class BlockStore {
171
165
 
172
166
  // Verify we're not overwriting checkpointed blocks
173
167
  const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
174
- if (!opts.force && firstBlockNumber <= lastCheckpointedBlockNumber) {
175
- 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);
176
175
  }
177
176
 
178
- // Check that the first block number is the expected one
179
- if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
180
- 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);
181
180
  }
182
181
 
183
182
  // The same check as above but for checkpoints
184
- if (!opts.force && previousCheckpointNumber !== firstBlockCheckpointNumber - 1) {
185
- throw new InitialCheckpointNumberNotSequentialError(firstBlockCheckpointNumber, previousCheckpointNumber);
183
+ if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
184
+ throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
186
185
  }
187
186
 
188
187
  // Extract the previous block if there is one and see if it is for the same checkpoint or not
189
188
  const previousBlockResult = await this.getBlock(previousBlockNumber);
190
189
 
191
- let expectedFirstblockIndex = 0;
190
+ let expectedBlockIndex = 0;
192
191
  let previousBlockIndex: number | undefined = undefined;
193
192
  if (previousBlockResult !== undefined) {
194
- if (previousBlockResult.checkpointNumber === firstBlockCheckpointNumber) {
193
+ if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
195
194
  // The previous block is for the same checkpoint, therefore our index should follow it
196
195
  previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
197
- expectedFirstblockIndex = previousBlockIndex + 1;
196
+ expectedBlockIndex = previousBlockIndex + 1;
198
197
  }
199
- if (!previousBlockResult.archive.root.equals(firstBlockLastArchive)) {
198
+ if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
200
199
  throw new BlockArchiveNotConsistentError(
201
- firstBlockNumber,
200
+ blockNumber,
202
201
  previousBlockResult.number,
203
- firstBlockLastArchive,
202
+ blockLastArchive,
204
203
  previousBlockResult.archive.root,
205
204
  );
206
205
  }
207
206
  }
208
207
 
209
- // Now check that the first block has the expected index value
210
- if (!opts.force && expectedFirstblockIndex !== firstBlockIndex) {
211
- 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);
212
211
  }
213
212
 
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.
215
- let previousBlock: L2Block | undefined = undefined;
216
- for (const block of blocks) {
217
- if (!opts.force && previousBlock) {
218
- if (previousBlock.number + 1 !== block.number) {
219
- throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
220
- }
221
- if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
222
- throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
223
- }
224
- if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
225
- throw new BlockArchiveNotConsistentError(
226
- block.number,
227
- previousBlock.number,
228
- block.header.lastArchive.root,
229
- previousBlock.archive.root,
230
- );
231
- }
232
- }
233
- if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
234
- throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
235
- }
236
- previousBlock = block;
237
- await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
238
- }
213
+ await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
239
214
 
240
215
  return true;
241
216
  });
@@ -29,11 +29,15 @@ export class ContractClassStore {
29
29
  blockNumber: number,
30
30
  ): Promise<void> {
31
31
  await this.db.transactionAsync(async () => {
32
- await this.#contractClasses.setIfNotExists(
33
- contractClass.id.toString(),
32
+ const key = contractClass.id.toString();
33
+ if (await this.#contractClasses.hasAsync(key)) {
34
+ throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`);
35
+ }
36
+ await this.#contractClasses.set(
37
+ key,
34
38
  serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
35
39
  );
36
- await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer());
40
+ await this.#bytecodeCommitments.set(key, bytecodeCommitment.toBuffer());
37
41
  });
38
42
  }
39
43
 
@@ -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
 
@@ -16,6 +16,7 @@ import {
16
16
  import type { CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
17
17
  import type {
18
18
  ContractClassPublic,
19
+ ContractClassPublicWithCommitment,
19
20
  ContractDataSource,
20
21
  ContractInstanceUpdateWithAddress,
21
22
  ContractInstanceWithAddress,
@@ -167,19 +168,14 @@ export class KVArchiverDataStore implements ContractDataSource {
167
168
 
168
169
  /**
169
170
  * Add new contract classes from an L2 block to the store's list.
170
- * @param data - List of contract classes to be added.
171
- * @param bytecodeCommitments - Bytecode commitments for the contract classes.
171
+ * @param data - List of contract classes (with bytecode commitments) to be added.
172
172
  * @param blockNumber - Number of the L2 block the contracts were registered in.
173
173
  * @returns True if the operation is successful.
174
174
  */
175
- async addContractClasses(
176
- data: ContractClassPublic[],
177
- bytecodeCommitments: Fr[],
178
- blockNumber: BlockNumber,
179
- ): Promise<boolean> {
175
+ async addContractClasses(data: ContractClassPublicWithCommitment[], blockNumber: BlockNumber): Promise<boolean> {
180
176
  return (
181
177
  await Promise.all(
182
- data.map((c, i) => this.#contractClassStore.addContractClass(c, bytecodeCommitments[i], blockNumber)),
178
+ data.map(c => this.#contractClassStore.addContractClass(c, c.publicBytecodeCommitment, blockNumber)),
183
179
  )
184
180
  ).every(Boolean);
185
181
  }
@@ -245,14 +241,14 @@ export class KVArchiverDataStore implements ContractDataSource {
245
241
  }
246
242
 
247
243
  /**
248
- * Append new proposed blocks to the store's list.
249
- * These are uncheckpointed blocks that have been proposed by the sequencer but not yet included in a checkpoint on L1.
244
+ * Append a new proposed block to the store.
245
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
250
246
  * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
251
- * @param blocks - The proposed L2 blocks to be added to the store.
247
+ * @param block - The proposed L2 block to be added to the store.
252
248
  * @returns True if the operation is successful.
253
249
  */
254
- addProposedBlocks(blocks: L2Block[], opts: { force?: boolean; checkpointNumber?: number } = {}): Promise<boolean> {
255
- return this.#blockStore.addProposedBlocks(blocks, opts);
250
+ addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
251
+ return this.#blockStore.addProposedBlock(block, opts);
256
252
  }
257
253
 
258
254
  /**