@aztec/archiver 0.0.1-commit.381b1a9 → 0.0.1-commit.3a4ae741b

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 (65) hide show
  1. package/dest/archiver.d.ts +1 -2
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +28 -15
  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 +3 -3
  11. package/dest/factory.d.ts.map +1 -1
  12. package/dest/factory.js +16 -13
  13. package/dest/l1/data_retrieval.d.ts +6 -3
  14. package/dest/l1/data_retrieval.d.ts.map +1 -1
  15. package/dest/l1/data_retrieval.js +12 -6
  16. package/dest/modules/data_source_base.d.ts +3 -3
  17. package/dest/modules/data_source_base.d.ts.map +1 -1
  18. package/dest/modules/data_source_base.js +4 -4
  19. package/dest/modules/data_store_updater.d.ts +7 -10
  20. package/dest/modules/data_store_updater.d.ts.map +1 -1
  21. package/dest/modules/data_store_updater.js +44 -74
  22. package/dest/modules/instrumentation.d.ts +12 -1
  23. package/dest/modules/instrumentation.d.ts.map +1 -1
  24. package/dest/modules/instrumentation.js +10 -0
  25. package/dest/modules/l1_synchronizer.d.ts +1 -1
  26. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  27. package/dest/modules/l1_synchronizer.js +17 -9
  28. package/dest/store/block_store.d.ts +5 -5
  29. package/dest/store/block_store.d.ts.map +1 -1
  30. package/dest/store/block_store.js +30 -48
  31. package/dest/store/contract_class_store.d.ts +2 -3
  32. package/dest/store/contract_class_store.d.ts.map +1 -1
  33. package/dest/store/contract_class_store.js +7 -67
  34. package/dest/store/contract_instance_store.d.ts +1 -1
  35. package/dest/store/contract_instance_store.d.ts.map +1 -1
  36. package/dest/store/contract_instance_store.js +6 -2
  37. package/dest/store/kv_archiver_store.d.ts +16 -16
  38. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  39. package/dest/store/kv_archiver_store.js +18 -17
  40. package/dest/store/log_store.d.ts +6 -3
  41. package/dest/store/log_store.d.ts.map +1 -1
  42. package/dest/store/log_store.js +45 -6
  43. package/dest/store/message_store.d.ts +5 -1
  44. package/dest/store/message_store.d.ts.map +1 -1
  45. package/dest/store/message_store.js +13 -0
  46. package/dest/test/fake_l1_state.d.ts +1 -1
  47. package/dest/test/fake_l1_state.d.ts.map +1 -1
  48. package/dest/test/fake_l1_state.js +11 -3
  49. package/package.json +13 -13
  50. package/src/archiver.ts +31 -13
  51. package/src/config.ts +8 -1
  52. package/src/errors.ts +40 -24
  53. package/src/factory.ts +18 -11
  54. package/src/l1/data_retrieval.ts +17 -9
  55. package/src/modules/data_source_base.ts +8 -3
  56. package/src/modules/data_store_updater.ts +45 -104
  57. package/src/modules/instrumentation.ts +20 -0
  58. package/src/modules/l1_synchronizer.ts +27 -20
  59. package/src/store/block_store.ts +31 -56
  60. package/src/store/contract_class_store.ts +8 -106
  61. package/src/store/contract_instance_store.ts +8 -5
  62. package/src/store/kv_archiver_store.ts +21 -28
  63. package/src/store/log_store.ts +60 -15
  64. package/src/store/message_store.ts +19 -0
  65. package/src/test/fake_l1_state.ts +15 -5
@@ -165,16 +165,21 @@ export abstract class ArchiverDataSourceBase
165
165
  return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
166
166
  }
167
167
 
168
- public getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
169
- return this.store.getPrivateLogsByTags(tags, page);
168
+ public getPrivateLogsByTags(
169
+ tags: SiloedTag[],
170
+ page?: number,
171
+ upToBlockNumber?: BlockNumber,
172
+ ): Promise<TxScopedL2Log[][]> {
173
+ return this.store.getPrivateLogsByTags(tags, page, upToBlockNumber);
170
174
  }
