@aztec/archiver 0.86.0 → 0.87.0

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 (57) hide show
  1. package/dest/archiver/archiver.d.ts +62 -5
  2. package/dest/archiver/archiver.d.ts.map +1 -1
  3. package/dest/archiver/archiver.js +362 -91
  4. package/dest/archiver/archiver_store.d.ts +33 -17
  5. package/dest/archiver/archiver_store.d.ts.map +1 -1
  6. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  7. package/dest/archiver/archiver_store_test_suite.js +364 -62
  8. package/dest/archiver/data_retrieval.d.ts +23 -14
  9. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  10. package/dest/archiver/data_retrieval.js +86 -38
  11. package/dest/archiver/errors.d.ts +8 -0
  12. package/dest/archiver/errors.d.ts.map +1 -1
  13. package/dest/archiver/errors.js +12 -0
  14. package/dest/archiver/instrumentation.d.ts.map +1 -1
  15. package/dest/archiver/kv_archiver_store/block_store.d.ts +4 -1
  16. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  17. package/dest/archiver/kv_archiver_store/block_store.js +43 -6
  18. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +3 -1
  19. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  20. package/dest/archiver/kv_archiver_store/contract_instance_store.js +17 -3
  21. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +24 -14
  22. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  23. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +37 -11
  24. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  25. package/dest/archiver/kv_archiver_store/log_store.js +1 -1
  26. package/dest/archiver/kv_archiver_store/message_store.d.ts +21 -15
  27. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  28. package/dest/archiver/kv_archiver_store/message_store.js +150 -48
  29. package/dest/archiver/structs/inbox_message.d.ts +14 -0
  30. package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
  31. package/dest/archiver/structs/inbox_message.js +38 -0
  32. package/dest/rpc/index.d.ts +1 -1
  33. package/dest/test/mock_l2_block_source.d.ts +2 -0
  34. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  35. package/dest/test/mock_l2_block_source.js +8 -1
  36. package/dest/test/mock_structs.d.ts +9 -0
  37. package/dest/test/mock_structs.d.ts.map +1 -0
  38. package/dest/test/mock_structs.js +37 -0
  39. package/package.json +15 -15
  40. package/src/archiver/archiver.ts +431 -108
  41. package/src/archiver/archiver_store.ts +37 -18
  42. package/src/archiver/archiver_store_test_suite.ts +307 -52
  43. package/src/archiver/data_retrieval.ts +130 -53
  44. package/src/archiver/errors.ts +21 -0
  45. package/src/archiver/instrumentation.ts +4 -1
  46. package/src/archiver/kv_archiver_store/block_store.ts +46 -8
  47. package/src/archiver/kv_archiver_store/contract_instance_store.ts +20 -7
  48. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +61 -17
  49. package/src/archiver/kv_archiver_store/log_store.ts +6 -2
  50. package/src/archiver/kv_archiver_store/message_store.ts +209 -53
  51. package/src/archiver/structs/inbox_message.ts +40 -0
  52. package/src/test/mock_l2_block_source.ts +9 -1
  53. package/src/test/mock_structs.ts +49 -0
  54. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
  55. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
  56. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
  57. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
@@ -1,5 +1,14 @@
1
1
  import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
2
- import { RollupContract, type ViemPublicClient, createEthereumChain } from '@aztec/ethereum';
2
+ import {
3
+ BlockTagTooOldError,
4
+ InboxContract,
5
+ type L1BlockId,
6
+ RollupContract,
7
+ type ViemPublicClient,
8
+ createEthereumChain,
9
+ } from '@aztec/ethereum';
10
+ import { maxBigint } from '@aztec/foundation/bigint';
11
+ import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
3
12
  import type { EthAddress } from '@aztec/foundation/eth-address';
4
13
  import { Fr } from '@aztec/foundation/fields';
5
14
  import { type Logger, createLogger } from '@aztec/foundation/log';
@@ -7,7 +16,8 @@ import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/runni
7
16
  import { sleep } from '@aztec/foundation/sleep';
8
17
  import { count } from '@aztec/foundation/string';
9
18
  import { elapsed } from '@aztec/foundation/timer';
