@aztec/archiver 0.0.1-commit.e588bc7e5 → 0.0.1-commit.e5a3663dd

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 (105) hide show
  1. package/dest/archiver.d.ts +19 -11
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +96 -53
  4. package/dest/config.d.ts +3 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +14 -3
  7. package/dest/errors.d.ts +32 -5
  8. package/dest/errors.d.ts.map +1 -1
  9. package/dest/errors.js +51 -6
  10. package/dest/factory.d.ts +4 -4
  11. package/dest/factory.d.ts.map +1 -1
  12. package/dest/factory.js +13 -10
  13. package/dest/index.d.ts +10 -3
  14. package/dest/index.d.ts.map +1 -1
  15. package/dest/index.js +9 -2
  16. package/dest/l1/calldata_retriever.d.ts +2 -1
  17. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  18. package/dest/l1/calldata_retriever.js +9 -4
  19. package/dest/l1/data_retrieval.d.ts +18 -9
  20. package/dest/l1/data_retrieval.d.ts.map +1 -1
  21. package/dest/l1/data_retrieval.js +13 -19
  22. package/dest/l1/validate_historical_logs.d.ts +23 -0
  23. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  24. package/dest/l1/validate_historical_logs.js +108 -0
  25. package/dest/modules/contract_data_source_adapter.d.ts +25 -0
  26. package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
  27. package/dest/modules/contract_data_source_adapter.js +42 -0
  28. package/dest/modules/data_source_base.d.ts +16 -10
  29. package/dest/modules/data_source_base.d.ts.map +1 -1
  30. package/dest/modules/data_source_base.js +71 -60
  31. package/dest/modules/data_store_updater.d.ts +16 -9
  32. package/dest/modules/data_store_updater.d.ts.map +1 -1
  33. package/dest/modules/data_store_updater.js +52 -40
  34. package/dest/modules/instrumentation.d.ts +7 -2
  35. package/dest/modules/instrumentation.d.ts.map +1 -1
  36. package/dest/modules/instrumentation.js +22 -6
  37. package/dest/modules/l1_synchronizer.d.ts +8 -4
  38. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  39. package/dest/modules/l1_synchronizer.js +212 -79
  40. package/dest/modules/validation.d.ts +4 -3
  41. package/dest/modules/validation.d.ts.map +1 -1
  42. package/dest/modules/validation.js +4 -4
  43. package/dest/store/block_store.d.ts +60 -21
  44. package/dest/store/block_store.d.ts.map +1 -1
  45. package/dest/store/block_store.js +229 -70
  46. package/dest/store/contract_class_store.d.ts +17 -3
  47. package/dest/store/contract_class_store.d.ts.map +1 -1
  48. package/dest/store/contract_class_store.js +17 -1
  49. package/dest/store/contract_instance_store.d.ts +28 -1
  50. package/dest/store/contract_instance_store.d.ts.map +1 -1
  51. package/dest/store/contract_instance_store.js +31 -0
  52. package/dest/store/data_stores.d.ts +68 -0
  53. package/dest/store/data_stores.d.ts.map +1 -0
  54. package/dest/store/data_stores.js +50 -0
  55. package/dest/store/function_names_cache.d.ts +17 -0
  56. package/dest/store/function_names_cache.d.ts.map +1 -0
  57. package/dest/store/function_names_cache.js +30 -0
  58. package/dest/store/l2_tips_cache.d.ts +1 -1
  59. package/dest/store/l2_tips_cache.d.ts.map +1 -1
  60. package/dest/store/l2_tips_cache.js +3 -3
  61. package/dest/store/log_store.d.ts +1 -1
  62. package/dest/store/log_store.d.ts.map +1 -1
  63. package/dest/store/log_store.js +2 -4
  64. package/dest/store/message_store.d.ts +9 -3
  65. package/dest/store/message_store.d.ts.map +1 -1
  66. package/dest/store/message_store.js +31 -1
  67. package/dest/test/fake_l1_state.d.ts +14 -3
  68. package/dest/test/fake_l1_state.d.ts.map +1 -1
  69. package/dest/test/fake_l1_state.js +55 -15
  70. package/dest/test/mock_l2_block_source.d.ts +12 -3
  71. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  72. package/dest/test/mock_l2_block_source.js +24 -2
  73. package/dest/test/noop_l1_archiver.d.ts +4 -4
  74. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  75. package/dest/test/noop_l1_archiver.js +9 -7
  76. package/package.json +13 -13
  77. package/src/archiver.ts +113 -52
  78. package/src/config.ts +15 -1
  79. package/src/errors.ts +75 -8
  80. package/src/factory.ts +11 -10
  81. package/src/index.ts +17 -2
  82. package/src/l1/calldata_retriever.ts +15 -4
  83. package/src/l1/data_retrieval.ts +30 -35
  84. package/src/l1/validate_historical_logs.ts +140 -0
  85. package/src/modules/contract_data_source_adapter.ts +59 -0
  86. package/src/modules/data_source_base.ts +75 -57
  87. package/src/modules/data_store_updater.ts +71 -39
  88. package/src/modules/instrumentation.ts +27 -7
  89. package/src/modules/l1_synchronizer.ts +301 -83
  90. package/src/modules/validation.ts +8 -7
  91. package/src/store/block_store.ts +264 -77
  92. package/src/store/contract_class_store.ts +28 -2
  93. package/src/store/contract_instance_store.ts +43 -0
  94. package/src/store/data_stores.ts +108 -0
  95. package/src/store/function_names_cache.ts +37 -0
  96. package/src/store/l2_tips_cache.ts +9 -3
  97. package/src/store/log_store.ts +2 -5
  98. package/src/store/message_store.ts +35 -2
  99. package/src/test/fake_l1_state.ts +62 -24
  100. package/src/test/mock_l2_block_source.ts +23 -2
  101. package/src/test/noop_l1_archiver.ts +9 -7
  102. package/dest/store/kv_archiver_store.d.ts +0 -377
  103. package/dest/store/kv_archiver_store.d.ts.map +0 -1
  104. package/dest/store/kv_archiver_store.js +0 -494
  105. package/src/store/kv_archiver_store.ts +0 -713