171
175
 
172
176
  public getPublicLogsByTagsFromContract(
173
177
  contractAddress: AztecAddress,
174
178
  tags: Tag[],
175
179
  page?: number,
180
+ upToBlockNumber?: BlockNumber,
176
181
  ): Promise<TxScopedL2Log[][]> {
177
- return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page);
182
+ return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
178
183
  }
179
184
 
180
185
  public getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
@@ -1,12 +1,7 @@
1
1
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
2
  import { filterAsync } from '@aztec/foundation/collection';
3
- import { Fr } from '@aztec/foundation/curves/bn254';
4
3
  import { createLogger } from '@aztec/foundation/log';
5
- import {
6
- ContractClassPublishedEvent,
7
- PrivateFunctionBroadcastedEvent,
8
- UtilityFunctionBroadcastedEvent,
9
- } from '@aztec/protocol-contracts/class-registry';
4
+ import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
10
5
  import {
11
6
  ContractInstancePublishedEvent,
12
7
  ContractInstanceUpdatedEvent,
@@ -14,18 +9,13 @@ import {
14
9
  import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
15
10
  import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
16
11
  import {
17
- type ExecutablePrivateFunctionWithMembershipProof,
18
- type UtilityFunctionWithMembershipProof,
12
+ type ContractClassPublicWithCommitment,
19
13
  computeContractAddressFromInstance,
20
- computePublicBytecodeCommitment,
21
- isValidPrivateFunctionMembershipProof,
22
- isValidUtilityFunctionMembershipProof,
14
+ computeContractClassId,
23
15
  } from '@aztec/stdlib/contract';
24
16
  import type { ContractClassLog, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
25
17
  import type { UInt64 } from '@aztec/stdlib/types';
26
18
 
27
- import groupBy from 'lodash.groupby';
28
-
29
19
  import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
30
20
  import type { L2TipsCache } from '../store/l2_tips_cache.js';
31
21
 
@@ -54,29 +44,28 @@ export class ArchiverDataStoreUpdater {
54
44
  ) {}
55
45
 
56
46
  /**
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.
59
- * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
60
- * and individually broadcasted functions from the block logs.
47
+ * Adds a proposed block to the store with contract class/instance extraction from logs.
48
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
49
+ * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the block logs.
61
50
  *
62
- * @param blocks - The proposed L2 blocks to add.
51
+ * @param block - The proposed L2 block to add.
63
52
  * @param pendingChainValidationStatus - Optional validation status to set.
64
53
  * @returns True if the operation is successful.
65
54
  */
66
- public async addProposedBlocks(
67
- blocks: L2Block[],
55
+ public async addProposedBlock(
56
+ block: L2Block,
68
57
  pendingChainValidationStatus?: ValidateCheckpointResult,
69
58
  ): Promise<boolean> {
70
59
  const result = await this.store.transactionAsync(async () => {
71
- await this.store.addProposedBlocks(blocks);
60
+ await this.store.addProposedBlock(block);
72
61
 
73
62
  const opResults = await Promise.all([
74
63
  // Update the pending chain validation status if provided
75
64
  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)),
65
+ // Add any logs emitted during the retrieved block
66
+ this.store.addLogs([block]),
67
+ // Unroll all logs emitted during the retrieved block and extract any contract classes and instances from it
68
+ this.addContractDataToDb(block),
80
69
  ]);
81
70
 
82
71
  await this.l2TipsCache?.refresh();
@@ -89,8 +78,7 @@ export class ArchiverDataStoreUpdater {
89
78
  * Reconciles local blocks with incoming checkpoints from L1.
90
79
  * Adds new checkpoints to the store with contract class/instance extraction from logs.
91
80
  * Prunes any local blocks that conflict with checkpoint data (by comparing archive roots).
92
- * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
93
- * and individually broadcasted functions from the checkpoint block logs.
81
+ * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the checkpoint block logs.
94
82
  *
95
83
  * @param checkpoints - The published checkpoints to add.
96
84
  * @param pendingChainValidationStatus - Optional validation status to set.
@@ -110,7 +98,7 @@ export class ArchiverDataStoreUpdater {
110
98
 
111
99
  await this.store.addCheckpoints(checkpoints);
112
100
 
113
- // Filter out blocks that were already inserted via addProposedBlocks() to avoid duplicating logs/contract data
101
+ // Filter out blocks that were already inserted via addProposedBlock() to avoid duplicating logs/contract data
114
102
  const newBlocks = checkpoints
115
103
  .flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks)
116
104
  .filter(b => lastAlreadyInsertedBlockNumber === undefined || b.number > lastAlreadyInsertedBlockNumber);
@@ -315,9 +303,6 @@ export class ArchiverDataStoreUpdater {
315
303
  this.updatePublishedContractClasses(contractClassLogs, block.number, operation),
316
304
  this.updateDeployedContractInstances(privateLogs, block.number, operation),
317
305
  this.updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, operation),
318
- operation === Operation.Store
319
- ? this.storeBroadcastedIndividualFunctions(contractClassLogs, block.number)
320
- : Promise.resolve(true),
321
306
  ])
322
307
  ).every(Boolean);
323
308
  }
@@ -334,18 +319,37 @@ export class ArchiverDataStoreUpdater {
334
319
  .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
335
320
  .map(log => ContractClassPublishedEvent.fromLog(log));
336
321
 
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) {
322
+ if (operation == Operation.Delete) {
323
+ const contractClasses = contractClassPublishedEvents.map(e => e.toContractClassPublic());
324
+ if (contractClasses.length > 0) {
325
+ contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
347
326
  return await this.store.deleteContractClasses(contractClasses, blockNum);
348
327
  }
328
+ return true;
329
+ }
330
+
331
+ // Compute bytecode commitments and validate class IDs in a single pass.
332
+ const contractClasses: ContractClassPublicWithCommitment[] = [];
333
+ for (const event of contractClassPublishedEvents) {
334
+ const contractClass = await event.toContractClassPublicWithBytecodeCommitment();
335
+ const computedClassId = await computeContractClassId({
336
+ artifactHash: contractClass.artifactHash,
337
+ privateFunctionsRoot: contractClass.privateFunctionsRoot,
338
+ publicBytecodeCommitment: contractClass.publicBytecodeCommitment,
339
+ });
340
+ if (!computedClassId.equals(contractClass.id)) {
341
+ this.log.warn(
342
+ `Skipping contract class with mismatched id at block ${blockNum}. Claimed ${contractClass.id}, computed ${computedClassId}`,
343
+ { blockNum, contractClassId: event.contractClassId.toString() },
344
+ );
345
+ continue;
346
+ }
347
+ contractClasses.push(contractClass);
348
+ }
349
+
350
+ if (contractClasses.length > 0) {
351
+ contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
352
+ return await this.store.addContractClasses(contractClasses, blockNum);
349
353
  }
350
354
  return true;
351
355
  }
@@ -417,67 +421,4 @@ export class ArchiverDataStoreUpdater {
417
421
  }
418
422
  return true;
419
423
  }
420
-
421
- /**
422
- * Stores the functions that were broadcasted individually.
423
- *
424
- * @dev Beware that there is not a delete variant of this, since they are added to contract classes
425
- * and will be deleted as part of the class if needed.
426
- */
427
- private async storeBroadcastedIndividualFunctions(
428
- allLogs: ContractClassLog[],
429
- _blockNum: BlockNumber,
430
- ): Promise<boolean> {
431
- // Filter out private and utility function broadcast events
432
- const privateFnEvents = allLogs
433
- .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
434
- .map(log => PrivateFunctionBroadcastedEvent.fromLog(log));
435
- const utilityFnEvents = allLogs
436
- .filter(log => UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log))
437
- .map(log => UtilityFunctionBroadcastedEvent.fromLog(log));
438
-
439
- // Group all events by contract class id
440
- for (const [classIdString, classEvents] of Object.entries(
441
- groupBy([...privateFnEvents, ...utilityFnEvents], e => e.contractClassId.toString()),
442
- )) {
443
- const contractClassId = Fr.fromHexString(classIdString);
444
- const contractClass = await this.store.getContractClass(contractClassId);
445
- if (!contractClass) {
446
- this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
447
- continue;
448
- }
449
-
450
- // Split private and utility functions, and filter out invalid ones
451
- const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
452
- const privateFns = allFns.filter(
453
- (fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'utilityFunctionsTreeRoot' in fn,
454
- );
455
- const utilityFns = allFns.filter(
456
- (fn): fn is UtilityFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
457
- );
458
-
459
- const privateFunctionsWithValidity = await Promise.all(
460
- privateFns.map(async fn => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })),
461
- );
462
- const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
463
- const utilityFunctionsWithValidity = await Promise.all(
464
- utilityFns.map(async fn => ({
465
- fn,
466
- valid: await isValidUtilityFunctionMembershipProof(fn, contractClass),
467
- })),
468
- );
469
- const validUtilityFns = utilityFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
470
- const validFnCount = validPrivateFns.length + validUtilityFns.length;
471
- if (validFnCount !== allFns.length) {
472
- this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
473
- }
474
-
475
- // Store the functions in the contract class in a single operation
476
- if (validFnCount > 0) {
477
- this.log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
478
- }
479
- await this.store.addFunctions(contractClassId, validPrivateFns, validUtilityFns);
480
- }
481
- return true;
482
- }
483
424
  }