10
- import { InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
19
+ import type { CustomRange } from '@aztec/kv-store';
20
+ import { RollupAbi } from '@aztec/l1-artifacts';
11
21
  import {
12
22
  ContractClassRegisteredEvent,
13
23
  PrivateFunctionBroadcastedEvent,
@@ -47,20 +57,25 @@ import {
47
57
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
48
58
  import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
49
59
  import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
50
- import type { InboxLeaf, L1ToL2MessageSource } from '@aztec/stdlib/messaging';
60
+ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
51
61
  import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
52
62
  import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
53
63
 
54
64
  import { EventEmitter } from 'events';
55
65
  import groupBy from 'lodash.groupby';
56
- import { type GetContractReturnType, createPublicClient, fallback, getContract, http } from 'viem';
66
+ import { type GetContractReturnType, createPublicClient, fallback, http } from 'viem';
57
67
 
58
68
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
59
69
  import type { ArchiverConfig } from './config.js';
60
- import { retrieveBlocksFromRollup, retrieveL1ToL2Messages } from './data_retrieval.js';
61
- import { NoBlobBodiesFoundError } from './errors.js';
70
+ import {
71
+ retrieveBlocksFromRollup,
72
+ retrieveL1ToL2Message,
73
+ retrieveL1ToL2Messages,
74
+ retrievedBlockToPublishedL2Block,
75
+ } from './data_retrieval.js';
76
+ import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
62
77
  import { ArchiverInstrumentation } from './instrumentation.js';
63
- import type { DataRetrieval } from './structs/data_retrieval.js';
78
+ import type { InboxMessage } from './structs/inbox_message.js';
64
79
  import type { PublishedL2Block } from './structs/published.js';
65
80
 
66
81
  /**
@@ -80,7 +95,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
80
95
  private runningPromise?: RunningPromise;
81
96
 
82
97
  private rollup: RollupContract;
83
- private inbox: GetContractReturnType<typeof InboxAbi, ViemPublicClient>;
98
+ private inbox: InboxContract;
84
99
 
85
100
  private store: ArchiverStoreHelper;
86
101
 
@@ -107,7 +122,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
107
122
  private readonly config: { pollingIntervalMs: number; batchSize: number },
108
123
  private readonly blobSinkClient: BlobSinkClientInterface,
109
124
  private readonly instrumentation: ArchiverInstrumentation,
110
- private readonly l1constants: L1RollupConstants,
125
+ private readonly l1constants: L1RollupConstants & { l1StartBlockHash: Buffer32 },
111
126
  private readonly log: Logger = createLogger('archiver'),
112
127
  ) {
113
128
  super();
@@ -116,12 +131,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
116
131
  this.store = new ArchiverStoreHelper(dataStore);
117
132
 
118
133
  this.rollup = new RollupContract(publicClient, l1Addresses.rollupAddress);
119
-
120
- this.inbox = getContract({
121
- address: l1Addresses.inboxAddress.toString(),
122
- abi: InboxAbi,
123
- client: publicClient,
124
- });
134
+ this.inbox = new InboxContract(publicClient, l1Addresses.inboxAddress);
125
135
  }
126
136
 
127
137
  /**
@@ -151,6 +161,10 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
151
161
  rollup.getL1GenesisTime(),
152
162
  ] as const);
153
163
 
164
+ const l1StartBlockHash = await publicClient
165
+ .getBlock({ blockNumber: l1StartBlock, includeTransactions: false })
166
+ .then(block => Buffer32.fromString(block.hash));
167
+
154
168
  const {
155
169
  aztecEpochDuration: epochDuration,
156
170
  aztecSlotDuration: slotDuration,
@@ -158,17 +172,29 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
158
172
  aztecProofSubmissionWindow: proofSubmissionWindow,
159
173
  } = config;
160
174
 
175
+ const l1Constants = {
176
+ l1StartBlockHash,
177
+ l1StartBlock,
178
+ l1GenesisTime,
179
+ epochDuration,
180
+ slotDuration,
181
+ ethereumSlotDuration,
182
+ proofSubmissionWindow,
183
+ };
184
+
185
+ const opts = {
186
+ pollingIntervalMs: config.archiverPollingIntervalMS ?? 10_000,
187
+ batchSize: config.archiverBatchSize ?? 100,
188
+ };
189
+
161
190
  const archiver = new Archiver(
162
191
  publicClient,
163
192
  config.l1Contracts,
164
193
  archiverStore,
165
- {
166
- pollingIntervalMs: config.archiverPollingIntervalMS ?? 10_000,
167
- batchSize: config.archiverBatchSize ?? 100,
168
- },
194
+ opts,
169
195
  deps.blobSinkClient,
170
196
  await ArchiverInstrumentation.new(deps.telemetry, () => archiverStore.estimateSize()),
171
- { l1StartBlock, l1GenesisTime, epochDuration, slotDuration, ethereumSlotDuration, proofSubmissionWindow },
197
+ l1Constants,
172
198
  );
173
199
  await archiver.start(blockUntilSynced);
174
200
  return archiver;
@@ -221,6 +247,8 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
221
247
  } catch (error) {
222
248
  if (error instanceof NoBlobBodiesFoundError) {
223
249
  this.log.error(`Error syncing archiver: ${error.message}`);
250
+ } else if (error instanceof BlockTagTooOldError) {
251
+ this.log.warn(`Re-running archiver sync: ${error.message}`);
224
252
  } else {
225
253
  this.log.error('Error during archiver sync', error);
226
254
  }
@@ -245,16 +273,21 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
245
273
  *
246
274
  * This code does not handle reorgs.
247
275
  */
248
- const { l1StartBlock } = this.l1constants;
249
- const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
250
- const currentL1BlockNumber = await this.publicClient.getBlockNumber();
276
+ const { l1StartBlock, l1StartBlockHash } = this.l1constants;
277
+ const {
278
+ blocksSynchedTo = l1StartBlock,
279
+ messagesSynchedTo = { l1BlockNumber: l1StartBlock, l1BlockHash: l1StartBlockHash },
280
+ } = await this.store.getSynchPoint();
281
+
282
+ const currentL1Block = await this.publicClient.getBlock({ includeTransactions: false });
283
+ const currentL1BlockNumber = currentL1Block.number;
284
+ const currentL1BlockHash = Buffer32.fromString(currentL1Block.hash);
251
285
 
252
286
  if (initialRun) {
253
287
  this.log.info(
254
- `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${Math.min(
255
- Number(blocksSynchedTo),
256
- Number(messagesSynchedTo),
257
- )} to current L1 block ${currentL1BlockNumber}`,
288
+ `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo}` +
289
+ ` to current L1 block ${currentL1BlockNumber} with hash ${currentL1BlockHash.toString()}`,
290
+ { blocksSynchedTo, messagesSynchedTo },
258
291
  );
259
292
  }
260
293
 
@@ -278,7 +311,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
278
311
  */
279
312
 
280
313
  // ********** Events that are processed per L1 block **********
281
- await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber);
314
+ await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber, currentL1BlockHash);
282
315
 
283
316
  // Get L1 timestamp for the current block
284
317
  const currentL1Timestamp =
@@ -289,13 +322,24 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
289
322
  // ********** Events that are processed per L2 block **********
290
323
  if (currentL1BlockNumber > blocksSynchedTo) {
291
324
  // First we retrieve new L2 blocks
292
- const { provenBlockNumber } = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
293
- // And then we prune the current epoch if it'd reorg on next submission.
325
+ const rollupStatus = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
326
+ // Then we prune the current epoch if it'd reorg on next submission.
294
327
  // Note that we don't do this before retrieving L2 blocks because we may need to retrieve
295
328
  // blocks from more than 2 epochs ago, so we want to make sure we have the latest view of
296
329
  // the chain locally before we start unwinding stuff. This can be optimized by figuring out
297
330
  // up to which point we're pruning, and then requesting L2 blocks up to that point only.
298
- await this.handleEpochPrune(provenBlockNumber, currentL1BlockNumber, currentL1Timestamp);
331
+ const { rollupCanPrune } = await this.handleEpochPrune(
332
+ rollupStatus.provenBlockNumber,
333
+ currentL1BlockNumber,
334
+ currentL1Timestamp,
335
+ );
336
+ // And lastly we check if we are missing any L2 blocks behind us due to a possible L1 reorg.
337
+ // We only do this if rollup cant prune on the next submission. Otherwise we will end up
338
+ // re-syncing the blocks we have just unwound above.
339
+ if (!rollupCanPrune) {
340
+ await this.checkForNewBlocksBeforeL1SyncPoint(rollupStatus, blocksSynchedTo, currentL1BlockNumber);
341
+ }
342
+
299
343
  this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
300
344
  }
301
345
 
@@ -315,7 +359,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
315
359
  }