package/src/archiver.ts CHANGED
@@ -11,7 +11,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
11
11
  import { type Logger, createLogger } from '@aztec/foundation/log';
12
12
  import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
13
13
  import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
14
- import { DateProvider } from '@aztec/foundation/timer';
14
+ import { DateProvider, elapsed } from '@aztec/foundation/timer';
15
15
  import {
16
16
  type ArchiverEmitter,
17
17
  L2Block,
@@ -25,18 +25,20 @@ import {
25
25
  getEpochAtSlot,
26
26
  getSlotAtNextL1Block,
27
27
  getSlotRangeForEpoch,
28
+ getTimestampForSlot,
28
29
  getTimestampRangeForEpoch,
29
30
  } from '@aztec/stdlib/epoch-helpers';
30
31
  import { type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
31
32
 
32
33
  import { type ArchiverConfig, mapArchiverConfig } from './config.js';
33
- import { BlockAlreadyCheckpointedError, NoBlobBodiesFoundError } from './errors.js';
34
+ import { BlockAlreadyCheckpointedError, BlockOrCheckpointSlotExpiredError, NoBlobBodiesFoundError } from './errors.js';
35
+ import { validateAndLogHistoricalLogsAvailability } from './l1/validate_historical_logs.js';
34
36
  import { validateAndLogTraceAvailability } from './l1/validate_trace.js';
35
37
  import { ArchiverDataSourceBase } from './modules/data_source_base.js';
36
38
  import { ArchiverDataStoreUpdater } from './modules/data_store_updater.js';
37
39
  import type { ArchiverInstrumentation } from './modules/instrumentation.js';
38
40
  import type { ArchiverL1Synchronizer } from './modules/l1_synchronizer.js';
39
- import type { KVArchiverDataStore } from './store/kv_archiver_store.js';
41
+ import { type ArchiverDataStores, backupArchiverDataStores, getArchiverSynchPoint } from './store/data_stores.js';
40
42
  import { L2TipsCache } from './store/l2_tips_cache.js';
41
43
 
42
44
  /** Export ArchiverEmitter for use in factory and tests. */
@@ -44,11 +46,20 @@ export type { ArchiverEmitter };
44
46
 
45
47
  /** Request to add a block to the archiver, queued for processing by the sync loop. */
46
48
  type AddBlockRequest = {
49
+ type: 'block';
47
50
  block: L2Block;
48
51
  resolve: () => void;
49
52
  reject: (err: Error) => void;
50
53
  };
51
54
 
55
+ /** Request to add a proposed checkpoint to the archiver, queued for processing by the sync loop. */
56
+ type AddProposedCheckpointRequest = {
57
+ type: 'checkpoint';
58
+ checkpoint: ProposedCheckpointInput;
59
+ resolve: () => void;
60
+ reject: (err: Error) => void;
61
+ };
62
+
52
63
  export type ArchiverDeps = {
53
64
  telemetry?: TelemetryClient;
54
65
  blobClient: BlobClientInterface;
@@ -74,8 +85,8 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
74
85
  private initialSyncComplete: boolean = false;
75
86
  private initialSyncPromise: PromiseWithResolvers<void>;
76
87
 
77
- /** Queue of blocks to be added to the store, processed by the sync loop. */
78
- private blockQueue: AddBlockRequest[] = [];
88
+ /** Queue of blocks and checkpoints to be added to the store, processed by the sync loop. */
89
+ private inboundQueue: (AddBlockRequest | AddProposedCheckpointRequest)[] = [];
79
90
 
80
91
  /** Helper to handle updates to the store */
81
92
  private readonly updater: ArchiverDataStoreUpdater;
@@ -85,14 +96,16 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
85
96
 
86
97
  public readonly tracer: Tracer;
87
98
 
99
+ private readonly instrumentation: ArchiverInstrumentation;
100
+
88
101
  /**
89
102
  * Creates a new instance of the Archiver.
90
103
  * @param publicClient - A client for interacting with the Ethereum node.
91
104
  * @param debugClient - A client for interacting with the Ethereum node for debug/trace methods.
92
105
  * @param rollup - Rollup contract instance.
93
106
  * @param inbox - Inbox contract instance.
94
- * @param l1Addresses - L1 contract addresses (registry, governance proposer, slash factory, slashing proposer).
95
- * @param dataStore - An archiver data store for storage & retrieval of blocks, encrypted logs & contract data.
107
+ * @param l1Addresses - L1 contract addresses (registry, governance proposer, slashing proposer).
108
+ * @param dataStores - Archiver substores for storage & retrieval of blocks, encrypted logs & contract data.
96
109
  * @param config - Archiver configuration options.
97
110
  * @param blobClient - Client for retrieving blob data.
98
111
  * @param dateProvider - Provider for current date/time.
@@ -106,15 +119,18 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
106
119
  private readonly rollup: RollupContract,
107
120
  private readonly l1Addresses: Pick<
108
121
  L1ContractAddresses,
109
- 'registryAddress' | 'governanceProposerAddress' | 'slashFactoryAddress'
110
- > & { slashingProposerAddress: EthAddress },
111
- readonly dataStore: KVArchiverDataStore,
122
+ 'rollupAddress' | 'registryAddress' | 'inboxAddress' | 'governanceProposerAddress'
123
+ > & {
124
+ slashingProposerAddress: EthAddress;
125
+ },
126
+ readonly dataStores: ArchiverDataStores,
112
127
  private config: {
113
128
  pollingIntervalMs: number;
114
129
  batchSize: number;
115
130
  skipValidateCheckpointAttestations?: boolean;
116
131
  maxAllowedEthClientDriftSeconds: number;
117
132
  ethereumAllowNoDebugHosts?: boolean;
133
+ skipHistoricalLogsCheck?: boolean;
118
134
  },
119
135
  private readonly blobClient: BlobClientInterface,
120
136
  instrumentation: ArchiverInstrumentation,
@@ -127,14 +143,15 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
127
143
  l2TipsCache?: L2TipsCache,
128
144
  private readonly log: Logger = createLogger('archiver'),
129
145
  ) {
130
- super(dataStore, l1Constants);
146
+ super(dataStores, l1Constants);
131
147
 
132
148
  this.tracer = instrumentation.tracer;
149
+ this.instrumentation = instrumentation;
133
150
  this.initialSyncPromise = promiseWithResolvers();
134
151
  this.synchronizer = synchronizer;
135
152
  this.events = events;
136
- this.l2TipsCache = l2TipsCache ?? new L2TipsCache(this.dataStore.blockStore);
137
- this.updater = new ArchiverDataStoreUpdater(this.dataStore, this.l2TipsCache, {
153
+ this.l2TipsCache = l2TipsCache ?? new L2TipsCache(this.dataStores.blocks);
154
+ this.updater = new ArchiverDataStoreUpdater(this.dataStores, this.l2TipsCache, {
138
155
  rollupManaLimit: l1Constants.rollupManaLimit,
139
156
  });
140
157
 
@@ -170,10 +187,23 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
170
187
  this.config.ethereumAllowNoDebugHosts ?? false,
171
188
  this.log.getBindings(),
172
189
  );
190
+ await validateAndLogHistoricalLogsAvailability(
191
+ this.publicClient,
192
+ {
193
+ rollupAddress: this.l1Addresses.rollupAddress,
194
+ inboxAddress: this.l1Addresses.inboxAddress,
195
+ registryAddress: this.l1Addresses.registryAddress,
196
+ governanceProposerAddress: this.l1Addresses.governanceProposerAddress,
197
+ },
198
+ this.config.skipHistoricalLogsCheck ?? false,
199
+ this.log.getBindings(),
200
+ );
173
201
 
174
202
  // Log initial state for the archiver
175
203
  const { l1StartBlock } = this.l1Constants;
176
- const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
204
+ const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await getArchiverSynchPoint(
205
+ this.stores,
206
+ );
177
207
  const currentL2Checkpoint = await this.getSynchedCheckpointNumber();
178
208
  this.log.info(
179
209
  `Starting archiver sync to rollup contract ${this.rollup.address} from L1 block ${blocksSynchedTo} and L2 checkpoint ${currentL2Checkpoint}`,
@@ -191,6 +221,14 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
191
221
  return this.runningPromise.trigger();
192
222
  }
193
223
 
224
+ public trySyncImmediate() {
225
+ try {
226
+ return this.syncImmediate();
227
+ } catch (err) {
228
+ this.log.error(`Failed to trigger immediate archiver sync: ${err}`, err);
229
+ }
230
+ }
231
+
194
232
  /**
195
233
  * Queues a block to be added to the archiver store and triggers processing.
196
234
  * The block will be processed by the sync loop.
@@ -199,62 +237,85 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
199
237
  * @returns A promise that resolves when the block has been added to the store, or rejects on error.
200
238
  */
201
239
  public addBlock(block: L2Block): Promise<void> {
202
- return new Promise<void>((resolve, reject) => {
203
- this.blockQueue.push({ block, resolve, reject });
204
- this.log.debug(`Queued block ${block.number} for processing`);
205
- // Trigger an immediate sync, but don't wait for it - the promise resolves when the block is processed
206
- this.syncImmediate().catch(err => {
207
- this.log.error(`Sync immediate call failed: ${err}`);
208
- });
209
- });
240
+ const promise = promiseWithResolvers<void>();
241
+ this.inboundQueue.push({ block, ...promise, type: 'block' });
242
+ this.log.debug(`Queued block ${block.number} for processing`);
243
+ void this.trySyncImmediate();
244
+ return promise.promise;
210
245
  }
211
246
 
212
- public async setProposedCheckpoint(pending: ProposedCheckpointInput): Promise<void> {
213
- await this.updater.setProposedCheckpoint(pending);
247
+ /**
248
+ * Queues a new proposed checkpoint into the archiver store.
249
+ * Checks that the checkpoint is not for an L2 slot already synced from L1.
250
+ * Resolves once the checkpoint has been processed.
251
+ */
252
+ public addProposedCheckpoint(pending: ProposedCheckpointInput): Promise<void> {
253
+ const promise = promiseWithResolvers<void>();
254
+ this.inboundQueue.push({ checkpoint: pending, ...promise, type: 'checkpoint' });
255
+ this.log.debug(`Queued checkpoint ${pending.checkpointNumber} for processing`);
256
+ void this.trySyncImmediate();
257
+ return promise.promise;
214
258
  }
215
259
 
216
260
  /**
217
- * Processes all queued blocks, adding them to the store.
261
+ * Processes all queued blocks and checkpoints, adding them to the store.
218
262
  * Called at the beginning of each sync iteration.
219
- * Blocks are processed in the order they were queued.
263
+ * Items are processed in the order they were queued.
220
264
  */
221
- private async processQueuedBlocks(): Promise<void> {
222
- if (this.blockQueue.length === 0) {
265
+ private async processInboundQueue(): Promise<void> {
266
+ if (this.inboundQueue.length === 0) {
223
267
  return;
224
268
  }
225
269
 
226
- // Take all blocks from the queue
227
- const queuedItems = this.blockQueue.splice(0, this.blockQueue.length);
228
- this.log.debug(`Processing ${queuedItems.length} queued block(s)`);
270
+ // Take all items from the queue
271
+ const queuedItems = this.inboundQueue.splice(0, this.inboundQueue.length);
272
+ this.log.debug(`Processing ${queuedItems.length} queued inbound items`);
229
273
 
230
274
  // Calculate slot threshold for validation
231
275
  const l1Timestamp = this.synchronizer.getL1Timestamp();
232
276
  const slotAtNextL1Block =
233
277
  l1Timestamp === undefined ? undefined : getSlotAtNextL1Block(l1Timestamp, this.l1Constants);
234
278
 
235
- // Process each block individually to properly resolve/reject each promise
236
- for (const { block, resolve, reject } of queuedItems) {
237
- const blockSlot = block.header.globalVariables.slotNumber;
238
- if (slotAtNextL1Block !== undefined && blockSlot < slotAtNextL1Block) {
279
+ // Helpers for manipulating blocks and checkpoints in the queue
280
+ const getSlot: (item: AddBlockRequest | AddProposedCheckpointRequest) => SlotNumber = item =>
281
+ item.type === 'block' ? item.block.header.globalVariables.slotNumber : item.checkpoint.header.slotNumber;
282
+ const getNumber: (item: AddBlockRequest | AddProposedCheckpointRequest) => number = item =>
283
+ item.type === 'block' ? item.block.number : item.checkpoint.checkpointNumber;
284
+
285
+ // Process each item individually to properly resolve/reject each promise
286
+ for (const item of queuedItems) {
287
+ const { resolve, reject, type } = item;
288
+ const itemSlot = getSlot(item);
289
+ const itemNumber = getNumber(item);
290
+ if (slotAtNextL1Block !== undefined && itemSlot < slotAtNextL1Block) {
291
+ const nextSlotTimestamp = getTimestampForSlot(slotAtNextL1Block, this.l1Constants);
239
292
  this.log.warn(
240
- `Rejecting proposed block ${block.number} for past slot ${blockSlot} (current is ${slotAtNextL1Block})`,
241
- { block: block.toBlockInfo(), l1Timestamp, slotAtNextL1Block },
293
+ `Rejecting proposed ${type} ${itemNumber} for past slot ${itemSlot} (current ${slotAtNextL1Block})`,
294
+ { number: itemNumber, type, l1Timestamp, slotAtNextL1Block, nextSlotTimestamp },
242
295
  );
243
- reject(new Error(`Block ${block.number} is for past slot ${blockSlot} (current is ${slotAtNextL1Block})`));
296
+ reject(new BlockOrCheckpointSlotExpiredError(itemSlot, nextSlotTimestamp, l1Timestamp));
244
297
  continue;
245
298
  }
246
299
 
247
300
  try {
248
- await this.updater.addProposedBlock(block);
249
- this.log.debug(`Added block ${block.number} to store`);
301
+ if (type === 'block') {
302
+ const [durationMs] = await elapsed(() => this.updater.addProposedBlock(item.block));
303
+ this.instrumentation.processNewProposedBlock(durationMs, item.block);
304
+ } else {
305
+ await this.updater.addProposedCheckpoint(item.checkpoint);
306
+ }
307
+ this.log.debug(`Added ${type} ${itemNumber} to store`);
250
308
  resolve();
251
309
  } catch (err: any) {
252
310
  if (err instanceof BlockAlreadyCheckpointedError) {
253
- this.log.debug(`Proposed block ${block.number} matches already checkpointed block, ignoring late proposal`);
311
+ this.log.debug(`Proposed block ${itemNumber} matches already checkpointed block, ignoring late proposal`);
254
312
  resolve();
255
313
  continue;
256
314
  }
257
- this.log.error(`Failed to add block ${block.number} to store: ${err.message}`);
315
+ this.log.error(`Failed to add ${type} ${itemNumber} to store: ${err.message}`, err, {
316
+ number: itemNumber,
317
+ type,
318
+ });
258
319
  reject(err);
259
320
  }
260
321
  }
@@ -270,7 +331,7 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
270
331
  @trackSpan('Archiver.sync')
271
332
  private async sync() {
272
333
  // Process any queued blocks first, before doing L1 sync
273
- await this.processQueuedBlocks();
334
+ await this.processInboundQueue();
274
335
  // Now perform L1 sync
275
336
  await this.syncFromL1();
276
337
  }
@@ -286,7 +347,7 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
286
347
  if (currentL1BlockNumber + 1n >= l1BlockNumberAtEnd) {
287
348
  this.log.info(`Initial archiver sync to L1 block ${currentL1BlockNumber} complete`, {
288
349
  l1BlockNumber: currentL1BlockNumber,
289
- syncPoint: await this.store.getSynchPoint(),
350
+ syncPoint: await getArchiverSynchPoint(this.stores),
290
351
  ...(await this.getL2Tips()),
291
352
  });
292
353
  this.runningPromise.setPollingIntervalMS(this.config.pollingIntervalMs);
@@ -318,7 +379,7 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
318
379
  }
319
380
 
320
381
  public backupTo(destPath: string): Promise<string> {
321
- return this.dataStore.backupTo(destPath);
382
+ return backupArchiverDataStores(this.dataStores, destPath);
322
383
  }
323
384
 
324
385
  public getL1Constants(): Promise<L1RollupConstants> {
@@ -360,9 +421,9 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
360
421
  }
361
422
 
362
423
  let slotFromCheckpoint: SlotNumber | undefined;
363
- const latestCheckpointNumber = await this.store.getSynchedCheckpointNumber();
424
+ const latestCheckpointNumber = await this.stores.blocks.getLatestCheckpointNumber();
364
425
  if (latestCheckpointNumber > 0) {
365
- const checkpointData = await this.store.getCheckpointData(latestCheckpointNumber);
426
+ const checkpointData = await this.stores.blocks.getCheckpointData(latestCheckpointNumber);
366
427
  if (checkpointData) {
367
428
  slotFromCheckpoint = checkpointData.header.slotNumber;
368
429
  }
@@ -451,14 +512,14 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
451
512
  if (targetL2BlockNumber >= currentL2Block) {
452
513
  throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
453
514
  }
454
- const targetL2Block = await this.store.getCheckpointedBlock(targetL2BlockNumber);
515
+ const targetL2Block = await this.stores.blocks.getCheckpointedBlock(targetL2BlockNumber);
455
516
  if (!targetL2Block) {
456
517
  throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
457
518
  }
458
519
  const targetCheckpointNumber = targetL2Block.checkpointNumber;
459
520
 
460
521
  // Rollback operates at checkpoint granularity: the target block must be the last block of its checkpoint.
461
- const checkpointData = await this.store.getCheckpointData(targetCheckpointNumber);
522
+ const checkpointData = await this.stores.blocks.getCheckpointData(targetCheckpointNumber);
462
523
  if (checkpointData) {
463
524
  const lastBlockInCheckpoint = BlockNumber(checkpointData.startBlock + checkpointData.blockCount - 1);
464
525
  if (targetL2BlockNumber !== lastBlockInCheckpoint) {
@@ -487,10 +548,10 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
487
548
  );
488
549
  await this.updater.removeCheckpointsAfter(targetCheckpointNumber);
489
550
  this.log.info(`Rolling back L1 to L2 messages to checkpoint ${targetCheckpointNumber}`);
490
- await this.store.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
551
+ await this.stores.messages.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
491
552
  this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
492
- await this.store.setCheckpointSynchedL1BlockNumber(targetL1BlockNumber);
493
- await this.store.setMessageSyncState(
553
+ await this.stores.blocks.setSynchedL1BlockNumber(targetL1BlockNumber);
554
+ await this.stores.messages.setMessageSyncState(
494
555
  { l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash },
495
556
  undefined,
496
557
  );
package/src/config.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  booleanConfigHelper,
8
8
  getConfigFromMappings,
9
9
  numberConfigHelper,
10
+ optionalNumberConfigHelper,
10
11
  } from '@aztec/foundation/config';
11
12
  import {
12
13
  type ChainConfig,
@@ -50,13 +51,17 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
50
51
  },
51
52
  archiverStoreMapSizeKb: {
52
53
  env: 'ARCHIVER_STORE_MAP_SIZE_KB',
53
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
54
+ ...optionalNumberConfigHelper(),
54
55
  description: 'The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKb.',
55
56
  },
56
57
  skipValidateCheckpointAttestations: {
57
58
  description: 'Skip validating checkpoint attestations (for testing purposes only)',
58
59
  ...booleanConfigHelper(false),
59
60
  },
61
+ skipPromoteProposedCheckpointDuringL1Sync: {
62
+ description: 'Skip promoting proposed checkpoints during L1 sync (for testing purposes only)',
63
+ ...booleanConfigHelper(false),
64
+ },
60
65
  maxAllowedEthClientDriftSeconds: {
61
66
  env: 'MAX_ALLOWED_ETH_CLIENT_DRIFT_SECONDS',
62
67
  description: 'Maximum allowed drift in seconds between the Ethereum client and current time.',
@@ -67,6 +72,13 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
67
72
  description: 'Whether to allow starting the archiver without debug/trace method support on Ethereum hosts',
68
73
  ...booleanConfigHelper(true),
69
74
  },
75
+ archiverSkipHistoricalLogsCheck: {
76
+ env: 'ARCHIVER_SKIP_HISTORICAL_LOGS_CHECK',
77
+ description:
78
+ 'Skip the startup check that probes the L1 RPC for historical Rollup contract logs. ' +
79
+ 'Set to true to bypass the check when the connected RPC node is known to prune old logs.',
80
+ ...booleanConfigHelper(false),
81
+ },
70
82
  ...chainConfigMappings,
71
83
  ...l1ReaderConfigMappings,
72
84
  viemPollingIntervalMS: {
@@ -96,7 +108,9 @@ export function mapArchiverConfig(config: Partial<ArchiverConfig>) {
96
108
  pollingIntervalMs: config.archiverPollingIntervalMS,
97
109
  batchSize: config.archiverBatchSize,
98
110
  skipValidateCheckpointAttestations: config.skipValidateCheckpointAttestations,
111
+ skipPromoteProposedCheckpointDuringL1Sync: config.skipPromoteProposedCheckpointDuringL1Sync,
99
112
  maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds,
100
113
  ethereumAllowNoDebugHosts: config.ethereumAllowNoDebugHosts,
114
+ skipHistoricalLogsCheck: config.archiverSkipHistoricalLogsCheck,
101
115
  };
102
116
  }
package/src/errors.ts CHANGED
@@ -1,14 +1,17 @@
1
+ import type { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
1
2
  import type { Fr } from '@aztec/foundation/schemas';
2
3
 
3
4
  export class NoBlobBodiesFoundError extends Error {
4
5
  constructor(l2BlockNum: number) {
5
6
  super(`No blob bodies found for block ${l2BlockNum}`);
7
+ this.name = 'NoBlobBodiesFoundError';
6
8
  }
7
9
  }
8
10
 
9
11
  export class BlockNumberNotSequentialError extends Error {
10
12
  constructor(newBlockNumber: number, previous: number | undefined) {
11
13
  super(`Cannot insert new block ${newBlockNumber} given previous block number is ${previous ?? 'undefined'}`);
14
+ this.name = 'BlockNumberNotSequentialError';
12
15
  }
13
16
  }
14
17
 
@@ -22,18 +25,29 @@ export class InitialCheckpointNumberNotSequentialError extends Error {
22
25
  previousCheckpointNumber ?? 'undefined'
23
26
  }`,
24
27
  );
28
+ this.name = 'InitialCheckpointNumberNotSequentialError';
25
29
  }
26
30
  }
27
31
 
28
- export class CheckpointNumberNotSequentialError extends Error {
32
+ export class BlockCheckpointNumberNotSequentialError extends Error {
29
33
  constructor(
30
- newCheckpointNumber: number,
31
- previous: number | undefined,
32
- source: 'confirmed' | 'proposed' = 'confirmed',
34
+ blockNumber: BlockNumber,
35
+ blockCheckpointNumber: CheckpointNumber,
36
+ previous: CheckpointNumber | undefined,
33
37
  ) {
34
38
  super(
35
- `Cannot insert new checkpoint ${newCheckpointNumber} given previous ${source} checkpoint number is ${previous ?? 'undefined'}`,
39
+ `Cannot insert new block ${blockNumber} for checkpoint ${blockCheckpointNumber} given previous checkpoint number is ${previous ?? 'undefined'}`,
40
+ );
41
+ this.name = 'BlockCheckpointNumberNotSequentialError';
42
+ }
43
+ }
44
+
45
+ export class CheckpointNumberNotSequentialError extends Error {
46
+ constructor(newCheckpointNumber: CheckpointNumber, previous: CheckpointNumber | undefined) {
47
+ super(
48
+ `Cannot insert new checkpoint ${newCheckpointNumber} given previous checkpoint number is ${previous ?? 'undefined'}`,
36
49
  );
50
+ this.name = 'CheckpointNumberNotSequentialError';
37
51
  }
38
52
  }
39
53
 
@@ -42,6 +56,7 @@ export class BlockIndexNotSequentialError extends Error {
42
56
  super(
43
57
  `Cannot insert new block at checkpoint index ${newBlockIndex} given previous block index is ${previousBlockIndex ?? 'undefined'}`,
44
58
  );
59
+ this.name = 'BlockIndexNotSequentialError';
45
60
  }
46
61
  }
47
62
 
@@ -55,18 +70,21 @@ export class BlockArchiveNotConsistentError extends Error {
55
70
  super(
56
71
  `Cannot insert new block number ${newBlockNumber} with archive ${newBlockArchive.toString()} previous block number is ${previousBlockNumber ?? 'undefined'}, previous archive is ${previousBlockArchive?.toString() ?? 'undefined'}`,
57
72
  );
73
+ this.name = 'BlockArchiveNotConsistentError';
58
74
  }
59
75
  }
60
76
 
61
77
  export class CheckpointNotFoundError extends Error {
62
78
  constructor(checkpointNumber: number) {
63
79
  super(`Failed to find expected checkpoint number ${checkpointNumber}`);
80
+ this.name = 'CheckpointNotFoundError';
64
81
  }
65
82
  }
66
83
 
67
84
  export class BlockNotFoundError extends Error {
68
85
  constructor(blockNumber: number) {
69
86
  super(`Failed to find expected block number ${blockNumber}`);
87
+ this.name = 'BlockNotFoundError';
70
88
  }
71
89
  }
72
90
 
@@ -119,19 +137,68 @@ export class ProposedCheckpointStaleError extends Error {
119
137
  }
120
138
  }
121
139
 
122
- /** Thrown when a proposed checkpoint number is not the expected confirmed + 1. */
140
+ /** Thrown when a proposed checkpoint number is not the expected latestTip + 1. */
123
141
  export class ProposedCheckpointNotSequentialError extends Error {
124
142
  constructor(
125
143
  public readonly proposedCheckpointNumber: number,
126
- public readonly confirmedCheckpointNumber: number,
144
+ public readonly latestTipNumber: number,
127
145
  ) {
128
146
  super(
129
- `Proposed checkpoint ${proposedCheckpointNumber} is not sequential: expected ${confirmedCheckpointNumber + 1} (confirmed + 1)`,
147
+ `Proposed checkpoint ${proposedCheckpointNumber} is not sequential: expected ${latestTipNumber + 1} (latest tip + 1, where tip is highest of confirmed or pending)`,
130
148
  );
131
149
  this.name = 'ProposedCheckpointNotSequentialError';
132
150
  }
133
151
  }
134
152
 
153
+ /** Thrown when a proposed checkpoint or block L2 slot has already expired on L1. */
154
+ export class BlockOrCheckpointSlotExpiredError extends Error {
155
+ constructor(
156
+ public readonly slot: number,
157
+ public readonly nextSlotStart: bigint,
158
+ public readonly l1TimestampSynced: bigint | undefined,
159
+ ) {
160
+ super(
161
+ `Checkpoint or block for slot ${slot} is expired: L1 synced to ${l1TimestampSynced} which is past the next slot start ${nextSlotStart}. ` +
162
+ `If the checkpoint still lands via a late L1 tx, the archiver will pick it up via normal L1-sync (not the pending-queue shortcut).`,
163
+ );
164
+ this.name = 'BlockOrCheckpointSlotExpiredError';
165
+ }
166
+ }
167
+
168
+ /** Thrown when attempting to promote a proposed checkpoint but no proposed checkpoint exists in the store. */
169
+ export class NoProposedCheckpointToPromoteError extends Error {
170
+ constructor() {
171
+ super('Cannot promote proposed checkpoint: no proposed checkpoint exists');
172
+ this.name = 'NoProposedCheckpointToPromoteError';
173
+ }
174
+ }
175
+
176
+ /** Thrown when the archive root of the proposed checkpoint does not match the expected one. */
177
+ export class ProposedCheckpointArchiveRootMismatchError extends Error {
178
+ constructor(
179
+ public readonly expectedArchiveRoot: Fr,
180
+ public readonly actualArchiveRoot: Fr,
181
+ ) {
182
+ super(
183
+ `Cannot promote proposed checkpoint: archive root mismatch (expected ${expectedArchiveRoot}, got ${actualArchiveRoot})`,
184
+ );
185
+ this.name = 'ProposedCheckpointArchiveRootMismatchError';
186
+ }
187
+ }
188
+
189
+ /** Thrown when the proposed checkpoint does not directly follow the latest confirmed checkpoint. */
190
+ export class ProposedCheckpointPromotionNotSequentialError extends Error {
191
+ constructor(
192
+ public readonly proposedCheckpointNumber: number,
193
+ public readonly latestCheckpointNumber: number,
194
+ ) {
195
+ super(
196
+ `Cannot promote proposed checkpoint: not sequential (latest ${latestCheckpointNumber}, proposed ${proposedCheckpointNumber})`,
197
+ );
198
+ this.name = 'ProposedCheckpointPromotionNotSequentialError';
199
+ }
200
+ }
201
+
135
202
  /** Thrown when a proposed block conflicts with an already checkpointed block (different content). */
136
203
  export class CannotOverwriteCheckpointedBlockError extends Error {
137
204
  constructor(
package/src/factory.ts CHANGED
@@ -24,7 +24,7 @@ import { Archiver, type ArchiverDeps } from './archiver.js';
24
24
  import { type ArchiverConfig, mapArchiverConfig } from './config.js';
25
25
  import { ArchiverInstrumentation } from './modules/instrumentation.js';
26
26
  import { ArchiverL1Synchronizer } from './modules/l1_synchronizer.js';
27
- import { ARCHIVER_DB_VERSION, KVArchiverDataStore } from './store/kv_archiver_store.js';
27
+ import { ARCHIVER_DB_VERSION, type ArchiverDataStores, createArchiverDataStores } from './store/data_stores.js';
28
28
  import { L2TipsCache } from './store/l2_tips_cache.js';
29
29
 
30
30
  export const ARCHIVER_STORE_NAME = 'archiver';
@@ -32,13 +32,13 @@ export const ARCHIVER_STORE_NAME = 'archiver';
32
32
  /** Creates an archiver store. */
33
33
  export async function createArchiverStore(
34
34
  userConfig: Pick<ArchiverConfig, 'archiverStoreMapSizeKb' | 'maxLogs'> & DataStoreConfig,
35
- ) {
35
+ ): Promise<ArchiverDataStores> {
36
36
  const config = {
37
37
  ...userConfig,
38
38
  dataStoreMapSizeKb: userConfig.archiverStoreMapSizeKb ?? userConfig.dataStoreMapSizeKb,
39
39
  };
40
40
  const store = await createStore(ARCHIVER_STORE_NAME, ARCHIVER_DB_VERSION, config);
41
- return new KVArchiverDataStore(store, config.maxLogs);
41
+ return createArchiverDataStores(store, { logsMaxPageSize: config.maxLogs });
42
42
  }
43
43
 
44
44
  /**
@@ -121,19 +121,20 @@ export async function createArchiver(
121
121
  batchSize: 100,
122
122
  maxAllowedEthClientDriftSeconds: 300,
123
123
  ethereumAllowNoDebugHosts: false,
124
+ skipHistoricalLogsCheck: false,
124
125
  },
125
126
  mapArchiverConfig(config),
126
127
  );
127
128
 
128
129
  const epochCache = deps.epochCache ?? (await EpochCache.create(config.l1Contracts.rollupAddress, config, deps));
129
130
  const telemetry = deps.telemetry ?? getTelemetryClient();
130
- const instrumentation = await ArchiverInstrumentation.new(telemetry, () => archiverStore.estimateSize());
131
+ const instrumentation = await ArchiverInstrumentation.new(telemetry, () => archiverStore.db.estimateSize());
131
132
 
132
133
  // Create the event emitter that will be shared by archiver and synchronizer
133
134
  const events = new EventEmitter() as ArchiverEmitter;
134
135
 
135
136
  // Create L2 tips cache shared by archiver and synchronizer
136
- const l2TipsCache = new L2TipsCache(archiverStore.blockStore);
137
+ const l2TipsCache = new L2TipsCache(archiverStore.blocks);
137
138
 
138
139
  // Create the L1 synchronizer
139
140
  const synchronizer = new ArchiverL1Synchronizer(
@@ -174,14 +175,14 @@ export async function createArchiver(
174
175
  }
175
176
 
176
177
  /** Registers protocol contracts in the archiver store. Idempotent — skips contracts that already exist (e.g. on node restart). */
177
- export async function registerProtocolContracts(store: KVArchiverDataStore) {
178
+ export async function registerProtocolContracts(stores: ArchiverDataStores) {
178
179
  const blockNumber = 0;
179
180
  for (const name of protocolContractNames) {
180
181
  const provider = new BundledProtocolContractsProvider();
181
182
  const contract = await provider.getProtocolContractArtifact(name);
182
183
 
183
184
  // Skip if already registered (happens on node restart with a persisted store).
184
- if (await store.getContractClass(contract.contractClass.id)) {
185
+ if (await stores.contractClasses.getContractClass(contract.contractClass.id)) {
185
186
  continue;
186
187
  }
187
188
 
@@ -195,8 +196,8 @@ export async function registerProtocolContracts(store: KVArchiverDataStore) {
195
196
  .filter(fn => fn.functionType === FunctionType.PUBLIC)
196
197
  .map(fn => decodeFunctionSignature(fn.name, fn.parameters));
197
198
 
198
- await store.registerContractFunctionSignatures(publicFunctionSignatures);
199
- await store.addContractClasses([contractClassPublic], BlockNumber(blockNumber));
200
- await store.addContractInstances([contract.instance], BlockNumber(blockNumber));
199
+ await stores.functionNames.register(publicFunctionSignatures);
200
+ await stores.contractClasses.addContractClasses([contractClassPublic], BlockNumber(blockNumber));
201
+ await stores.contractInstances.addContractInstances([contract.instance], BlockNumber(blockNumber));
201
202
  }
202
203
  }
package/src/index.ts CHANGED
@@ -6,8 +6,23 @@ export * from './modules/data_store_updater.js';
6
6
  export * from './config.js';
7
7
 
8
8
  export { type L1PublishedData } from './structs/published.js';
9
- export { KVArchiverDataStore, ARCHIVER_DB_VERSION } from './store/kv_archiver_store.js';
9
+ export {
10
+ ARCHIVER_DB_VERSION,
11
+ type ArchiverDataStores,
12
+ type ArchiverL1SynchPoint,
13
+ backupArchiverDataStores,
14
+ createArchiverDataStores,
15
+ createContractDataSource,
16
+ getArchiverSynchPoint,
17
+ } from './store/data_stores.js';
18
+ export { FunctionNamesCache } from './store/function_names_cache.js';
19
+ export { ArchiverContractDataSourceAdapter } from './modules/contract_data_source_adapter.js';
20
+ export { BlockStore } from './store/block_store.js';
21
+ export { LogStore } from './store/log_store.js';
22
+ export { MessageStore } from './store/message_store.js';
23
+ export { ContractClassStore } from './store/contract_class_store.js';
10
24
  export { ContractInstanceStore } from './store/contract_instance_store.js';
11
25
  export { L2TipsCache } from './store/l2_tips_cache.js';
12
26
 
13
- export { retrieveCheckpointsFromRollup, retrieveL2ProofVerifiedEvents } from './l1/data_retrieval.js';
27
+ export { retrieveL2ProofVerifiedEvents } from './l1/data_retrieval.js';
28
+ export { CalldataRetriever } from './l1/calldata_retriever.js';