@@ -1,6 +1,9 @@
1
+ import type { SlotNumber } from '@aztec/foundation/branded-types';
1
2
  import { createLogger } from '@aztec/foundation/log';
2
3
  import type { L2Block } from '@aztec/stdlib/block';
3
4
  import type { CheckpointData } from '@aztec/stdlib/checkpoint';
5
+ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
6
+ import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
4
7
  import {
5
8
  Attributes,
6
9
  type Gauge,
@@ -38,6 +41,8 @@ export class ArchiverInstrumentation {
38
41
 
39
42
  private blockProposalTxTargetCount: UpDownCounter;
40
43
 
44
+ private checkpointL1InclusionDelay: Histogram;
45
+
41
46
  private log = createLogger('archiver:instrumentation');
42
47
 
43
48
  private constructor(
@@ -85,6 +90,8 @@ export class ArchiverInstrumentation {
85
90
  },
86
91
  );
87
92
 
93
+ this.checkpointL1InclusionDelay = meter.createHistogram(Metrics.ARCHIVER_CHECKPOINT_L1_INCLUSION_DELAY);
94
+
88
95
  this.dbMetrics = new LmdbMetrics(
89
96
  meter,
90
97
  {
@@ -161,4 +168,17 @@ export class ArchiverInstrumentation {
161
168
  [Attributes.L1_BLOCK_PROPOSAL_USED_TRACE]: usedTrace,
162
169
  });
163
170
  }
171
+
172
+ /**
173
+ * Records L1 inclusion timing for a checkpoint observed on L1 (seconds into the L2 slot).
174
+ */
175
+ public processCheckpointL1Timing(data: {
176
+ slotNumber: SlotNumber;
177
+ l1Timestamp: bigint;
178
+ l1Constants: Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>;
179
+ }): void {
180
+ const slotStartTs = getTimestampForSlot(data.slotNumber, data.l1Constants);
181
+ const inclusionDelaySeconds = Number(data.l1Timestamp - slotStartTs);
182
+ this.checkpointL1InclusionDelay.record(inclusionDelaySeconds);
183
+ }
164
184
  }
@@ -3,6 +3,7 @@ import { EpochCache } from '@aztec/epoch-cache';
3
3
  import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
4
4
  import type { L1BlockId } from '@aztec/ethereum/l1-types';
5
5
  import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
6
+ import { asyncPool } from '@aztec/foundation/async-pool';
6
7
  import { maxBigint } from '@aztec/foundation/bigint';
7
8
  import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
8
9
  import { Buffer32 } from '@aztec/foundation/buffer';
@@ -245,16 +246,10 @@ export class ArchiverL1Synchronizer implements Traceable {
245
246
  const localFinalizedCheckpointNumber = await this.store.getFinalizedCheckpointNumber();
246
247
  if (localFinalizedCheckpointNumber !== finalizedCheckpointNumber) {
247
248
  await this.updater.setFinalizedCheckpointNumber(finalizedCheckpointNumber);
248
- const finalizedL2BlockNumber = await this.store.getFinalizedL2BlockNumber();
249
- this.log.info(
250
- `Updated finalized chain to checkpoint ${finalizedCheckpointNumber} (L2 block ${finalizedL2BlockNumber})`,
251
- {
252
- finalizedCheckpointNumber,
253
- previousFinalizedCheckpointNumber: localFinalizedCheckpointNumber,
254
- finalizedL2BlockNumber,
255
- finalizedL1BlockNumber,
256
- },
257
- );
249
+ this.log.info(`Updated finalized chain to checkpoint ${finalizedCheckpointNumber}`, {
250
+ finalizedCheckpointNumber,
251
+ finalizedL1BlockNumber,
252
+ });
258
253
  }
259
254
  } catch (err) {
260
255
  this.log.warn(`Failed to update finalized checkpoint: ${err}`);
@@ -339,17 +334,20 @@ export class ArchiverL1Synchronizer implements Traceable {
339
334
 
340
335
  const checkpointsToUnwind = localPendingCheckpointNumber - provenCheckpointNumber;
341
336
 
342
- const checkpointPromises = Array.from({ length: checkpointsToUnwind })
343
- .fill(0)
344
- .map((_, i) => this.store.getCheckpointData(CheckpointNumber(i + pruneFrom)));
345
- const checkpoints = await Promise.all(checkpointPromises);
346
-
347
- const blockPromises = await Promise.all(
348
- checkpoints
349
- .filter(isDefined)
350
- .map(cp => this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber))),
337
+ // Fetch checkpoints and blocks in bounded batches to avoid unbounded concurrent
338
+ // promises when the gap between local pending and proven checkpoint numbers is large.
339
+ const BATCH_SIZE = 10;
340
+ const indices = Array.from({ length: checkpointsToUnwind }, (_, i) => CheckpointNumber(i + pruneFrom));
341
+ const checkpoints = (await asyncPool(BATCH_SIZE, indices, idx => this.store.getCheckpointData(idx))).filter(
342
+ isDefined,
351
343
  );
352
- const newBlocks = blockPromises.filter(isDefined).flat();
344
+ const newBlocks = (
345
+ await asyncPool(BATCH_SIZE, checkpoints, cp =>
346
+ this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber)),
347
+ )
348
+ )
349
+ .filter(isDefined)
350
+ .flat();
353
351
 
354
352
  // Emit an event for listening services to react to the chain prune
355
353
  this.events.emit(L2BlockSourceEvents.L2PruneUnproven, {
@@ -397,6 +395,7 @@ export class ArchiverL1Synchronizer implements Traceable {
397
395
  const localMessagesInserted = await this.store.getTotalL1ToL2MessageCount();
398
396
  const localLastMessage = await this.store.getLastL1ToL2Message();
399
397
  const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
398
+ await this.store.setInboxTreeInProgress(remoteMessagesState.treeInProgress);
400
399
 
401
400
  this.log.trace(`Retrieved remote inbox state at L1 block ${currentL1BlockNumber}.`, {
402
401
  localMessagesInserted,
@@ -831,6 +830,14 @@ export class ArchiverL1Synchronizer implements Traceable {
831
830
  );
832
831
  }
833
832
 
833
+ for (const published of validCheckpoints) {
834
+ this.instrumentation.processCheckpointL1Timing({
835
+ slotNumber: published.checkpoint.header.slotNumber,
836
+ l1Timestamp: published.l1.timestamp,
837
+ l1Constants: this.l1Constants,
838
+ });
839
+ }
840
+
834
841
  try {
835
842
  const updatedValidationResult =
836
843
  rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
@@ -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
  });