316
360
  }
317
361
 
318
- /** Queries the rollup contract on whether a prune can be executed on the immediatenext L1 block. */
362
+ /** Queries the rollup contract on whether a prune can be executed on the immediate next L1 block. */
319
363
  private async canPrune(currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
320
364
  const time = (currentL1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
321
365
  const result = await this.rollup.canPruneAtTime(time, { blockNumber: currentL1BlockNumber });
@@ -331,9 +375,9 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
331
375
 
332
376
  /** Checks if there'd be a reorg for the next block submission and start pruning now. */
333
377
  private async handleEpochPrune(provenBlockNumber: bigint, currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
378
+ const rollupCanPrune = await this.canPrune(currentL1BlockNumber, currentL1Timestamp);
334
379
  const localPendingBlockNumber = BigInt(await this.getBlockNumber());
335
- const canPrune =
336
- localPendingBlockNumber > provenBlockNumber && (await this.canPrune(currentL1BlockNumber, currentL1Timestamp));
380
+ const canPrune = localPendingBlockNumber > provenBlockNumber && rollupCanPrune;
337
381
 
338
382
  if (canPrune) {
339
383
  const pruneFrom = provenBlockNumber + 1n;
@@ -369,6 +413,8 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
369
413
  // Seems like the next iteration should handle this.
370
414
  // await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
371
415
  }
416
+
417
+ return { rollupCanPrune };
372
418
  }
373
419
 
374
420
  private nextRange(end: bigint, limit: bigint): [bigint, bigint] {
@@ -381,49 +427,176 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
381
427
  return [nextStart, nextEnd];
382
428
  }
383
429
 
384
- private async handleL1ToL2Messages(messagesSynchedTo: bigint, currentL1BlockNumber: bigint) {
385
- this.log.trace(`Handling L1 to L2 messages from ${messagesSynchedTo} to ${currentL1BlockNumber}.`);
386
- if (currentL1BlockNumber <= messagesSynchedTo) {
430
+ private async handleL1ToL2Messages(
431
+ messagesSyncPoint: L1BlockId,
432
+ currentL1BlockNumber: bigint,
433
+ currentL1BlockHash: Buffer32,
434
+ ) {
435
+ this.log.trace(`Handling L1 to L2 messages from ${messagesSyncPoint.l1BlockNumber} to ${currentL1BlockNumber}.`);
436
+ if (currentL1BlockNumber <= messagesSyncPoint.l1BlockNumber) {
387
437
  return;
388
438
  }
389
439
 
390
- const localTotalMessageCount = await this.store.getTotalL1ToL2MessageCount();
391
- const destinationTotalMessageCount = await this.inbox.read.totalMessagesInserted();
440
+ // Load remote and local inbox states.
441
+ const localMessagesInserted = await this.store.getTotalL1ToL2MessageCount();
442
+ const localLastMessage = await this.store.getLastL1ToL2Message();
443
+ const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
392
444
 
393
- if (localTotalMessageCount === destinationTotalMessageCount) {
394
- await this.store.setMessageSynchedL1BlockNumber(currentL1BlockNumber);
395
- this.log.trace(
396
- `Retrieved no new L1 to L2 messages between L1 blocks ${messagesSynchedTo + 1n} and ${currentL1BlockNumber}.`,
445
+ this.log.trace(`Retrieved remote inbox state at L1 block ${currentL1BlockNumber}.`, {
446
+ localMessagesInserted,
447
+ localLastMessage,
448
+ remoteMessagesState,
449
+ });
450
+
451
+ // Compare message count and rolling hash. If they match, no need to retrieve anything.
452
+ if (
453
+ remoteMessagesState.totalMessagesInserted === localMessagesInserted &&
454
+ remoteMessagesState.messagesRollingHash.equals(localLastMessage?.rollingHash ?? Buffer16.ZERO)
455
+ ) {
456
+ this.log.debug(
457
+ `No L1 to L2 messages to query between L1 blocks ${messagesSyncPoint.l1BlockNumber} and ${currentL1BlockNumber}.`,
397
458
  );
459
+ await this.store.setMessageSynchedL1Block({
460
+ l1BlockHash: currentL1BlockHash,
461
+ l1BlockNumber: currentL1BlockNumber,
462
+ });
398
463
  return;
399
464
  }
400
465
 
401
- // Retrieve messages in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
402
- let searchStartBlock: bigint = messagesSynchedTo;
403
- let searchEndBlock: bigint = messagesSynchedTo;
466
+ // Check if our syncpoint is still valid. If not, there was an L1 reorg and we need to re-retrieve messages.
467
+ // Note that we need to fetch it from logs and not from inbox state at the syncpoint l1 block number, since it
468
+ // could be older than 128 blocks and non-archive nodes cannot resolve it.
469
+ if (localLastMessage) {
470
+ const remoteLastMessage = await this.retrieveL1ToL2Message(localLastMessage.leaf);
471
+ this.log.trace(`Retrieved remote message for local last`, { remoteLastMessage, localLastMessage });
472
+ if (!remoteLastMessage || !remoteLastMessage.rollingHash.equals(localLastMessage.rollingHash)) {
473
+ this.log.warn(`Rolling back L1 to L2 messages due to hash mismatch or msg not found.`, {
474
+ remoteLastMessage,
475
+ messagesSyncPoint,
476
+ localLastMessage,
477
+ });
478
+
479
+ messagesSyncPoint = await this.rollbackL1ToL2Messages(localLastMessage, messagesSyncPoint);
480
+ this.log.debug(`Rolled back L1 to L2 messages to L1 block ${messagesSyncPoint.l1BlockNumber}.`, {
481
+ messagesSyncPoint,
482
+ });
483
+ }
484
+ }
485
+
486
+ // Retrieve and save messages in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
487
+ let searchStartBlock: bigint = 0n;
488
+ let searchEndBlock: bigint = messagesSyncPoint.l1BlockNumber;
489
+
490
+ let lastMessage: InboxMessage | undefined;
491
+ let messageCount = 0;
492
+
404
493
  do {
405
494
  [searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
406
495
  this.log.trace(`Retrieving L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
407
- const retrievedL1ToL2Messages = await retrieveL1ToL2Messages(this.inbox, searchStartBlock, searchEndBlock);
496
+ const messages = await retrieveL1ToL2Messages(this.inbox.getContract(), searchStartBlock, searchEndBlock);
408
497
  this.log.verbose(
409
- `Retrieved ${retrievedL1ToL2Messages.retrievedData.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`,
498
+ `Retrieved ${messages.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`,
410
499
  );
411
- await this.store.addL1ToL2Messages(retrievedL1ToL2Messages);
412
- for (const msg of retrievedL1ToL2Messages.retrievedData) {
413
- this.log.debug(`Downloaded L1 to L2 message`, { leaf: msg.leaf.toString(), index: msg.index });
500
+ await this.store.addL1ToL2Messages(messages);
501
+ for (const msg of messages) {
502
+ this.log.debug(`Downloaded L1 to L2 message`, { ...msg, leaf: msg.leaf.toString() });
503
+ lastMessage = msg;
504
+ messageCount++;
414
505
  }
415
506
  } while (searchEndBlock < currentL1BlockNumber);
507
+
508
+ // Log stats for messages retrieved (if any).
509
+ if (messageCount > 0) {
510
+ this.log.info(
511
+ `Retrieved ${messageCount} new L1 to L2 messages up to message with index ${lastMessage?.index} for L2 block ${lastMessage?.l2BlockNumber}`,
512
+ { lastMessage, messageCount },
513
+ );
514
+ }
515
+
516
+ // Warn if the resulting rolling hash does not match the remote state we had retrieved.
517
+ if (lastMessage && !lastMessage.rollingHash.equals(remoteMessagesState.messagesRollingHash)) {
518
+ this.log.warn(`Last message retrieved rolling hash does not match remote state.`, {
519
+ lastMessage,
520
+ remoteMessagesState,
521
+ });
522
+ }
416
523
  }
417
524
 
418
- private async handleL2blocks(
419
- blocksSynchedTo: bigint,
420
- currentL1BlockNumber: bigint,
421
- ): Promise<{ provenBlockNumber: bigint }> {
525
+ private retrieveL1ToL2Message(leaf: Fr): Promise<InboxMessage | undefined> {
526
+ return retrieveL1ToL2Message(this.inbox.getContract(), leaf, this.l1constants.l1StartBlock);
527
+ }
528
+
529
+ private async rollbackL1ToL2Messages(localLastMessage: InboxMessage, messagesSyncPoint: L1BlockId) {
530
+ // Slowly go back through our messages until we find the last common message.
531
+ // We could query the logs in batch as an optimization, but the depth of the reorg should not be deep, and this
532
+ // is a very rare case, so it's fine to query one log at a time.
533
+ let commonMsg: undefined | InboxMessage;
534
+ this.log.verbose(`Searching most recent common L1 to L2 message at or before index ${localLastMessage.index}`);
535
+ for await (const msg of this.store.iterateL1ToL2Messages({ reverse: true, end: localLastMessage.index })) {
536
+ const remoteMsg = await this.retrieveL1ToL2Message(msg.leaf);
537
+ const logCtx = { remoteMsg, localMsg: msg };
538
+ if (remoteMsg && remoteMsg.rollingHash.equals(msg.rollingHash)) {
539
+ this.log.verbose(
540
+ `Found most recent common L1 to L2 message at index ${msg.index} on L1 block ${msg.l1BlockNumber}`,
541
+ logCtx,
542
+ );
543
+ commonMsg = remoteMsg;
544
+ break;
545
+ } else if (remoteMsg) {
546
+ this.log.debug(`Local L1 to L2 message with index ${msg.index} has different rolling hash`, logCtx);
547
+ } else {
548
+ this.log.debug(`Local L1 to L2 message with index ${msg.index} not found on L1`, logCtx);
549
+ }
550
+ }
551
+
552
+ // Delete everything after the common message we found.
553
+ const lastGoodIndex = commonMsg?.index;
554
+ this.log.warn(`Deleting all local L1 to L2 messages after index ${lastGoodIndex ?? 'undefined'}`);
555
+ await this.store.removeL1ToL2Messages(lastGoodIndex !== undefined ? lastGoodIndex + 1n : 0n);
556
+
557
+ // Update the syncpoint so the loop below reprocesses the changed messages. We go to the block before
558
+ // the last common one, so we force reprocessing it, in case new messages were added on that same L1 block
559
+ // after the last common message.
560
+ const syncPointL1BlockNumber = commonMsg ? commonMsg.l1BlockNumber - 1n : this.l1constants.l1StartBlock;
561
+ const syncPointL1BlockHash = await this.getL1BlockHash(syncPointL1BlockNumber);
562
+ messagesSyncPoint = { l1BlockNumber: syncPointL1BlockNumber, l1BlockHash: syncPointL1BlockHash };
563
+ await this.store.setMessageSynchedL1Block(messagesSyncPoint);
564
+ return messagesSyncPoint;
565
+ }
566
+
567
+ private async getL1BlockHash(l1BlockNumber: bigint): Promise<Buffer32> {
568
+ const block = await this.publicClient.getBlock({ blockNumber: l1BlockNumber, includeTransactions: false });
569
+ if (!block) {
570
+ throw new Error(`Missing L1 block ${l1BlockNumber}`);
571
+ }
572
+ return Buffer32.fromString(block.hash);
573
+ }
574
+
575
+ private async handleL2blocks(blocksSynchedTo: bigint, currentL1BlockNumber: bigint) {
422
576
  const localPendingBlockNumber = BigInt(await this.getBlockNumber());
423
577
  const [provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive, archiveForLocalPendingBlockNumber] =
424
578
  await this.rollup.status(localPendingBlockNumber, { blockNumber: currentL1BlockNumber });
579
+ const rollupStatus = { provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive };
580
+ this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
581
+ localPendingBlockNumber,
582
+ blocksSynchedTo,
583
+ currentL1BlockNumber,
584
+ archiveForLocalPendingBlockNumber,
585
+ ...rollupStatus,
586
+ });
425
587
 
426
588
  const updateProvenBlock = async () => {
589
+ // Annoying edge case: if proven block is moved back to 0 due to a reorg at the beginning of the chain,
590
+ // we need to set it to zero. This is an edge case because we dont have a block zero (initial block is one),
591
+ // so localBlockForDestinationProvenBlockNumber would not be found below.
592
+ if (provenBlockNumber === 0n) {
593
+ const localProvenBlockNumber = await this.store.getProvenL2BlockNumber();
594
+ if (localProvenBlockNumber !== Number(provenBlockNumber)) {
595
+ await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
596
+ this.log.info(`Rolled back proven chain to block ${provenBlockNumber}`, { provenBlockNumber });
597
+ }
598
+ }
599
+
427
600
  const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
428
601
 
429
602
  // Sanity check. I've hit what seems to be a state where the proven block is set to a value greater than the latest
@@ -435,6 +608,12 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
435
608
  );
436
609
  }
437
610
 
611
+ this.log.trace(
612
+ `Local block for remote proven block ${provenBlockNumber} is ${
613
+ localBlockForDestinationProvenBlockNumber?.archive.root.toString() ?? 'undefined'
614
+ }`,
615
+ );
616
+
438
617
  if (
439
618
  localBlockForDestinationProvenBlockNumber &&
440
619
  provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()
@@ -445,6 +624,8 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
445
624
  this.log.info(`Updated proven chain to block ${provenBlockNumber}`, {
446
625
  provenBlockNumber,
447
626
  });
627
+ } else {
628
+ this.log.trace(`Proven block ${provenBlockNumber} already stored.`);
448
629
  }
449
630
  }
450
631
  this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
@@ -458,7 +639,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
458
639
  this.log.debug(
459
640
  `No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no blocks on chain`,
460
641
  );
461
- return { provenBlockNumber };
642
+ return rollupStatus;
462
643
  }
463
644
 
464
645
  await updateProvenBlock();
@@ -479,10 +660,10 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
479
660
  // this block again (or any blocks before).
480
661
  // However, in the re-org scenario, our L1 node is temporarily lying to us and we end up potentially missing blocks
481
662
  // We must only set this block number based on actually retrieved logs.
482
- // TODO(https://github.com/AztecProtocol/aztec-packages/issues/8621): Tackle this properly when we handle L1 Re-orgs.
483
- //await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
663
+ // TODO(#8621): Tackle this properly when we handle L1 Re-orgs.
664
+ // await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
484
665
  this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
485
- return { provenBlockNumber };
666
+ return rollupStatus;
486
667
  }
487
668
 
488
669
  const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingBlock.archive.root.toString();
@@ -523,6 +704,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
523
704
  // computed using the L2 block time vs the L1 block time.
524
705
  let searchStartBlock: bigint = blocksSynchedTo;
525
706
  let searchEndBlock: bigint = blocksSynchedTo;
707
+ let lastRetrievedBlock: PublishedL2Block | undefined;
526
708
 
527
709
  do {
528
710
  [searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
@@ -551,7 +733,9 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
551
733
  `Retrieved ${retrievedBlocks.length} new L2 blocks between L1 blocks ${searchStartBlock} and ${searchEndBlock} with last processed L1 block ${lastProcessedL1BlockNumber}.`,
552
734
  );
553
735
 
554
- for (const block of retrievedBlocks) {
736
+ const publishedBlocks = retrievedBlocks.map(b => retrievedBlockToPublishedL2Block(b));
737
+
738
+ for (const block of publishedBlocks) {
555
739
  this.log.debug(`Ingesting new L2 block ${block.block.number} with ${block.block.body.txEffects.length} txs`, {
556
740
  blockHash: block.block.hash(),
557
741
  l1BlockNumber: block.l1.blockNumber,
@@ -560,26 +744,95 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
560
744
  });
561
745
  }
562
746
 
563
- const [processDuration] = await elapsed(() => this.store.addBlocks(retrievedBlocks));
564
- this.instrumentation.processNewBlocks(
565
- processDuration / retrievedBlocks.length,
566
- retrievedBlocks.map(b => b.block),
567
- );
747
+ try {
748
+ const [processDuration] = await elapsed(() => this.store.addBlocks(publishedBlocks));
749
+ this.instrumentation.processNewBlocks(
750
+ processDuration / publishedBlocks.length,
751
+ publishedBlocks.map(b => b.block),
752
+ );
753
+ } catch (err) {
754
+ if (err instanceof InitialBlockNumberNotSequentialError) {
755
+ const { previousBlockNumber, newBlockNumber } = err;
756
+ const previousBlock = previousBlockNumber
757
+ ? await this.store.getPublishedBlock(previousBlockNumber)
758
+ : undefined;
759
+ const updatedL1SyncPoint = previousBlock?.l1.blockNumber ?? this.l1constants.l1StartBlock;
760
+ await this.store.setBlockSynchedL1BlockNumber(updatedL1SyncPoint);
761
+ this.log.warn(
762
+ `Attempting to insert block ${newBlockNumber} with previous block ${previousBlockNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
763
+ {
764
+ previousBlockNumber,
765
+ previousBlockHash: await previousBlock?.block.hash(),
766
+ newBlockNumber,
767
+ updatedL1SyncPoint,
768
+ },
769
+ );
770
+ }
771
+ throw err;
772
+ }
568
773
 
569
- for (const block of retrievedBlocks) {
774
+ for (const block of publishedBlocks) {
570
775
  this.log.info(`Downloaded L2 block ${block.block.number}`, {
571
- blockHash: block.block.hash(),
776
+ blockHash: await block.block.hash(),
572
777
  blockNumber: block.block.number,
573
778
  txCount: block.block.body.txEffects.length,
574
779
  globalVariables: block.block.header.globalVariables.toInspect(),
780
+ archiveRoot: block.block.archive.root.toString(),
781
+ archiveNextLeafIndex: block.block.archive.nextAvailableLeafIndex,
575
782
  });
576
783
  }
784
+ lastRetrievedBlock = publishedBlocks.at(-1) ?? lastRetrievedBlock;
577
785
  } while (searchEndBlock < currentL1BlockNumber);
578
786
 
579
787
  // Important that we update AFTER inserting the blocks.
580
788
  await updateProvenBlock();
581
789
 
582
- return { provenBlockNumber };
790
+ return { ...rollupStatus, lastRetrievedBlock };
791
+ }
792
+
793
+ private async checkForNewBlocksBeforeL1SyncPoint(
794
+ status: {
795
+ lastRetrievedBlock?: PublishedL2Block;
796
+ pendingBlockNumber: bigint;
797
+ },
798
+ blocksSynchedTo: bigint,
799
+ currentL1BlockNumber: bigint,
800
+ ) {
801
+ const { lastRetrievedBlock, pendingBlockNumber } = status;
802
+ // Compare the last L2 block we have (either retrieved in this round or loaded from store) with what the
803
+ // rollup contract told us was the latest one (pinned at the currentL1BlockNumber).
804
+ const latestLocalL2BlockNumber = lastRetrievedBlock?.block.number ?? (await this.store.getSynchedL2BlockNumber());
805
+ if (latestLocalL2BlockNumber < pendingBlockNumber) {
806
+ // Here we have consumed all logs until the `currentL1Block` we pinned at the beginning of the archiver loop,
807
+ // but still havent reached the pending block according to the call to the rollup contract.
808
+ // We suspect an L1 reorg that added blocks *behind* us. If that is the case, it must have happened between the
809
+ // last L2 block we saw and the current one, so we reset the last synched L1 block number. In the edge case we
810
+ // don't have one, we go back 2 L1 epochs, which is the deepest possible reorg (assuming Casper is working).
811
+ const latestLocalL2Block =
812
+ lastRetrievedBlock ??
813
+ (latestLocalL2BlockNumber > 0
814
+ ? await this.store.getPublishedBlocks(latestLocalL2BlockNumber, 1).then(([b]) => b)
815
+ : undefined);
816
+ const targetL1BlockNumber = latestLocalL2Block?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
817
+ const latestLocalL2BlockArchive = latestLocalL2Block?.block.archive.root.toString();
818
+ this.log.warn(
819
+ `Failed to reach L2 block ${pendingBlockNumber} at ${currentL1BlockNumber} (latest is ${latestLocalL2BlockNumber}). ` +
820
+ `Rolling back last synched L1 block number to ${targetL1BlockNumber}.`,
821
+ {
822
+ latestLocalL2BlockNumber,
823
+ latestLocalL2BlockArchive,
824
+ blocksSynchedTo,
825
+ currentL1BlockNumber,
826
+ ...status,
827
+ },
828
+ );
829
+ await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
830
+ } else {
831
+ this.log.trace(`No new blocks behind L1 sync point to retrieve.`, {
832
+ latestLocalL2BlockNumber,
833
+ pendingBlockNumber,
834
+ });
835
+ }
583
836
  }
584
837
 
585
838
  /** Resumes the archiver after a stop. */
@@ -731,7 +984,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
731
984
  const limitWithProven = proven
732
985
  ? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
733
986
  : limit;
734
- return limitWithProven === 0 ? [] : await this.store.getBlocks(from, limitWithProven);
987
+ return limitWithProven === 0 ? [] : await this.store.getPublishedBlocks(from, limitWithProven);
735
988
  }
736
989
 
737
990
  /**
@@ -744,11 +997,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
744
997
  if (number < 0) {
745
998
  number = await this.store.getSynchedL2BlockNumber();
746
999
  }
747
- if (number == 0) {
1000
+ if (number === 0) {
748
1001
  return undefined;
749
1002
  }
750
- const blocks = await this.store.getBlocks(number, 1);
751
- return blocks.length === 0 ? undefined : blocks[0].block;
1003
+ const publishedBlock = await this.store.getPublishedBlock(number);
1004
+ return publishedBlock?.block;
752
1005
  }
753
1006
 
754
1007
  public async getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
@@ -876,9 +1129,14 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
876
1129
  this.getProvenBlockNumber(),
877
1130
  ] as const);
878
1131
 
879
- const [latestBlockHeader, provenBlockHeader] = await Promise.all([
1132
+ // TODO(#13569): Compute proper finalized block number based on L1 finalized block.
1133
+ // We just force it 2 epochs worth of proven data for now.
1134
+ const finalizedBlockNumber = Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0);
1135
+
1136
+ const [latestBlockHeader, provenBlockHeader, finalizedBlockHeader] = await Promise.all([
880
1137
  latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
881
1138
  provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined,
1139
+ finalizedBlockNumber > 0 ? this.getBlockHeader(finalizedBlockNumber) : undefined,
882
1140
  ] as const);
883
1141
 
884
1142
  if (latestBlockNumber > 0 && !latestBlockHeader) {
@@ -891,9 +1149,16 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
891
1149
  );
892
1150
  }
893
1151
 
1152
+ if (finalizedBlockNumber > 0 && !finalizedBlockHeader) {
1153
+ throw new Error(
1154
+ `Failed to retrieve finalized block header for block ${finalizedBlockNumber} (latest block is ${latestBlockNumber})`,
1155
+ );
1156
+ }
1157
+
894
1158
  const latestBlockHeaderHash = await latestBlockHeader?.hash();
895
1159
  const provenBlockHeaderHash = await provenBlockHeader?.hash();
896
- const finalizedBlockHeaderHash = await provenBlockHeader?.hash();
1160
+ const finalizedBlockHeaderHash = await finalizedBlockHeader?.hash();
1161
+
897
1162
  return {
898
1163
  latest: {
899
1164
  number: latestBlockNumber,
@@ -904,11 +1169,45 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
904
1169
  hash: provenBlockHeaderHash?.toString(),
905
1170
  } as L2BlockId,
906
1171
  finalized: {
907
- number: provenBlockNumber,
1172
+ number: finalizedBlockNumber,
908
1173
  hash: finalizedBlockHeaderHash?.toString(),
909
1174
  } as L2BlockId,
910
1175
  };
911
1176
  }
1177
+
1178
+ public async rollbackTo(targetL2BlockNumber: number): Promise<void> {
1179
+ const currentBlocks = await this.getL2Tips();
1180
+ const currentL2Block = currentBlocks.latest.number;
1181
+ const currentProvenBlock = currentBlocks.proven.number;
1182
+ // const currentFinalizedBlock = currentBlocks.finalized.number;
1183
+
1184
+ if (targetL2BlockNumber >= currentL2Block) {
1185
+ throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
1186
+ }
1187
+ const blocksToUnwind = currentL2Block - targetL2BlockNumber;
1188
+ const targetL2Block = await this.store.getPublishedBlock(targetL2BlockNumber);
1189
+ if (!targetL2Block) {
1190
+ throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
1191
+ }
1192
+ const targetL1BlockNumber = targetL2Block.l1.blockNumber;
1193
+ const targetL1BlockHash = await this.getL1BlockHash(targetL1BlockNumber);
1194
+ this.log.info(`Unwinding ${blocksToUnwind} blocks from L2 block ${currentL2Block}`);
1195
+ await this.store.unwindBlocks(currentL2Block, blocksToUnwind);
1196
+ this.log.info(`Unwinding L1 to L2 messages to ${targetL2BlockNumber}`);
1197
+ await this.store.rollbackL1ToL2MessagesToL2Block(targetL2BlockNumber);
1198
+ this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
1199
+ await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
1200
+ await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
1201
+ if (targetL2BlockNumber < currentProvenBlock) {
1202
+ this.log.info(`Clearing proven L2 block number`);
1203
+ await this.store.setProvenL2BlockNumber(0);
1204
+ }
1205
+ // TODO(palla/reorg): Set the finalized block when we add support for it.
1206
+ // if (targetL2BlockNumber < currentFinalizedBlock) {
1207
+ // this.log.info(`Clearing finalized L2 block number`);
1208
+ // await this.store.setFinalizedL2BlockNumber(0);
1209
+ // }
1210
+ }
912
1211
  }
913
1212
 
914
1213
  enum Operation {
@@ -922,7 +1221,7 @@ enum Operation {
922
1221
  * I would have preferred to not have this type. But it is useful for handling the logic that any
923
1222
  * store would need to include otherwise while exposing fewer functions and logic directly to the archiver.
924
1223
  */
925
- class ArchiverStoreHelper
1224
+ export class ArchiverStoreHelper
926
1225
  implements
927
1226
  Omit<
928
1227
  ArchiverDataStore,
@@ -937,11 +1236,12 @@ class ArchiverStoreHelper
937
1236
  | 'addFunctions'
938
1237
  | 'backupTo'
939
1238
  | 'close'
1239
+ | 'transactionAsync'
940
1240
  >
941
1241
  {
942
1242
  #log = createLogger('archiver:block-helper');
943
1243
 
944
- constructor(private readonly store: ArchiverDataStore) {}
1244
+ constructor(protected readonly store: ArchiverDataStore) {}
945
1245
 
946
1246
  /**
947
1247
  * Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
@@ -1077,38 +1377,46 @@ class ArchiverStoreHelper
1077
1377
  return true;
1078
1378
  }
1079
1379
 
1080
- async addBlocks(blocks: PublishedL2Block[]): Promise<boolean> {
1081
- const opResults = await Promise.all([
1082
- this.store.addLogs(blocks.map(block => block.block)),
1083
- // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1084
- ...blocks.map(async block => {
1085
- const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1086
- // ContractInstanceDeployed event logs are broadcast in privateLogs.
1087
- const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1088
- const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1089
- return (
1090
- await Promise.all([
1091
- this.#updateRegisteredContractClasses(contractClassLogs, block.block.number, Operation.Store),
1092
- this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
1093
- this.#updateUpdatedContractInstances(publicLogs, block.block.number, Operation.Store),
1094
- this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.block.number),
1095
- ])
1096
- ).every(Boolean);
1097
- }),
1098
- this.store.addBlocks(blocks),
1099
- ]);
1100
-
1101
- return opResults.every(Boolean);
1380
+ public addBlocks(blocks: PublishedL2Block[]): Promise<boolean> {
1381
+ // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1382
+ // or if the previous block is not in the store.
1383
+ return this.store.transactionAsync(async () => {
1384
+ await this.store.addBlocks(blocks);
1385
+
1386
+ const opResults = await Promise.all([
1387
+ this.store.addLogs(blocks.map(block => block.block)),
1388
+ // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1389
+ ...blocks.map(async block => {
1390
+ const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1391
+ // ContractInstanceDeployed event logs are broadcast in privateLogs.
1392
+ const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1393
+ const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1394
+ return (
1395
+ await Promise.all([
1396
+ this.#updateRegisteredContractClasses(contractClassLogs, block.block.number, Operation.Store),
1397
+ this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
1398
+ this.#updateUpdatedContractInstances(publicLogs, block.block.number, Operation.Store),
1399
+ this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.block.number),
1400
+ ])
1401
+ ).every(Boolean);
1402
+ }),
1403
+ ]);
1404
+
1405
+ return opResults.every(Boolean);
1406
+ });
1102
1407
  }
1103
1408
 
1104
- async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
1409
+ public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
1105
1410
  const last = await this.getSynchedL2BlockNumber();
1106
1411
  if (from != last) {
1107
- throw new Error(`Can only remove from the tip`);
1412
+ throw new Error(`Cannot unwind blocks from block ${from} when the last block is ${last}`);
1413
+ }
1414
+ if (blocksToUnwind <= 0) {
1415
+ throw new Error(`Cannot unwind ${blocksToUnwind} blocks`);
1108
1416
  }
1109
1417
 
1110
1418
  // from - blocksToUnwind = the new head, so + 1 for what we need to remove
1111
- const blocks = await this.getBlocks(from - blocksToUnwind + 1, blocksToUnwind);
1419
+ const blocks = await this.getPublishedBlocks(from - blocksToUnwind + 1, blocksToUnwind);
1112
1420
 
1113
1421
  const opResults = await Promise.all([
1114
1422
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
@@ -1134,8 +1442,11 @@ class ArchiverStoreHelper
1134
1442
  return opResults.every(Boolean);
1135
1443
  }
1136
1444
 
1137
- getBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
1138
- return this.store.getBlocks(from, limit);
1445
+ getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
1446
+ return this.store.getPublishedBlocks(from, limit);
1447
+ }
1448
+ getPublishedBlock(number: number): Promise<PublishedL2Block | undefined> {
1449
+ return this.store.getPublishedBlock(number);
1139
1450
  }
1140
1451
  getBlockHeaders(from: number, limit: number): Promise<BlockHeader[]> {
1141
1452
  return this.store.getBlockHeaders(from, limit);
@@ -1146,7 +1457,7 @@ class ArchiverStoreHelper
1146
1457
  getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
1147
1458
  return this.store.getSettledTxReceipt(txHash);
1148
1459
  }
1149
- addL1ToL2Messages(messages: DataRetrieval<InboxLeaf>): Promise<boolean> {
1460
+ addL1ToL2Messages(messages: InboxMessage[]): Promise<void> {
1150
1461
  return this.store.addL1ToL2Messages(messages);
1151
1462
  }
1152
1463
  getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
@@ -1179,8 +1490,8 @@ class ArchiverStoreHelper
1179
1490
  setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
1180
1491
  return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
1181
1492
  }
1182
- setMessageSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
1183
- return this.store.setMessageSynchedL1BlockNumber(l1BlockNumber);
1493
+ setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void> {
1494
+ return this.store.setMessageSynchedL1Block(l1Block);
1184
1495
  }
1185
1496
  getSynchPoint(): Promise<ArchiverL1SynchPoint> {
1186
1497
  return this.store.getSynchPoint();
@@ -1206,7 +1517,19 @@ class ArchiverStoreHelper
1206
1517
  getTotalL1ToL2MessageCount(): Promise<bigint> {
1207
1518
  return this.store.getTotalL1ToL2MessageCount();
1208
1519
  }
1209
- estimateSize(): Promise<{ mappingSize: number; actualSize: number; numItems: number }> {
1520
+ estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }> {
1210
1521
  return this.store.estimateSize();
1211
1522
  }
1523
+ rollbackL1ToL2MessagesToL2Block(targetBlockNumber: number | bigint): Promise<void> {
1524
+ return this.store.rollbackL1ToL2MessagesToL2Block(targetBlockNumber);
1525
+ }
1526
+ iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
1527
+ return this.store.iterateL1ToL2Messages(range);
1528
+ }
1529
+ removeL1ToL2Messages(startIndex: bigint): Promise<void> {
1530
+ return this.store.removeL1ToL2Messages(startIndex);
1531
+ }
1532
+ getLastL1ToL2Message(): Promise<InboxMessage | undefined> {
1533
+ return this.store.getLastL1ToL2Message();
1534
+ }
1212
1535
  }