@aztec/archiver 0.0.0-test.1 → 0.0.1-commit.24de95ac

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 (102) hide show
  1. package/README.md +27 -6
  2. package/dest/archiver/archiver.d.ts +126 -46
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +683 -261
  5. package/dest/archiver/archiver_store.d.ts +84 -49
  6. package/dest/archiver/archiver_store.d.ts.map +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  8. package/dest/archiver/archiver_store_test_suite.js +707 -213
  9. package/dest/archiver/config.d.ts +4 -20
  10. package/dest/archiver/config.d.ts.map +1 -1
  11. package/dest/archiver/config.js +16 -12
  12. package/dest/archiver/data_retrieval.d.ts +25 -20
  13. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  14. package/dest/archiver/data_retrieval.js +147 -68
  15. package/dest/archiver/errors.d.ts +8 -0
  16. package/dest/archiver/errors.d.ts.map +1 -1
  17. package/dest/archiver/errors.js +12 -0
  18. package/dest/archiver/index.d.ts +2 -3
  19. package/dest/archiver/index.d.ts.map +1 -1
  20. package/dest/archiver/index.js +1 -2
  21. package/dest/archiver/instrumentation.d.ts +9 -3
  22. package/dest/archiver/instrumentation.d.ts.map +1 -1
  23. package/dest/archiver/instrumentation.js +58 -17
  24. package/dest/archiver/kv_archiver_store/block_store.d.ts +47 -10
  25. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  26. package/dest/archiver/kv_archiver_store/block_store.js +216 -63
  27. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
  28. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  29. package/dest/archiver/kv_archiver_store/contract_class_store.js +12 -18
  30. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +10 -7
  31. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  32. package/dest/archiver/kv_archiver_store/contract_instance_store.js +30 -16
  33. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +49 -34
  34. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  35. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +88 -46
  36. package/dest/archiver/kv_archiver_store/log_store.d.ts +1 -1
  37. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  38. package/dest/archiver/kv_archiver_store/log_store.js +18 -46
  39. package/dest/archiver/kv_archiver_store/message_store.d.ts +22 -16
  40. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  41. package/dest/archiver/kv_archiver_store/message_store.js +150 -48
  42. package/dest/archiver/structs/inbox_message.d.ts +15 -0
  43. package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
  44. package/dest/archiver/structs/inbox_message.js +38 -0
  45. package/dest/archiver/structs/published.d.ts +1 -10
  46. package/dest/archiver/structs/published.d.ts.map +1 -1
  47. package/dest/archiver/structs/published.js +1 -1
  48. package/dest/archiver/validation.d.ts +11 -0
  49. package/dest/archiver/validation.d.ts.map +1 -0
  50. package/dest/archiver/validation.js +90 -0
  51. package/dest/factory.d.ts +7 -12
  52. package/dest/factory.d.ts.map +1 -1
  53. package/dest/factory.js +18 -49
  54. package/dest/rpc/index.d.ts +1 -2
  55. package/dest/rpc/index.d.ts.map +1 -1
  56. package/dest/rpc/index.js +1 -4
  57. package/dest/test/mock_archiver.d.ts +1 -1
  58. package/dest/test/mock_l1_to_l2_message_source.d.ts +4 -2
  59. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  60. package/dest/test/mock_l1_to_l2_message_source.js +14 -1
  61. package/dest/test/mock_l2_block_source.d.ts +32 -5
  62. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  63. package/dest/test/mock_l2_block_source.js +118 -7
  64. package/dest/test/mock_structs.d.ts +9 -0
  65. package/dest/test/mock_structs.d.ts.map +1 -0
  66. package/dest/test/mock_structs.js +37 -0
  67. package/package.json +25 -27
  68. package/src/archiver/archiver.ts +858 -317
  69. package/src/archiver/archiver_store.ts +97 -55
  70. package/src/archiver/archiver_store_test_suite.ts +663 -210
  71. package/src/archiver/config.ts +23 -41
  72. package/src/archiver/data_retrieval.ts +215 -92
  73. package/src/archiver/errors.ts +21 -0
  74. package/src/archiver/index.ts +2 -3
  75. package/src/archiver/instrumentation.ts +75 -20
  76. package/src/archiver/kv_archiver_store/block_store.ts +270 -72
  77. package/src/archiver/kv_archiver_store/contract_class_store.ts +13 -23
  78. package/src/archiver/kv_archiver_store/contract_instance_store.ts +35 -27
  79. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +127 -63
  80. package/src/archiver/kv_archiver_store/log_store.ts +24 -62
  81. package/src/archiver/kv_archiver_store/message_store.ts +209 -53
  82. package/src/archiver/structs/inbox_message.ts +41 -0
  83. package/src/archiver/structs/published.ts +1 -11
  84. package/src/archiver/validation.ts +99 -0
  85. package/src/factory.ts +24 -66
  86. package/src/rpc/index.ts +1 -5
  87. package/src/test/mock_archiver.ts +1 -1
  88. package/src/test/mock_l1_to_l2_message_source.ts +14 -3
  89. package/src/test/mock_l2_block_source.ts +152 -8
  90. package/src/test/mock_structs.ts +49 -0
  91. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +0 -12
  92. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +0 -1
  93. package/dest/archiver/kv_archiver_store/nullifier_store.js +0 -73
  94. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
  95. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
  96. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
  97. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +0 -175
  98. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +0 -1
  99. package/dest/archiver/memory_archiver_store/memory_archiver_store.js +0 -636
  100. package/src/archiver/kv_archiver_store/nullifier_store.ts +0 -97
  101. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
  102. package/src/archiver/memory_archiver_store/memory_archiver_store.ts +0 -801
@@ -1,42 +1,54 @@
1
1
  import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
2
- import { type ViemPublicClient, createEthereumChain } from '@aztec/ethereum';
2
+ import { EpochCache } from '@aztec/epoch-cache';
3
+ import {
4
+ BlockTagTooOldError,
5
+ InboxContract,
6
+ type L1BlockId,
7
+ RollupContract,
8
+ type ViemPublicClient,
9
+ createEthereumChain,
10
+ } from '@aztec/ethereum';
11
+ import { maxBigint } from '@aztec/foundation/bigint';
12
+ import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
13
+ import { merge, pick } from '@aztec/foundation/collection';
3
14
  import type { EthAddress } from '@aztec/foundation/eth-address';
4
15
  import { Fr } from '@aztec/foundation/fields';
5
16
  import { type Logger, createLogger } from '@aztec/foundation/log';
17
+ import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
6
18
  import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
19
+ import { sleep } from '@aztec/foundation/sleep';
7
20
  import { count } from '@aztec/foundation/string';
8
- import { elapsed } from '@aztec/foundation/timer';
9
- import { InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
21
+ import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
22
+ import type { CustomRange } from '@aztec/kv-store';
23
+ import { RollupAbi } from '@aztec/l1-artifacts';
10
24
  import {
11
- ContractClassRegisteredEvent,
25
+ ContractClassPublishedEvent,
12
26
  PrivateFunctionBroadcastedEvent,
13
- UnconstrainedFunctionBroadcastedEvent,
14
- } from '@aztec/protocol-contracts/class-registerer';
27
+ UtilityFunctionBroadcastedEvent,
28
+ } from '@aztec/protocol-contracts/class-registry';
15
29
  import {
16
- ContractInstanceDeployedEvent,
30
+ ContractInstancePublishedEvent,
17
31
  ContractInstanceUpdatedEvent,
18
- } from '@aztec/protocol-contracts/instance-deployer';
32
+ } from '@aztec/protocol-contracts/instance-registry';
19
33
  import type { FunctionSelector } from '@aztec/stdlib/abi';
20
34
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
21
35
  import {
22
- type InBlock,
36
+ type ArchiverEmitter,
23
37
  type L2Block,
24
38
  type L2BlockId,
25
39
  type L2BlockSource,
26
40
  L2BlockSourceEvents,
27
41
  type L2Tips,
28
- type NullifierWithBlockSource,
29
42
  } from '@aztec/stdlib/block';
30
43
  import {
31
44
  type ContractClassPublic,
32
45
  type ContractDataSource,
33
46
  type ContractInstanceWithAddress,
34
47
  type ExecutablePrivateFunctionWithMembershipProof,
35
- type PublicFunction,
36
- type UnconstrainedFunctionWithMembershipProof,
48
+ type UtilityFunctionWithMembershipProof,
37
49
  computePublicBytecodeCommitment,
38
50
  isValidPrivateFunctionMembershipProof,
39
- isValidUnconstrainedFunctionMembershipProof,
51
+ isValidUtilityFunctionMembershipProof,
40
52
  } from '@aztec/stdlib/contract';
41
53
  import {
42
54
  type L1RollupConstants,
@@ -49,49 +61,74 @@ import {
49
61
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
50
62
  import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
51
63
  import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
52
- import type { InboxLeaf, L1ToL2MessageSource } from '@aztec/stdlib/messaging';
53
- import { type BlockHeader, TxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
54
- import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
64
+ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
65
+ import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
66
+ import type { UInt64 } from '@aztec/stdlib/types';
67
+ import {
68
+ Attributes,
69
+ type TelemetryClient,
70
+ type Traceable,
71
+ type Tracer,
72
+ getTelemetryClient,
73
+ trackSpan,
74
+ } from '@aztec/telemetry-client';
55
75
 
56
76
  import { EventEmitter } from 'events';
57
77
  import groupBy from 'lodash.groupby';
58
- import { type GetContractReturnType, createPublicClient, fallback, getContract, http } from 'viem';
78
+ import { type GetContractReturnType, createPublicClient, fallback, http } from 'viem';
59
79
 
60
80
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
61
81
  import type { ArchiverConfig } from './config.js';
62
- import { retrieveBlocksFromRollup, retrieveL1ToL2Messages } from './data_retrieval.js';
63
- import { NoBlobBodiesFoundError } from './errors.js';
82
+ import {
83
+ retrieveBlocksFromRollup,
84
+ retrieveL1ToL2Message,
85
+ retrieveL1ToL2Messages,
86
+ retrievedBlockToPublishedL2Block,
87
+ } from './data_retrieval.js';
88
+ import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
64
89
  import { ArchiverInstrumentation } from './instrumentation.js';
65
- import type { DataRetrieval } from './structs/data_retrieval.js';
66
- import type { L1Published } from './structs/published.js';
90
+ import type { InboxMessage } from './structs/inbox_message.js';
91
+ import type { PublishedL2Block } from './structs/published.js';
92
+ import { type ValidateBlockResult, validateBlockAttestations } from './validation.js';
67
93
 
68
94
  /**
69
95
  * Helper interface to combine all sources this archiver implementation provides.
70
96
  */
71
- export type ArchiveSource = L2BlockSource &
72
- L2LogsSource &
73
- ContractDataSource &
74
- L1ToL2MessageSource &
75
- NullifierWithBlockSource;
97
+ export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
98
+
99
+ export type ArchiverDeps = {
100
+ telemetry?: TelemetryClient;
101
+ blobSinkClient: BlobSinkClientInterface;
102
+ epochCache?: EpochCache;
103
+ dateProvider?: DateProvider;
104
+ };
105
+
106
+ function mapArchiverConfig(config: Partial<ArchiverConfig>) {
107
+ return {
108
+ pollingIntervalMs: config.archiverPollingIntervalMS,
109
+ batchSize: config.archiverBatchSize,
110
+ skipValidateBlockAttestations: config.skipValidateBlockAttestations,
111
+ };
112
+ }
76
113
 
77
114
  /**
78
115
  * Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
79
116
  * Responsible for handling robust L1 polling so that other components do not need to
80
117
  * concern themselves with it.
81
118
  */
82
- export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
83
- /**
84
- * A promise in which we will be continually fetching new L2 blocks.
85
- */
119
+ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implements ArchiveSource, Traceable {
120
+ /** A loop in which we will be continually fetching new L2 blocks. */
86
121
  private runningPromise?: RunningPromise;
87
122
 
88
- private rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>;
89
- private inbox: GetContractReturnType<typeof InboxAbi, ViemPublicClient>;
123
+ private rollup: RollupContract;
124
+ private inbox: InboxContract;
90
125
 
91
126
  private store: ArchiverStoreHelper;
92
127
 
93
- public l1BlockNumber: bigint | undefined;
94
- public l1Timestamp: bigint | undefined;
128
+ private l1BlockNumber: bigint | undefined;
129
+ private l1Timestamp: bigint | undefined;
130
+ private initialSyncComplete: boolean = false;
131
+ private initialSyncPromise: PromiseWithResolvers<void>;
95
132
 
96
133
  public readonly tracer: Tracer;
97
134
 
@@ -109,10 +146,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
109
146
  private readonly publicClient: ViemPublicClient,
110
147
  private readonly l1Addresses: { rollupAddress: EthAddress; inboxAddress: EthAddress; registryAddress: EthAddress },
111
148
  readonly dataStore: ArchiverDataStore,
112
- private readonly config: { pollingIntervalMs: number; batchSize: number },
149
+ private config: { pollingIntervalMs: number; batchSize: number; skipValidateBlockAttestations?: boolean },
113
150
  private readonly blobSinkClient: BlobSinkClientInterface,
151
+ private readonly epochCache: EpochCache,
114
152
  private readonly instrumentation: ArchiverInstrumentation,
115
- private readonly l1constants: L1RollupConstants,
153
+ private readonly l1constants: L1RollupConstants & { l1StartBlockHash: Buffer32; genesisArchiveRoot: Fr },
116
154
  private readonly log: Logger = createLogger('archiver'),
117
155
  ) {
118
156
  super();
@@ -120,17 +158,9 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
120
158
  this.tracer = instrumentation.tracer;
121
159
  this.store = new ArchiverStoreHelper(dataStore);
122
160
 
123
- this.rollup = getContract({
124
- address: l1Addresses.rollupAddress.toString(),
125
- abi: RollupAbi,
126
- client: publicClient,
127
- });
128
-
129
- this.inbox = getContract({
130
- address: l1Addresses.inboxAddress.toString(),
131
- abi: InboxAbi,
132
- client: publicClient,
133
- });
161
+ this.rollup = new RollupContract(publicClient, l1Addresses.rollupAddress);
162
+ this.inbox = new InboxContract(publicClient, l1Addresses.inboxAddress);
163
+ this.initialSyncPromise = promiseWithResolvers();
134
164
  }
135
165
 
136
166
  /**
@@ -143,7 +173,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
143
173
  public static async createAndSync(
144
174
  config: ArchiverConfig,
145
175
  archiverStore: ArchiverDataStore,
146
- deps: { telemetry: TelemetryClient; blobSinkClient: BlobSinkClientInterface },
176
+ deps: ArchiverDeps,
147
177
  blockUntilSynced = true,
148
178
  ): Promise<Archiver> {
149
179
  const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
@@ -153,35 +183,56 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
153
183
  pollingInterval: config.viemPollingIntervalMS,
154
184
  });
155
185
 
156
- const rollup = getContract({
157
- address: config.l1Contracts.rollupAddress.toString(),
158
- abi: RollupAbi,
159
- client: publicClient,
160
- });
186
+ const rollup = new RollupContract(publicClient, config.l1Contracts.rollupAddress);
161
187
 
162
- const [l1StartBlock, l1GenesisTime] = await Promise.all([
163
- rollup.read.L1_BLOCK_AT_GENESIS(),
164
- rollup.read.getGenesisTime(),
188
+ const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, genesisArchiveRoot] = await Promise.all([
189
+ rollup.getL1StartBlock(),
190
+ rollup.getL1GenesisTime(),
191
+ rollup.getProofSubmissionEpochs(),
192
+ rollup.getGenesisArchiveTreeRoot(),
165
193
  ] as const);
166
194
 
195
+ const l1StartBlockHash = await publicClient
196
+ .getBlock({ blockNumber: l1StartBlock, includeTransactions: false })
197
+ .then(block => Buffer32.fromString(block.hash));
198
+
167
199
  const { aztecEpochDuration: epochDuration, aztecSlotDuration: slotDuration, ethereumSlotDuration } = config;
168
200
 
201
+ const l1Constants = {
202
+ l1StartBlockHash,
203
+ l1StartBlock,
204
+ l1GenesisTime,
205
+ epochDuration,
206
+ slotDuration,
207
+ ethereumSlotDuration,
208
+ proofSubmissionEpochs: Number(proofSubmissionEpochs),
209
+ genesisArchiveRoot: Fr.fromHexString(genesisArchiveRoot),
210
+ };
211
+
212
+ const opts = merge({ pollingIntervalMs: 10_000, batchSize: 100 }, mapArchiverConfig(config));
213
+
214
+ const epochCache = deps.epochCache ?? (await EpochCache.create(config.l1Contracts.rollupAddress, config, deps));
215
+ const telemetry = deps.telemetry ?? getTelemetryClient();
216
+
169
217
  const archiver = new Archiver(
170
218
  publicClient,
171
219
  config.l1Contracts,
172
220
  archiverStore,
173
- {
174
- pollingIntervalMs: config.archiverPollingIntervalMS ?? 10_000,
175
- batchSize: config.archiverBatchSize ?? 100,
176
- },
221
+ opts,
177
222
  deps.blobSinkClient,
178
- await ArchiverInstrumentation.new(deps.telemetry, () => archiverStore.estimateSize()),
179
- { l1StartBlock, l1GenesisTime, epochDuration, slotDuration, ethereumSlotDuration },
223
+ epochCache,
224
+ await ArchiverInstrumentation.new(telemetry, () => archiverStore.estimateSize()),
225
+ l1Constants,
180
226
  );
181
227
  await archiver.start(blockUntilSynced);
182
228
  return archiver;
183
229
  }
184
230
 
231
+ /** Updates archiver config */
232
+ public updateConfig(newConfig: Partial<ArchiverConfig>) {
233
+ this.config = merge(this.config, mapArchiverConfig(newConfig));
234
+ }
235
+
185
236
  /**
186
237
  * Starts sync process.
187
238
  * @param blockUntilSynced - If true, blocks until the archiver has fully synced.
@@ -191,8 +242,13 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
191
242
  throw new Error('Archiver is already running');
192
243
  }
193
244
 
245
+ await this.blobSinkClient.testSources();
246
+
194
247
  if (blockUntilSynced) {
195
- await this.syncSafe(blockUntilSynced);
248
+ while (!(await this.syncSafe(true))) {
249
+ this.log.info(`Retrying initial archiver sync in ${this.config.pollingIntervalMs}ms`);
250
+ await sleep(this.config.pollingIntervalMs);
251
+ }
196
252
  }
197
253
 
198
254
  this.runningPromise = new RunningPromise(
@@ -210,11 +266,30 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
210
266
  this.runningPromise.start();
211
267
  }
212
268
 
269
+ public syncImmediate() {
270
+ if (!this.runningPromise) {
271
+ throw new Error('Archiver is not running');
272
+ }
273
+ return this.runningPromise.trigger();
274
+ }
275
+
276
+ public waitForInitialSync() {
277
+ return this.initialSyncPromise.promise;
278
+ }
279
+
213
280
  private async syncSafe(initialRun: boolean) {
214
281
  try {
215
282
  await this.sync(initialRun);
283
+ return true;
216
284
  } catch (error) {
217
- this.log.error('Error during sync', { error });
285
+ if (error instanceof NoBlobBodiesFoundError) {
286
+ this.log.error(`Error syncing archiver: ${error.message}`);
287
+ } else if (error instanceof BlockTagTooOldError) {
288
+ this.log.warn(`Re-running archiver sync: ${error.message}`);
289
+ } else {
290
+ this.log.error('Error during archiver sync', error);
291
+ }
292
+ return false;
218
293
  }
219
294
  }
220
295
 
@@ -235,16 +310,21 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
235
310
  *
236
311
  * This code does not handle reorgs.
237
312
  */
238
- const { l1StartBlock } = this.l1constants;
239
- const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
240
- const currentL1BlockNumber = await this.publicClient.getBlockNumber();
313
+ const { l1StartBlock, l1StartBlockHash } = this.l1constants;
314
+ const {
315
+ blocksSynchedTo = l1StartBlock,
316
+ messagesSynchedTo = { l1BlockNumber: l1StartBlock, l1BlockHash: l1StartBlockHash },
317
+ } = await this.store.getSynchPoint();
318
+
319
+ const currentL1Block = await this.publicClient.getBlock({ includeTransactions: false });
320
+ const currentL1BlockNumber = currentL1Block.number;
321
+ const currentL1BlockHash = Buffer32.fromString(currentL1Block.hash);
241
322
 
242
323
  if (initialRun) {
243
324
  this.log.info(
244
- `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${Math.min(
245
- Number(blocksSynchedTo),
246
- Number(messagesSynchedTo),
247
- )} to current L1 block ${currentL1BlockNumber}`,
325
+ `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo}` +
326
+ ` to current L1 block ${currentL1BlockNumber} with hash ${currentL1BlockHash.toString()}`,
327
+ { blocksSynchedTo, messagesSynchedTo },
248
328
  );
249
329
  }
250
330
 
@@ -268,28 +348,49 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
268
348
  */
269
349
 
270
350
  // ********** Events that are processed per L1 block **********
271
- await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber);
351
+ await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber, currentL1BlockHash);
272
352
 
273
- // Store latest l1 block number and timestamp seen. Used for epoch and slots calculations.
274
- if (!this.l1BlockNumber || this.l1BlockNumber < currentL1BlockNumber) {
275
- this.l1Timestamp = (await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber })).timestamp;
276
- this.l1BlockNumber = currentL1BlockNumber;
277
- }
353
+ // Get L1 timestamp for the current block
354
+ const currentL1Timestamp =
355
+ !this.l1Timestamp || !this.l1BlockNumber || this.l1BlockNumber !== currentL1BlockNumber
356
+ ? (await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber })).timestamp
357
+ : this.l1Timestamp;
278
358
 
279
359
  // ********** Events that are processed per L2 block **********
280
360
  if (currentL1BlockNumber > blocksSynchedTo) {
281
- // First we retrieve new L2 blocks
282
- const { provenBlockNumber } = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
283
- // And then we prune the current epoch if it'd reorg on next submission.
361
+ // First we retrieve new L2 blocks and store them in the DB. This will also update the
362
+ // pending chain validation status, proven block number, and synched L1 block number.
363
+ const rollupStatus = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
364
+ // Then we prune the current epoch if it'd reorg on next submission.
284
365
  // Note that we don't do this before retrieving L2 blocks because we may need to retrieve
285
366
  // blocks from more than 2 epochs ago, so we want to make sure we have the latest view of
286
367
  // the chain locally before we start unwinding stuff. This can be optimized by figuring out
287
368
  // up to which point we're pruning, and then requesting L2 blocks up to that point only.
288
- await this.handleEpochPrune(provenBlockNumber, currentL1BlockNumber);
369
+ const { rollupCanPrune } = await this.handleEpochPrune(
370
+ rollupStatus.provenBlockNumber,
371
+ currentL1BlockNumber,
372
+ currentL1Timestamp,
373
+ );
374
+
375
+ // And lastly we check if we are missing any L2 blocks behind us due to a possible L1 reorg.
376
+ // We only do this if rollup cant prune on the next submission. Otherwise we will end up
377
+ // re-syncing the blocks we have just unwound above. We also dont do this if the last block is invalid,
378
+ // since the archiver will rightfully refuse to sync up to it.
379
+ if (!rollupCanPrune && rollupStatus.validationResult?.valid) {
380
+ await this.checkForNewBlocksBeforeL1SyncPoint(rollupStatus, blocksSynchedTo, currentL1BlockNumber);
381
+ }
289
382
 
290
383
  this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
291
384
  }
292
385
 
386
+ // After syncing has completed, update the current l1 block number and timestamp,
387
+ // otherwise we risk announcing to the world that we've synced to a given point,
388
+ // but the corresponding blocks have not been processed (see #12631).
389
+ this.l1Timestamp = currentL1Timestamp;
390
+ this.l1BlockNumber = currentL1BlockNumber;
391
+ this.initialSyncComplete = true;
392
+ this.initialSyncPromise.resolve();
393
+
293
394
  if (initialRun) {
294
395
  this.log.info(`Initial archiver sync to L1 block ${currentL1BlockNumber} complete.`, {
295
396
  l1BlockNumber: currentL1BlockNumber,
@@ -299,32 +400,51 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
299
400
  }
300
401
  }
301
402
 
302
- /** Queries the rollup contract on whether a prune can be executed on the immediatenext L1 block. */
303
- private async canPrune(currentL1BlockNumber: bigint) {
304
- const time = (this.l1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
305
- return await this.rollup.read.canPruneAtTime([time], { blockNumber: currentL1BlockNumber });
403
+ /** Queries the rollup contract on whether a prune can be executed on the immediate next L1 block. */
404
+ private async canPrune(currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
405
+ const time = (currentL1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
406
+ const result = await this.rollup.canPruneAtTime(time, { blockNumber: currentL1BlockNumber });
407
+ if (result) {
408
+ this.log.debug(`Rollup contract allows pruning at L1 block ${currentL1BlockNumber} time ${time}`, {
409
+ currentL1Timestamp,
410
+ pruneTime: time,
411
+ currentL1BlockNumber,
412
+ });
413
+ }
414
+ return result;
306
415
  }
307
416
 
308
417
  /** Checks if there'd be a reorg for the next block submission and start pruning now. */
309
- private async handleEpochPrune(provenBlockNumber: bigint, currentL1BlockNumber: bigint) {
310
- const localPendingBlockNumber = BigInt(await this.getBlockNumber());
311
- const canPrune = localPendingBlockNumber > provenBlockNumber && (await this.canPrune(currentL1BlockNumber));
418
+ private async handleEpochPrune(provenBlockNumber: number, currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
419
+ const rollupCanPrune = await this.canPrune(currentL1BlockNumber, currentL1Timestamp);
420
+ const localPendingBlockNumber = await this.getBlockNumber();
421
+ const canPrune = localPendingBlockNumber > provenBlockNumber && rollupCanPrune;
312
422
 
313
423
  if (canPrune) {
314
- const localPendingSlotNumber = await this.getL2SlotNumber();
315
- const localPendingEpochNumber = getEpochAtSlot(localPendingSlotNumber, this.l1constants);
424
+ const timer = new Timer();
425
+ const pruneFrom = provenBlockNumber + 1;
426
+
427
+ const header = await this.getBlockHeader(Number(pruneFrom));
428
+ if (header === undefined) {
429
+ throw new Error(`Missing block header ${pruneFrom}`);
430
+ }
431
+
432
+ const pruneFromSlotNumber = header.globalVariables.slotNumber.toBigInt();
433
+ const pruneFromEpochNumber = getEpochAtSlot(pruneFromSlotNumber, this.l1constants);
434
+
435
+ const blocksToUnwind = localPendingBlockNumber - provenBlockNumber;
436
+
437
+ const blocks = await this.getBlocks(Number(provenBlockNumber) + 1, Number(blocksToUnwind));
316
438
 
317
439
  // Emit an event for listening services to react to the chain prune
318
440
  this.emit(L2BlockSourceEvents.L2PruneDetected, {
319
441
  type: L2BlockSourceEvents.L2PruneDetected,
320
- blockNumber: localPendingBlockNumber,
321
- slotNumber: localPendingSlotNumber,
322
- epochNumber: localPendingEpochNumber,
442
+ epochNumber: pruneFromEpochNumber,
443
+ blocks,
323
444
  });
324
445
 
325
- const blocksToUnwind = localPendingBlockNumber - provenBlockNumber;
326
446
  this.log.debug(
327
- `L2 prune from ${provenBlockNumber + 1n} to ${localPendingBlockNumber} will occur on next block submission.`,
447
+ `L2 prune from ${provenBlockNumber + 1} to ${localPendingBlockNumber} will occur on next block submission.`,
328
448
  );
329
449
  await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
330
450
  this.log.warn(
@@ -332,11 +452,13 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
332
452
  `to ${provenBlockNumber} due to predicted reorg at L1 block ${currentL1BlockNumber}. ` +
333
453
  `Updated L2 latest block is ${await this.getBlockNumber()}.`,
334
454
  );
335
- this.instrumentation.processPrune();
455
+ this.instrumentation.processPrune(timer.ms());
336
456
  // TODO(palla/reorg): Do we need to set the block synched L1 block number here?
337
457
  // Seems like the next iteration should handle this.
338
458
  // await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
339
459
  }
460
+
461
+ return { rollupCanPrune };
340
462
  }
341
463
 
342
464
  private nextRange(end: bigint, limit: bigint): [bigint, bigint] {
@@ -349,49 +471,196 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
349
471
  return [nextStart, nextEnd];
350
472
  }
351
473
 
352
- private async handleL1ToL2Messages(messagesSynchedTo: bigint, currentL1BlockNumber: bigint) {
353
- this.log.trace(`Handling L1 to L2 messages from ${messagesSynchedTo} to ${currentL1BlockNumber}.`);
354
- if (currentL1BlockNumber <= messagesSynchedTo) {
474
+ private async handleL1ToL2Messages(
475
+ messagesSyncPoint: L1BlockId,
476
+ currentL1BlockNumber: bigint,
477
+ _currentL1BlockHash: Buffer32,
478
+ ) {
479
+ this.log.trace(`Handling L1 to L2 messages from ${messagesSyncPoint.l1BlockNumber} to ${currentL1BlockNumber}.`);
480
+ if (currentL1BlockNumber <= messagesSyncPoint.l1BlockNumber) {
355
481
  return;
356
482
  }
357
483
 
358
- const localTotalMessageCount = await this.store.getTotalL1ToL2MessageCount();
359
- const destinationTotalMessageCount = await this.inbox.read.totalMessagesInserted();
484
+ // Load remote and local inbox states.
485
+ const localMessagesInserted = await this.store.getTotalL1ToL2MessageCount();
486
+ const localLastMessage = await this.store.getLastL1ToL2Message();
487
+ const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
360
488
 
361
- if (localTotalMessageCount === destinationTotalMessageCount) {
362
- await this.store.setMessageSynchedL1BlockNumber(currentL1BlockNumber);
363
- this.log.trace(
364
- `Retrieved no new L1 to L2 messages between L1 blocks ${messagesSynchedTo + 1n} and ${currentL1BlockNumber}.`,
489
+ this.log.trace(`Retrieved remote inbox state at L1 block ${currentL1BlockNumber}.`, {
490
+ localMessagesInserted,
491
+ localLastMessage,
492
+ remoteMessagesState,
493
+ });
494
+
495
+ // Compare message count and rolling hash. If they match, no need to retrieve anything.
496
+ if (
497
+ remoteMessagesState.totalMessagesInserted === localMessagesInserted &&
498
+ remoteMessagesState.messagesRollingHash.equals(localLastMessage?.rollingHash ?? Buffer16.ZERO)
499
+ ) {
500
+ this.log.debug(
501
+ `No L1 to L2 messages to query between L1 blocks ${messagesSyncPoint.l1BlockNumber} and ${currentL1BlockNumber}.`,
365
502
  );
366
503
  return;
367
504
  }
368
505
 
369
- // Retrieve messages in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
370
- let searchStartBlock: bigint = messagesSynchedTo;
371
- let searchEndBlock: bigint = messagesSynchedTo;
506
+ // Check if our syncpoint is still valid. If not, there was an L1 reorg and we need to re-retrieve messages.
507
+ // Note that we need to fetch it from logs and not from inbox state at the syncpoint l1 block number, since it
508
+ // could be older than 128 blocks and non-archive nodes cannot resolve it.
509
+ if (localLastMessage) {
510
+ const remoteLastMessage = await this.retrieveL1ToL2Message(localLastMessage.leaf);
511
+ this.log.trace(`Retrieved remote message for local last`, { remoteLastMessage, localLastMessage });
512
+ if (!remoteLastMessage || !remoteLastMessage.rollingHash.equals(localLastMessage.rollingHash)) {
513
+ this.log.warn(`Rolling back L1 to L2 messages due to hash mismatch or msg not found.`, {
514
+ remoteLastMessage,
515
+ messagesSyncPoint,
516
+ localLastMessage,
517
+ });
518
+
519
+ messagesSyncPoint = await this.rollbackL1ToL2Messages(localLastMessage, messagesSyncPoint);
520
+ this.log.debug(`Rolled back L1 to L2 messages to L1 block ${messagesSyncPoint.l1BlockNumber}.`, {
521
+ messagesSyncPoint,
522
+ });
523
+ }
524
+ }
525
+
526
+ // Retrieve and save messages in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
527
+ let searchStartBlock: bigint = 0n;
528
+ let searchEndBlock: bigint = messagesSyncPoint.l1BlockNumber;
529
+
530
+ let lastMessage: InboxMessage | undefined;
531
+ let messageCount = 0;
532
+
372
533
  do {
373
534
  [searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
374
535
  this.log.trace(`Retrieving L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
375
- const retrievedL1ToL2Messages = await retrieveL1ToL2Messages(this.inbox, searchStartBlock, searchEndBlock);
536
+ const messages = await retrieveL1ToL2Messages(this.inbox.getContract(), searchStartBlock, searchEndBlock);
376
537
  this.log.verbose(
377
- `Retrieved ${retrievedL1ToL2Messages.retrievedData.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`,
538
+ `Retrieved ${messages.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`,
378
539
  );
379
- await this.store.addL1ToL2Messages(retrievedL1ToL2Messages);
380
- for (const msg of retrievedL1ToL2Messages.retrievedData) {
381
- this.log.debug(`Downloaded L1 to L2 message`, { leaf: msg.leaf.toString(), index: msg.index });
540
+ const timer = new Timer();
541
+ await this.store.addL1ToL2Messages(messages);
542
+ const perMsg = timer.ms() / messages.length;
543
+ this.instrumentation.processNewMessages(messages.length, perMsg);
544
+ for (const msg of messages) {
545
+ this.log.debug(`Downloaded L1 to L2 message`, { ...msg, leaf: msg.leaf.toString() });
546
+ lastMessage = msg;
547
+ messageCount++;
382
548
  }
383
549
  } while (searchEndBlock < currentL1BlockNumber);
550
+
551
+ // Log stats for messages retrieved (if any).
552
+ if (messageCount > 0) {
553
+ this.log.info(
554
+ `Retrieved ${messageCount} new L1 to L2 messages up to message with index ${lastMessage?.index} for L2 block ${lastMessage?.l2BlockNumber}`,
555
+ { lastMessage, messageCount },
556
+ );
557
+ }
558
+
559
+ // Warn if the resulting rolling hash does not match the remote state we had retrieved.
560
+ if (lastMessage && !lastMessage.rollingHash.equals(remoteMessagesState.messagesRollingHash)) {
561
+ this.log.warn(`Last message retrieved rolling hash does not match remote state.`, {
562
+ lastMessage,
563
+ remoteMessagesState,
564
+ });
565
+ }
384
566
  }
385
567
 
386
- private async handleL2blocks(
387
- blocksSynchedTo: bigint,
388
- currentL1BlockNumber: bigint,
389
- ): Promise<{ provenBlockNumber: bigint }> {
390
- const localPendingBlockNumber = BigInt(await this.getBlockNumber());
568
+ private async retrieveL1ToL2Message(leaf: Fr): Promise<InboxMessage | undefined> {
569
+ const currentL1BlockNumber = await this.publicClient.getBlockNumber();
570
+ let searchStartBlock: bigint = 0n;
571
+ let searchEndBlock: bigint = this.l1constants.l1StartBlock - 1n;
572
+
573
+ do {
574
+ [searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
575
+
576
+ const message = await retrieveL1ToL2Message(this.inbox.getContract(), leaf, searchStartBlock, searchEndBlock);
577
+
578
+ if (message) {
579
+ return message;
580
+ }
581
+ } while (searchEndBlock < currentL1BlockNumber);
582
+
583
+ return undefined;
584
+ }
585
+
586
+ private async rollbackL1ToL2Messages(localLastMessage: InboxMessage, messagesSyncPoint: L1BlockId) {
587
+ // Slowly go back through our messages until we find the last common message.
588
+ // We could query the logs in batch as an optimization, but the depth of the reorg should not be deep, and this
589
+ // is a very rare case, so it's fine to query one log at a time.
590
+ let commonMsg: undefined | InboxMessage;
591
+ this.log.verbose(`Searching most recent common L1 to L2 message at or before index ${localLastMessage.index}`);
592
+ for await (const msg of this.store.iterateL1ToL2Messages({ reverse: true, end: localLastMessage.index })) {
593
+ const remoteMsg = await this.retrieveL1ToL2Message(msg.leaf);
594
+ const logCtx = { remoteMsg, localMsg: msg };
595
+ if (remoteMsg && remoteMsg.rollingHash.equals(msg.rollingHash)) {
596
+ this.log.verbose(
597
+ `Found most recent common L1 to L2 message at index ${msg.index} on L1 block ${msg.l1BlockNumber}`,
598
+ logCtx,
599
+ );
600
+ commonMsg = remoteMsg;
601
+ break;
602
+ } else if (remoteMsg) {
603
+ this.log.debug(`Local L1 to L2 message with index ${msg.index} has different rolling hash`, logCtx);
604
+ } else {
605
+ this.log.debug(`Local L1 to L2 message with index ${msg.index} not found on L1`, logCtx);
606
+ }
607
+ }
608
+
609
+ // Delete everything after the common message we found.
610
+ const lastGoodIndex = commonMsg?.index;
611
+ this.log.warn(`Deleting all local L1 to L2 messages after index ${lastGoodIndex ?? 'undefined'}`);
612
+ await this.store.removeL1ToL2Messages(lastGoodIndex !== undefined ? lastGoodIndex + 1n : 0n);
613
+
614
+ // Update the syncpoint so the loop below reprocesses the changed messages. We go to the block before
615
+ // the last common one, so we force reprocessing it, in case new messages were added on that same L1 block
616
+ // after the last common message.
617
+ const syncPointL1BlockNumber = commonMsg ? commonMsg.l1BlockNumber - 1n : this.l1constants.l1StartBlock;
618
+ const syncPointL1BlockHash = await this.getL1BlockHash(syncPointL1BlockNumber);
619
+ messagesSyncPoint = { l1BlockNumber: syncPointL1BlockNumber, l1BlockHash: syncPointL1BlockHash };
620
+ await this.store.setMessageSynchedL1Block(messagesSyncPoint);
621
+ return messagesSyncPoint;
622
+ }
623
+
624
+ private async getL1BlockHash(l1BlockNumber: bigint): Promise<Buffer32> {
625
+ const block = await this.publicClient.getBlock({ blockNumber: l1BlockNumber, includeTransactions: false });
626
+ if (!block) {
627
+ throw new Error(`Missing L1 block ${l1BlockNumber}`);
628
+ }
629
+ return Buffer32.fromString(block.hash);
630
+ }
631
+
632
+ private async handleL2blocks(blocksSynchedTo: bigint, currentL1BlockNumber: bigint) {
633
+ const localPendingBlockNumber = await this.getBlockNumber();
634
+ const initialValidationResult: ValidateBlockResult | undefined = await this.store.getPendingChainValidationStatus();
391
635
  const [provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive, archiveForLocalPendingBlockNumber] =
392
- await this.rollup.read.status([localPendingBlockNumber], { blockNumber: currentL1BlockNumber });
636
+ await this.rollup.status(BigInt(localPendingBlockNumber), { blockNumber: currentL1BlockNumber });
637
+ const rollupStatus = {
638
+ provenBlockNumber: Number(provenBlockNumber),
639
+ provenArchive,
640
+ pendingBlockNumber: Number(pendingBlockNumber),
641
+ pendingArchive,
642
+ validationResult: initialValidationResult,
643
+ };
644
+ this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
645
+ localPendingBlockNumber,
646
+ blocksSynchedTo,
647
+ currentL1BlockNumber,
648
+ archiveForLocalPendingBlockNumber,
649
+ ...rollupStatus,
650
+ });
393
651
 
394
652
  const updateProvenBlock = async () => {
653
+ // Annoying edge case: if proven block is moved back to 0 due to a reorg at the beginning of the chain,
654
+ // we need to set it to zero. This is an edge case because we dont have a block zero (initial block is one),
655
+ // so localBlockForDestinationProvenBlockNumber would not be found below.
656
+ if (provenBlockNumber === 0n) {
657
+ const localProvenBlockNumber = await this.store.getProvenL2BlockNumber();
658
+ if (localProvenBlockNumber !== Number(provenBlockNumber)) {
659
+ await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
660
+ this.log.info(`Rolled back proven chain to block ${provenBlockNumber}`, { provenBlockNumber });
661
+ }
662
+ }
663
+
395
664
  const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
396
665
 
397
666
  // Sanity check. I've hit what seems to be a state where the proven block is set to a value greater than the latest
@@ -403,6 +672,12 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
403
672
  );
404
673
  }
405
674
 
675
+ this.log.trace(
676
+ `Local block for remote proven block ${provenBlockNumber} is ${
677
+ localBlockForDestinationProvenBlockNumber?.archive.root.toString() ?? 'undefined'
678
+ }`,
679
+ );
680
+
406
681
  if (
407
682
  localBlockForDestinationProvenBlockNumber &&
408
683
  provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()
@@ -413,6 +688,17 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
413
688
  this.log.info(`Updated proven chain to block ${provenBlockNumber}`, {
414
689
  provenBlockNumber,
415
690
  });
691
+ const provenSlotNumber =
692
+ localBlockForDestinationProvenBlockNumber.header.globalVariables.slotNumber.toBigInt();
693
+ const provenEpochNumber = getEpochAtSlot(provenSlotNumber, this.l1constants);
694
+ this.emit(L2BlockSourceEvents.L2BlockProven, {
695
+ type: L2BlockSourceEvents.L2BlockProven,
696
+ blockNumber: provenBlockNumber,
697
+ slotNumber: provenSlotNumber,
698
+ epochNumber: provenEpochNumber,
699
+ });
700
+ } else {
701
+ this.log.trace(`Proven block ${provenBlockNumber} already stored.`);
416
702
  }
417
703
  }
418
704
  this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
@@ -420,11 +706,13 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
420
706
 
421
707
  // This is an edge case that we only hit if there are no proposed blocks.
422
708
  // If we have 0 blocks locally and there are no blocks onchain there is nothing to do.
423
- const noBlocks = localPendingBlockNumber === 0n && pendingBlockNumber === 0n;
709
+ const noBlocks = localPendingBlockNumber === 0 && pendingBlockNumber === 0n;
424
710
  if (noBlocks) {
425
711
  await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
426
- this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
427
- return { provenBlockNumber };
712
+ this.log.debug(
713
+ `No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no blocks on chain`,
714
+ );
715
+ return rollupStatus;
428
716
  }
429
717
 
430
718
  await updateProvenBlock();
@@ -432,25 +720,36 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
432
720
  // Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
433
721
  // are any state that could be impacted by it. If we have no blocks, there is no impact.
434
722
  if (localPendingBlockNumber > 0) {
435
- const localPendingBlock = await this.getBlock(Number(localPendingBlockNumber));
723
+ const localPendingBlock = await this.getBlock(localPendingBlockNumber);
436
724
  if (localPendingBlock === undefined) {
437
725
  throw new Error(`Missing block ${localPendingBlockNumber}`);
438
726
  }
439
727
 
440
- const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingBlock.archive.root.toString();
728
+ const localPendingArchiveRoot = localPendingBlock.archive.root.toString();
729
+ const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingArchiveRoot;
441
730
  if (noBlockSinceLast) {
442
- await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
731
+ // We believe the following line causes a problem when we encounter L1 re-orgs.
732
+ // Basically, by setting the synched L1 block number here, we are saying that we have
733
+ // processed all blocks up to the current L1 block number and we will not attempt to retrieve logs from
734
+ // this block again (or any blocks before).
735
+ // However, in the re-org scenario, our L1 node is temporarily lying to us and we end up potentially missing blocks
736
+ // We must only set this block number based on actually retrieved logs.
737
+ // TODO(#8621): Tackle this properly when we handle L1 Re-orgs.
738
+ // await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
443
739
  this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
444
- return { provenBlockNumber };
740
+ return rollupStatus;
445
741
  }
446
742
 
447
- const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingBlock.archive.root.toString();
743
+ const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingArchiveRoot;
448
744
  if (!localPendingBlockInChain) {
449
745
  // If our local pending block tip is not in the chain on L1 a "prune" must have happened
450
746
  // or the L1 have reorged.
451
747
  // In any case, we have to figure out how far into the past the action will take us.
452
748
  // For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
453
- this.log.debug(`L2 prune has been detected.`);
749
+ this.log.debug(
750
+ `L2 prune has been detected due to local pending block ${localPendingBlockNumber} not in chain`,
751
+ { localPendingBlockNumber, localPendingArchiveRoot, archiveForLocalPendingBlockNumber },
752
+ );
454
753
 
455
754
  let tipAfterUnwind = localPendingBlockNumber;
456
755
  while (true) {
@@ -459,7 +758,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
459
758
  break;
460
759
  }
461
760
 
462
- const archiveAtContract = await this.rollup.read.archiveAt([BigInt(candidateBlock.number)]);
761
+ const archiveAtContract = await this.rollup.archiveAt(BigInt(candidateBlock.number));
463
762
 
464
763
  if (archiveAtContract === candidateBlock.archive.root.toString()) {
465
764
  break;
@@ -478,19 +777,20 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
478
777
  }
479
778
  }
480
779
 
481
- // Retrieve L2 blocks in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
780
+ // Retrieve L2 blocks in batches. Each batch is estimated to accommodate up to L2 'blockBatchSize' blocks,
482
781
  // computed using the L2 block time vs the L1 block time.
483
782
  let searchStartBlock: bigint = blocksSynchedTo;
484
783
  let searchEndBlock: bigint = blocksSynchedTo;
784
+ let lastRetrievedBlock: PublishedL2Block | undefined;
485
785
 
486
786
  do {
487
787
  [searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
488
788
 
489
789
  this.log.trace(`Retrieving L2 blocks from L1 block ${searchStartBlock} to ${searchEndBlock}`);
490
790
 
491
- // TODO(md): Retreive from blob sink then from consensus client, then from peers
791
+ // TODO(md): Retrieve from blob sink then from consensus client, then from peers
492
792
  const retrievedBlocks = await retrieveBlocksFromRollup(
493
- this.rollup,
793
+ this.rollup.getContract() as GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
494
794
  this.publicClient,
495
795
  this.blobSinkClient,
496
796
  searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
@@ -510,35 +810,159 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
510
810
  `Retrieved ${retrievedBlocks.length} new L2 blocks between L1 blocks ${searchStartBlock} and ${searchEndBlock} with last processed L1 block ${lastProcessedL1BlockNumber}.`,
511
811
  );
512
812
 
513
- for (const block of retrievedBlocks) {
514
- this.log.debug(`Ingesting new L2 block ${block.data.number} with ${block.data.body.txEffects.length} txs`, {
515
- blockHash: block.data.hash(),
813
+ const publishedBlocks = await Promise.all(retrievedBlocks.map(b => retrievedBlockToPublishedL2Block(b)));
814
+ const validBlocks: PublishedL2Block[] = [];
815
+
816
+ for (const block of publishedBlocks) {
817
+ const validationResult = this.config.skipValidateBlockAttestations
818
+ ? { valid: true as const }
819
+ : await validateBlockAttestations(block, this.epochCache, this.l1constants, this.log);
820
+
821
+ // Only update the validation result if it has changed, so we can keep track of the first invalid block
822
+ // in case there is a sequence of more than one invalid block, as we need to invalidate the first one.
823
+ // There is an exception though: if an invalid block is invalidated and replaced with another invalid block,
824
+ // we need to update the validation result, since we need to be able to invalidate the new one.
825
+ // See test 'chain progresses if an invalid block is invalidated with an invalid one' for more info.
826
+ if (
827
+ rollupStatus.validationResult?.valid !== validationResult.valid ||
828
+ (!rollupStatus.validationResult.valid &&
829
+ !validationResult.valid &&
830
+ rollupStatus.validationResult.block.blockNumber === validationResult.block.blockNumber)
831
+ ) {
832
+ rollupStatus.validationResult = validationResult;
833
+ }
834
+
835
+ if (!validationResult.valid) {
836
+ this.log.warn(`Skipping block ${block.block.number} due to invalid attestations`, {
837
+ blockHash: block.block.hash(),
838
+ l1BlockNumber: block.l1.blockNumber,
839
+ ...pick(validationResult, 'reason'),
840
+ });
841
+
842
+ // Emit event for invalid block detection
843
+ this.emit(L2BlockSourceEvents.InvalidAttestationsBlockDetected, {
844
+ type: L2BlockSourceEvents.InvalidAttestationsBlockDetected,
845
+ validationResult,
846
+ });
847
+
848
+ // We keep consuming blocks if we find an invalid one, since we do not listen for BlockInvalidated events
849
+ // We just pretend the invalid ones are not there and keep consuming the next blocks
850
+ // Note that this breaks if the committee ever attests to a descendant of an invalid block
851
+ continue;
852
+ }
853
+
854
+ validBlocks.push(block);
855
+ this.log.debug(`Ingesting new L2 block ${block.block.number} with ${block.block.body.txEffects.length} txs`, {
856
+ blockHash: block.block.hash(),
516
857
  l1BlockNumber: block.l1.blockNumber,
517
- ...block.data.header.globalVariables.toInspect(),
518
- ...block.data.getStats(),
858
+ ...block.block.header.globalVariables.toInspect(),
859
+ ...block.block.getStats(),
519
860
  });
520
861
  }
521
862
 
522
- const [processDuration] = await elapsed(() => this.store.addBlocks(retrievedBlocks));
523
- this.instrumentation.processNewBlocks(
524
- processDuration / retrievedBlocks.length,
525
- retrievedBlocks.map(b => b.data),
526
- );
863
+ try {
864
+ const updatedValidationResult =
865
+ rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
866
+ const [processDuration] = await elapsed(() => this.store.addBlocks(validBlocks, updatedValidationResult));
867
+ this.instrumentation.processNewBlocks(
868
+ processDuration / validBlocks.length,
869
+ validBlocks.map(b => b.block),
870
+ );
871
+ } catch (err) {
872
+ if (err instanceof InitialBlockNumberNotSequentialError) {
873
+ const { previousBlockNumber, newBlockNumber } = err;
874
+ const previousBlock = previousBlockNumber
875
+ ? await this.store.getPublishedBlock(previousBlockNumber)
876
+ : undefined;
877
+ const updatedL1SyncPoint = previousBlock?.l1.blockNumber ?? this.l1constants.l1StartBlock;
878
+ await this.store.setBlockSynchedL1BlockNumber(updatedL1SyncPoint);
879
+ this.log.warn(
880
+ `Attempting to insert block ${newBlockNumber} with previous block ${previousBlockNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
881
+ {
882
+ previousBlockNumber,
883
+ previousBlockHash: await previousBlock?.block.hash(),
884
+ newBlockNumber,
885
+ updatedL1SyncPoint,
886
+ },
887
+ );
888
+ }
889
+ throw err;
890
+ }
527
891
 
528
- for (const block of retrievedBlocks) {
529
- this.log.info(`Downloaded L2 block ${block.data.number}`, {
530
- blockHash: block.data.hash(),
531
- blockNumber: block.data.number,
532
- txCount: block.data.body.txEffects.length,
533
- globalVariables: block.data.header.globalVariables.toInspect(),
892
+ for (const block of validBlocks) {
893
+ this.log.info(`Downloaded L2 block ${block.block.number}`, {
894
+ blockHash: await block.block.hash(),
895
+ blockNumber: block.block.number,
896
+ txCount: block.block.body.txEffects.length,
897
+ globalVariables: block.block.header.globalVariables.toInspect(),
898
+ archiveRoot: block.block.archive.root.toString(),
899
+ archiveNextLeafIndex: block.block.archive.nextAvailableLeafIndex,
534
900
  });
535
901
  }
902
+ lastRetrievedBlock = validBlocks.at(-1) ?? lastRetrievedBlock;
536
903
  } while (searchEndBlock < currentL1BlockNumber);
537
904
 
538
905
  // Important that we update AFTER inserting the blocks.
539
906
  await updateProvenBlock();
540
907
 
541
- return { provenBlockNumber };
908
+ return { ...rollupStatus, lastRetrievedBlock };
909
+ }
910
+
911
+ private async checkForNewBlocksBeforeL1SyncPoint(
912
+ status: {
913
+ lastRetrievedBlock?: PublishedL2Block;
914
+ pendingBlockNumber: number;
915
+ },
916
+ blocksSynchedTo: bigint,
917
+ currentL1BlockNumber: bigint,
918
+ ) {
919
+ const { lastRetrievedBlock, pendingBlockNumber } = status;
920
+ // Compare the last L2 block we have (either retrieved in this round or loaded from store) with what the
921
+ // rollup contract told us was the latest one (pinned at the currentL1BlockNumber).
922
+ const latestLocalL2BlockNumber = lastRetrievedBlock?.block.number ?? (await this.store.getSynchedL2BlockNumber());
923
+ if (latestLocalL2BlockNumber < pendingBlockNumber) {
924
+ // Here we have consumed all logs until the `currentL1Block` we pinned at the beginning of the archiver loop,
925
+ // but still havent reached the pending block according to the call to the rollup contract.
926
+ // We suspect an L1 reorg that added blocks *behind* us. If that is the case, it must have happened between the
927
+ // last L2 block we saw and the current one, so we reset the last synched L1 block number. In the edge case we
928
+ // don't have one, we go back 2 L1 epochs, which is the deepest possible reorg (assuming Casper is working).
929
+ const latestLocalL2Block =
930
+ lastRetrievedBlock ??
931
+ (latestLocalL2BlockNumber > 0
932
+ ? await this.store.getPublishedBlocks(latestLocalL2BlockNumber, 1).then(([b]) => b)
933
+ : undefined);
934
+ const targetL1BlockNumber = latestLocalL2Block?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
935
+ const latestLocalL2BlockArchive = latestLocalL2Block?.block.archive.root.toString();
936
+ this.log.warn(
937
+ `Failed to reach L2 block ${pendingBlockNumber} at ${currentL1BlockNumber} (latest is ${latestLocalL2BlockNumber}). ` +
938
+ `Rolling back last synched L1 block number to ${targetL1BlockNumber}.`,
939
+ {
940
+ latestLocalL2BlockNumber,
941
+ latestLocalL2BlockArchive,
942
+ blocksSynchedTo,
943
+ currentL1BlockNumber,
944
+ ...status,
945
+ },
946
+ );
947
+ await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
948
+ } else {
949
+ this.log.trace(`No new blocks behind L1 sync point to retrieve.`, {
950
+ latestLocalL2BlockNumber,
951
+ pendingBlockNumber,
952
+ });
953
+ }
954
+ }
955
+
956
+ /** Resumes the archiver after a stop. */
957
+ public resume() {
958
+ if (!this.runningPromise) {
959
+ throw new Error(`Archiver was never started`);
960
+ }
961
+ if (this.runningPromise.isRunning()) {
962
+ this.log.warn(`Archiver already running`);
963
+ }
964
+ this.log.info(`Restarting archiver`);
965
+ this.runningPromise.start();
542
966
  }
543
967
 
544
968
  /**
@@ -553,10 +977,18 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
553
977
  return Promise.resolve();
554
978
  }
555
979
 
980
+ public backupTo(destPath: string): Promise<string> {
981
+ return this.dataStore.backupTo(destPath);
982
+ }
983
+
556
984
  public getL1Constants(): Promise<L1RollupConstants> {
557
985
  return Promise.resolve(this.l1constants);
558
986
  }
559
987
 
988
+ public getGenesisValues(): Promise<{ genesisArchiveRoot: Fr }> {
989
+ return Promise.resolve({ genesisArchiveRoot: this.l1constants.genesisArchiveRoot });
990
+ }
991
+
560
992
  public getRollupAddress(): Promise<EthAddress> {
561
993
  return Promise.resolve(this.l1Addresses.rollupAddress);
562
994
  }
@@ -565,28 +997,24 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
565
997
  return Promise.resolve(this.l1Addresses.registryAddress);
566
998
  }
567
999
 
568
- public getL1BlockNumber(): bigint {
569
- const l1BlockNumber = this.l1BlockNumber;
570
- if (!l1BlockNumber) {
571
- throw new Error('L1 block number not yet available. Complete an initial sync first.');
572
- }
573
- return l1BlockNumber;
1000
+ public getL1BlockNumber(): bigint | undefined {
1001
+ return this.l1BlockNumber;
574
1002
  }
575
1003
 
576
- public getL1Timestamp(): bigint {
577
- const l1Timestamp = this.l1Timestamp;
578
- if (!l1Timestamp) {
579
- throw new Error('L1 timestamp not yet available. Complete an initial sync first.');
580
- }
581
- return l1Timestamp;
1004
+ public getL1Timestamp(): Promise<bigint | undefined> {
1005
+ return Promise.resolve(this.l1Timestamp);
582
1006
  }
583
1007
 
584
- public getL2SlotNumber(): Promise<bigint> {
585
- return Promise.resolve(getSlotAtTimestamp(this.getL1Timestamp(), this.l1constants));
1008
+ public getL2SlotNumber(): Promise<bigint | undefined> {
1009
+ return Promise.resolve(
1010
+ this.l1Timestamp === undefined ? undefined : getSlotAtTimestamp(this.l1Timestamp, this.l1constants),
1011
+ );
586
1012
  }
587
1013
 
588
- public getL2EpochNumber(): Promise<bigint> {
589
- return Promise.resolve(getEpochNumberAtTimestamp(this.getL1Timestamp(), this.l1constants));
1014
+ public getL2EpochNumber(): Promise<bigint | undefined> {
1015
+ return Promise.resolve(
1016
+ this.l1Timestamp === undefined ? undefined : getEpochNumberAtTimestamp(this.l1Timestamp, this.l1constants),
1017
+ );
590
1018
  }
591
1019
 
592
1020
  public async getBlocksForEpoch(epochNumber: bigint): Promise<L2Block[]> {
@@ -607,6 +1035,24 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
607
1035
  return blocks.reverse();
608
1036
  }
609
1037
 
1038
+ public async getBlockHeadersForEpoch(epochNumber: bigint): Promise<BlockHeader[]> {
1039
+ const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1040
+ const blocks: BlockHeader[] = [];
1041
+
1042
+ // Walk the list of blocks backwards and filter by slots matching the requested epoch.
1043
+ // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1044
+ let number = await this.store.getSynchedL2BlockNumber();
1045
+ let header = await this.getBlockHeader(number);
1046
+ const slot = (b: BlockHeader) => b.globalVariables.slotNumber.toBigInt();
1047
+ while (header && slot(header) >= start) {
1048
+ if (slot(header) <= end) {
1049
+ blocks.push(header);
1050
+ }
1051
+ header = await this.getBlockHeader(--number);
1052
+ }
1053
+ return blocks.reverse();
1054
+ }
1055
+
610
1056
  public async isEpochComplete(epochNumber: bigint): Promise<boolean> {
611
1057
  // The epoch is complete if the current L2 block is the last one in the epoch (or later)
612
1058
  const header = await this.getBlockHeader('latest');
@@ -635,6 +1081,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
635
1081
  return l1Timestamp + leeway >= endTimestamp;
636
1082
  }
637
1083
 
1084
+ /** Returns whether the archiver has completed an initial sync run successfully. */
1085
+ public isInitialSyncComplete(): boolean {
1086
+ return this.initialSyncComplete;
1087
+ }
1088
+
638
1089
  /**
639
1090
  * Gets up to `limit` amount of L2 blocks starting from `from`.
640
1091
  * @param from - Number of the first block to return (inclusive).
@@ -642,11 +1093,32 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
642
1093
  * @param proven - If true, only return blocks that have been proven.
643
1094
  * @returns The requested L2 blocks.
644
1095
  */
645
- public async getBlocks(from: number, limit: number, proven?: boolean): Promise<L2Block[]> {
1096
+ public getBlocks(from: number, limit: number, proven?: boolean): Promise<L2Block[]> {
1097
+ return this.getPublishedBlocks(from, limit, proven).then(blocks => blocks.map(b => b.block));
1098
+ }
1099
+
1100
+ /** Equivalent to getBlocks but includes publish data. */
1101
+ public async getPublishedBlocks(from: number, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
646
1102
  const limitWithProven = proven
647
1103
  ? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
648
1104
  : limit;
649
- return limitWithProven === 0 ? [] : (await this.store.getBlocks(from, limitWithProven)).map(b => b.data);
1105
+ return limitWithProven === 0 ? [] : await this.store.getPublishedBlocks(from, limitWithProven);
1106
+ }
1107
+
1108
+ public getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1109
+ return this.store.getPublishedBlockByHash(blockHash);
1110
+ }
1111
+
1112
+ public getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1113
+ return this.store.getPublishedBlockByArchive(archive);
1114
+ }
1115
+
1116
+ public getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
1117
+ return this.store.getBlockHeaderByHash(blockHash);
1118
+ }
1119
+
1120
+ public getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1121
+ return this.store.getBlockHeaderByArchive(archive);
650
1122
  }
651
1123
 
652
1124
  /**
@@ -659,11 +1131,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
659
1131
  if (number < 0) {
660
1132
  number = await this.store.getSynchedL2BlockNumber();
661
1133
  }
662
- if (number == 0) {
1134
+ if (number === 0) {
663
1135
  return undefined;
664
1136
  }
665
- const blocks = await this.store.getBlocks(number, 1);
666
- return blocks.length === 0 ? undefined : blocks[0].data;
1137
+ const publishedBlock = await this.store.getPublishedBlock(number);
1138
+ return publishedBlock?.block;
667
1139
  }
668
1140
 
669
1141
  public async getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
@@ -685,29 +1157,6 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
685
1157
  return this.store.getSettledTxReceipt(txHash);
686
1158
  }
687
1159
 
688
- /**
689
- * Gets the public function data for a contract.
690
- * @param address - The contract address containing the function to fetch.
691
- * @param selector - The function selector of the function to fetch.
692
- * @returns The public function data (if found).
693
- */
694
- public async getPublicFunction(
695
- address: AztecAddress,
696
- selector: FunctionSelector,
697
- ): Promise<PublicFunction | undefined> {
698
- const instance = await this.getContract(address);
699
- if (!instance) {
700
- throw new Error(`Contract ${address.toString()} not found`);
701
- }
702
- const contractClass = await this.getContractClass(instance.currentContractClassId);
703
- if (!contractClass) {
704
- throw new Error(
705
- `Contract class ${instance.currentContractClassId.toString()} for ${address.toString()} not found`,
706
- );
707
- }
708
- return contractClass.publicFunctions.find(f => f.selector.equals(selector));
709
- }
710
-
711
1160
  /**
712
1161
  * Retrieves all private logs from up to `limit` blocks, starting from the block number `from`.
713
1162
  * @param from - The block number from which to begin retrieving logs.
@@ -728,17 +1177,6 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
728
1177
  return this.store.getLogsByTags(tags);
729
1178
  }
730
1179
 
731
- /**
732
- * Returns the provided nullifier indexes scoped to the block
733
- * they were first included in, or undefined if they're not present in the tree
734
- * @param blockNumber Max block number to search for the nullifiers
735
- * @param nullifiers Nullifiers to get
736
- * @returns The block scoped indexes of the provided nullifiers, or undefined if the nullifier doesn't exist in the tree
737
- */
738
- findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
739
- return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
740
- }
741
-
742
1180
  /**
743
1181
  * Gets public logs based on the provided filter.
744
1182
  * @param filter - The filter to apply to the logs.
@@ -782,8 +1220,20 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
782
1220
  return this.store.getBytecodeCommitment(id);
783
1221
  }
784
1222
 
785
- public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
786
- return this.store.getContractInstance(address);
1223
+ public async getContract(
1224
+ address: AztecAddress,
1225
+ maybeTimestamp?: UInt64,
1226
+ ): Promise<ContractInstanceWithAddress | undefined> {
1227
+ let timestamp;
1228
+ if (maybeTimestamp === undefined) {
1229
+ const latestBlockHeader = await this.getBlockHeader('latest');
1230
+ // If we get undefined block header, it means that the archiver has not yet synced any block so we default to 0.
1231
+ timestamp = latestBlockHeader ? latestBlockHeader.globalVariables.timestamp : 0n;
1232
+ } else {
1233
+ timestamp = maybeTimestamp;
1234
+ }
1235
+
1236
+ return this.store.getContractInstance(address, timestamp);
787
1237
  }
788
1238
 
789
1239
  /**
@@ -791,7 +1241,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
791
1241
  * @param blockNumber - L2 block number to get messages for.
792
1242
  * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found).
793
1243
  */
794
- getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
1244
+ getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
795
1245
  return this.store.getL1ToL2Messages(blockNumber);
796
1246
  }
797
1247
 
@@ -808,22 +1258,20 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
808
1258
  return this.store.getContractClassIds();
809
1259
  }
810
1260
 
811
- // TODO(#10007): Remove this method
812
- async addContractClass(contractClass: ContractClassPublic): Promise<void> {
813
- await this.store.addContractClasses(
814
- [contractClass],
815
- [await computePublicBytecodeCommitment(contractClass.packedBytecode)],
816
- 0,
817
- );
818
- return;
1261
+ registerContractFunctionSignatures(signatures: string[]): Promise<void> {
1262
+ return this.store.registerContractFunctionSignatures(signatures);
819
1263
  }
820
1264
 
821
- registerContractFunctionSignatures(address: AztecAddress, signatures: string[]): Promise<void> {
822
- return this.store.registerContractFunctionSignatures(address, signatures);
1265
+ getDebugFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
1266
+ return this.store.getDebugFunctionName(address, selector);
823
1267
  }
824
1268
 
825
- getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
826
- return this.store.getContractFunctionName(address, selector);
1269
+ async getPendingChainValidationStatus(): Promise<ValidateBlockResult> {
1270
+ return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
1271
+ }
1272
+
1273
+ isPendingChainInvalid(): Promise<boolean> {
1274
+ return this.getPendingChainValidationStatus().then(status => !status.valid);
827
1275
  }
828
1276
 
829
1277
  async getL2Tips(): Promise<L2Tips> {
@@ -832,9 +1280,15 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
832
1280
  this.getProvenBlockNumber(),
833
1281
  ] as const);
834
1282
 
835
- const [latestBlockHeader, provenBlockHeader] = await Promise.all([
1283
+ // TODO(#13569): Compute proper finalized block number based on L1 finalized block.
1284
+ // We just force it 2 epochs worth of proven data for now.
1285
+ // NOTE: update end-to-end/src/e2e_epochs/epochs_empty_blocks.test.ts as that uses finalized blocks in computations
1286
+ const finalizedBlockNumber = Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0);
1287
+
1288
+ const [latestBlockHeader, provenBlockHeader, finalizedBlockHeader] = await Promise.all([
836
1289
  latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
837
1290
  provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined,
1291
+ finalizedBlockNumber > 0 ? this.getBlockHeader(finalizedBlockNumber) : undefined,
838
1292
  ] as const);
839
1293
 
840
1294
  if (latestBlockNumber > 0 && !latestBlockHeader) {
@@ -847,9 +1301,16 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
847
1301
  );
848
1302
  }
849
1303
 
1304
+ if (finalizedBlockNumber > 0 && !finalizedBlockHeader) {
1305
+ throw new Error(
1306
+ `Failed to retrieve finalized block header for block ${finalizedBlockNumber} (latest block is ${latestBlockNumber})`,
1307
+ );
1308
+ }
1309
+
850
1310
  const latestBlockHeaderHash = await latestBlockHeader?.hash();
851
1311
  const provenBlockHeaderHash = await provenBlockHeader?.hash();
852
- const finalizedBlockHeaderHash = await provenBlockHeader?.hash();
1312
+ const finalizedBlockHeaderHash = await finalizedBlockHeader?.hash();
1313
+
853
1314
  return {
854
1315
  latest: {
855
1316
  number: latestBlockNumber,
@@ -860,11 +1321,45 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
860
1321
  hash: provenBlockHeaderHash?.toString(),
861
1322
  } as L2BlockId,
862
1323
  finalized: {
863
- number: provenBlockNumber,
1324
+ number: finalizedBlockNumber,
864
1325
  hash: finalizedBlockHeaderHash?.toString(),
865
1326
  } as L2BlockId,
866
1327
  };
867
1328
  }
1329
+
1330
+ public async rollbackTo(targetL2BlockNumber: number): Promise<void> {
1331
+ const currentBlocks = await this.getL2Tips();
1332
+ const currentL2Block = currentBlocks.latest.number;
1333
+ const currentProvenBlock = currentBlocks.proven.number;
1334
+ // const currentFinalizedBlock = currentBlocks.finalized.number;
1335
+
1336
+ if (targetL2BlockNumber >= currentL2Block) {
1337
+ throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
1338
+ }
1339
+ const blocksToUnwind = currentL2Block - targetL2BlockNumber;
1340
+ const targetL2Block = await this.store.getPublishedBlock(targetL2BlockNumber);
1341
+ if (!targetL2Block) {
1342
+ throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
1343
+ }
1344
+ const targetL1BlockNumber = targetL2Block.l1.blockNumber;
1345
+ const targetL1BlockHash = await this.getL1BlockHash(targetL1BlockNumber);
1346
+ this.log.info(`Unwinding ${blocksToUnwind} blocks from L2 block ${currentL2Block}`);
1347
+ await this.store.unwindBlocks(currentL2Block, blocksToUnwind);
1348
+ this.log.info(`Unwinding L1 to L2 messages to ${targetL2BlockNumber}`);
1349
+ await this.store.rollbackL1ToL2MessagesToL2Block(targetL2BlockNumber);
1350
+ this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
1351
+ await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
1352
+ await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
1353
+ if (targetL2BlockNumber < currentProvenBlock) {
1354
+ this.log.info(`Clearing proven L2 block number`);
1355
+ await this.store.setProvenL2BlockNumber(0);
1356
+ }
1357
+ // TODO(palla/reorg): Set the finalized block when we add support for it.
1358
+ // if (targetL2BlockNumber < currentFinalizedBlock) {
1359
+ // this.log.info(`Clearing finalized L2 block number`);
1360
+ // await this.store.setFinalizedL2BlockNumber(0);
1361
+ // }
1362
+ }
868
1363
  }
869
1364
 
870
1365
  enum Operation {
@@ -878,14 +1373,12 @@ enum Operation {
878
1373
  * I would have preferred to not have this type. But it is useful for handling the logic that any
879
1374
  * store would need to include otherwise while exposing fewer functions and logic directly to the archiver.
880
1375
  */
881
- class ArchiverStoreHelper
1376
+ export class ArchiverStoreHelper
882
1377
  implements
883
1378
  Omit<
884
1379
  ArchiverDataStore,
885
1380
  | 'addLogs'
886
1381
  | 'deleteLogs'
887
- | 'addNullifiers'
888
- | 'deleteNullifiers'
889
1382
  | 'addContractClasses'
890
1383
  | 'deleteContractClasses'
891
1384
  | 'addContractInstances'
@@ -893,31 +1386,26 @@ class ArchiverStoreHelper
893
1386
  | 'addContractInstanceUpdates'
894
1387
  | 'deleteContractInstanceUpdates'
895
1388
  | 'addFunctions'
1389
+ | 'backupTo'
1390
+ | 'close'
1391
+ | 'transactionAsync'
1392
+ | 'addBlocks'
896
1393
  >
897
1394
  {
898
1395
  #log = createLogger('archiver:block-helper');
899
1396
 
900
- constructor(private readonly store: ArchiverDataStore) {}
901
-
902
- // TODO(#10007): Remove this method
903
- addContractClasses(
904
- contractClasses: ContractClassPublic[],
905
- bytecodeCommitments: Fr[],
906
- blockNum: number,
907
- ): Promise<boolean> {
908
- return this.store.addContractClasses(contractClasses, bytecodeCommitments, blockNum);
909
- }
1397
+ constructor(protected readonly store: ArchiverDataStore) {}
910
1398
 
911
1399
  /**
912
- * Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
1400
+ * Extracts and stores contract classes out of ContractClassPublished events emitted by the class registry contract.
913
1401
  * @param allLogs - All logs emitted in a bunch of blocks.
914
1402
  */
915
- async #updateRegisteredContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
916
- const contractClassRegisteredEvents = allLogs
917
- .filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log))
918
- .map(log => ContractClassRegisteredEvent.fromLog(log));
1403
+ async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
1404
+ const contractClassPublishedEvents = allLogs
1405
+ .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
1406
+ .map(log => ContractClassPublishedEvent.fromLog(log));
919
1407
 
920
- const contractClasses = await Promise.all(contractClassRegisteredEvents.map(e => e.toContractClassPublic()));
1408
+ const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic()));
921
1409
  if (contractClasses.length > 0) {
922
1410
  contractClasses.forEach(c => this.#log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
923
1411
  if (operation == Operation.Store) {
@@ -934,13 +1422,13 @@ class ArchiverStoreHelper
934
1422
  }
935
1423
 
936
1424
  /**
937
- * Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
1425
+ * Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
938
1426
  * @param allLogs - All logs emitted in a bunch of blocks.
939
1427
  */
940
1428
  async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: number, operation: Operation) {
941
1429
  const contractInstances = allLogs
942
- .filter(log => ContractInstanceDeployedEvent.isContractInstanceDeployedEvent(log))
943
- .map(log => ContractInstanceDeployedEvent.fromLog(log))
1430
+ .filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
1431
+ .map(log => ContractInstancePublishedEvent.fromLog(log))
944
1432
  .map(e => e.toContractInstance());
945
1433
  if (contractInstances.length > 0) {
946
1434
  contractInstances.forEach(c =>
@@ -956,10 +1444,12 @@ class ArchiverStoreHelper
956
1444
  }
957
1445
 
958
1446
  /**
959
- * Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
1447
+ * Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
960
1448
  * @param allLogs - All logs emitted in a bunch of blocks.
1449
+ * @param timestamp - Timestamp at which the updates were scheduled.
1450
+ * @param operation - The operation to perform on the contract instance updates (Store or Delete).
961
1451
  */
962
- async #updateUpdatedContractInstances(allLogs: PublicLog[], blockNum: number, operation: Operation) {
1452
+ async #updateUpdatedContractInstances(allLogs: PublicLog[], timestamp: UInt64, operation: Operation) {
963
1453
  const contractUpdates = allLogs
964
1454
  .filter(log => ContractInstanceUpdatedEvent.isContractInstanceUpdatedEvent(log))
965
1455
  .map(log => ContractInstanceUpdatedEvent.fromLog(log))
@@ -970,16 +1460,16 @@ class ArchiverStoreHelper
970
1460
  this.#log.verbose(`${Operation[operation]} contract instance update at ${c.address.toString()}`),
971
1461
  );
972
1462
  if (operation == Operation.Store) {
973
- return await this.store.addContractInstanceUpdates(contractUpdates, blockNum);
1463
+ return await this.store.addContractInstanceUpdates(contractUpdates, timestamp);
974
1464
  } else if (operation == Operation.Delete) {
975
- return await this.store.deleteContractInstanceUpdates(contractUpdates, blockNum);
1465
+ return await this.store.deleteContractInstanceUpdates(contractUpdates, timestamp);
976
1466
  }
977
1467
  }
978
1468
  return true;
979
1469
  }
980
1470
 
981
1471
  /**
982
- * Stores the functions that was broadcasted individually
1472
+ * Stores the functions that were broadcasted individually
983
1473
  *
984
1474
  * @dev Beware that there is not a delete variant of this, since they are added to contract classes
985
1475
  * and will be deleted as part of the class if needed.
@@ -989,17 +1479,17 @@ class ArchiverStoreHelper
989
1479
  * @returns
990
1480
  */
991
1481
  async #storeBroadcastedIndividualFunctions(allLogs: ContractClassLog[], _blockNum: number) {
992
- // Filter out private and unconstrained function broadcast events
1482
+ // Filter out private and utility function broadcast events
993
1483
  const privateFnEvents = allLogs
994
1484
  .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
995
1485
  .map(log => PrivateFunctionBroadcastedEvent.fromLog(log));
996
- const unconstrainedFnEvents = allLogs
997
- .filter(log => UnconstrainedFunctionBroadcastedEvent.isUnconstrainedFunctionBroadcastedEvent(log))
998
- .map(log => UnconstrainedFunctionBroadcastedEvent.fromLog(log));
1486
+ const utilityFnEvents = allLogs
1487
+ .filter(log => UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log))
1488
+ .map(log => UtilityFunctionBroadcastedEvent.fromLog(log));
999
1489
 
1000
1490
  // Group all events by contract class id
1001
1491
  for (const [classIdString, classEvents] of Object.entries(
1002
- groupBy([...privateFnEvents, ...unconstrainedFnEvents], e => e.contractClassId.toString()),
1492
+ groupBy([...privateFnEvents, ...utilityFnEvents], e => e.contractClassId.toString()),
1003
1493
  )) {
1004
1494
  const contractClassId = Fr.fromHexString(classIdString);
1005
1495
  const contractClass = await this.getContractClass(contractClassId);
@@ -1008,27 +1498,27 @@ class ArchiverStoreHelper
1008
1498
  continue;
1009
1499
  }
1010
1500
 
1011
- // Split private and unconstrained functions, and filter out invalid ones
1501
+ // Split private and utility functions, and filter out invalid ones
1012
1502
  const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
1013
1503
  const privateFns = allFns.filter(
1014
- (fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'unconstrainedFunctionsArtifactTreeRoot' in fn,
1504
+ (fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'utilityFunctionsTreeRoot' in fn,
1015
1505
  );
1016
- const unconstrainedFns = allFns.filter(
1017
- (fn): fn is UnconstrainedFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
1506
+ const utilityFns = allFns.filter(
1507
+ (fn): fn is UtilityFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
1018
1508
  );
1019
1509
 
1020
1510
  const privateFunctionsWithValidity = await Promise.all(
1021
1511
  privateFns.map(async fn => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })),
1022
1512
  );
1023
1513
  const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
1024
- const unconstrainedFunctionsWithValidity = await Promise.all(
1025
- unconstrainedFns.map(async fn => ({
1514
+ const utilityFunctionsWithValidity = await Promise.all(
1515
+ utilityFns.map(async fn => ({
1026
1516
  fn,
1027
- valid: await isValidUnconstrainedFunctionMembershipProof(fn, contractClass),
1517
+ valid: await isValidUtilityFunctionMembershipProof(fn, contractClass),
1028
1518
  })),
1029
1519
  );
1030
- const validUnconstrainedFns = unconstrainedFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
1031
- const validFnCount = validPrivateFns.length + validUnconstrainedFns.length;
1520
+ const validUtilityFns = utilityFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
1521
+ const validFnCount = validPrivateFns.length + validUtilityFns.length;
1032
1522
  if (validFnCount !== allFns.length) {
1033
1523
  this.#log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
1034
1524
  }
@@ -1037,85 +1527,120 @@ class ArchiverStoreHelper
1037
1527
  if (validFnCount > 0) {
1038
1528
  this.#log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
1039
1529
  }
1040
- return await this.store.addFunctions(contractClassId, validPrivateFns, validUnconstrainedFns);
1530
+ return await this.store.addFunctions(contractClassId, validPrivateFns, validUtilityFns);
1041
1531
  }
1042
1532
  return true;
1043
1533
  }
1044
1534
 
1045
- async addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean> {
1046
- const opResults = await Promise.all([
1047
- this.store.addLogs(blocks.map(block => block.data)),
1048
- // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1049
- ...blocks.map(async block => {
1050
- const contractClassLogs = block.data.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1051
- // ContractInstanceDeployed event logs are broadcast in privateLogs.
1052
- const privateLogs = block.data.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1053
- const publicLogs = block.data.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1054
- return (
1055
- await Promise.all([
1056
- this.#updateRegisteredContractClasses(contractClassLogs, block.data.number, Operation.Store),
1057
- this.#updateDeployedContractInstances(privateLogs, block.data.number, Operation.Store),
1058
- this.#updateUpdatedContractInstances(publicLogs, block.data.number, Operation.Store),
1059
- this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.data.number),
1060
- ])
1061
- ).every(Boolean);
1062
- }),
1063
- this.store.addNullifiers(blocks.map(block => block.data)),
1064
- this.store.addBlocks(blocks),
1065
- ]);
1066
-
1067
- return opResults.every(Boolean);
1535
+ public addBlocks(blocks: PublishedL2Block[], pendingChainValidationStatus?: ValidateBlockResult): Promise<boolean> {
1536
+ // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1537
+ // or if the previous block is not in the store.
1538
+ return this.store.transactionAsync(async () => {
1539
+ await this.store.addBlocks(blocks);
1540
+
1541
+ const opResults = await Promise.all([
1542
+ // Update the pending chain validation status if provided
1543
+ pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
1544
+ // Add any logs emitted during the retrieved blocks
1545
+ this.store.addLogs(blocks.map(block => block.block)),
1546
+ // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1547
+ ...blocks.map(async block => {
1548
+ const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1549
+ // ContractInstancePublished event logs are broadcast in privateLogs.
1550
+ const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1551
+ const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1552
+ return (
1553
+ await Promise.all([
1554
+ this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Store),
1555
+ this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
1556
+ this.#updateUpdatedContractInstances(
1557
+ publicLogs,
1558
+ block.block.header.globalVariables.timestamp,
1559
+ Operation.Store,
1560
+ ),
1561
+ this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.block.number),
1562
+ ])
1563
+ ).every(Boolean);
1564
+ }),
1565
+ ]);
1566
+
1567
+ return opResults.every(Boolean);
1568
+ });
1068
1569
  }
1069
1570
 
1070
- async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
1571
+ public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
1071
1572
  const last = await this.getSynchedL2BlockNumber();
1072
1573
  if (from != last) {
1073
- throw new Error(`Can only remove from the tip`);
1574
+ throw new Error(`Cannot unwind blocks from block ${from} when the last block is ${last}`);
1575
+ }
1576
+ if (blocksToUnwind <= 0) {
1577
+ throw new Error(`Cannot unwind ${blocksToUnwind} blocks`);
1074
1578
  }
1075
1579
 
1076
1580
  // from - blocksToUnwind = the new head, so + 1 for what we need to remove
1077
- const blocks = await this.getBlocks(from - blocksToUnwind + 1, blocksToUnwind);
1581
+ const blocks = await this.getPublishedBlocks(from - blocksToUnwind + 1, blocksToUnwind);
1078
1582
 
1079
1583
  const opResults = await Promise.all([
1584
+ // Prune rolls back to the last proven block, which is by definition valid
1585
+ this.store.setPendingChainValidationStatus({ valid: true }),
1080
1586
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1081
1587
  ...blocks.map(async block => {
1082
- const contractClassLogs = block.data.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1083
- // ContractInstanceDeployed event logs are broadcast in privateLogs.
1084
- const privateLogs = block.data.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1085
- const publicLogs = block.data.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1588
+ const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1589
+ // ContractInstancePublished event logs are broadcast in privateLogs.
1590
+ const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1591
+ const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1086
1592
 
1087
1593
  return (
1088
1594
  await Promise.all([
1089
- this.#updateRegisteredContractClasses(contractClassLogs, block.data.number, Operation.Delete),
1090
- this.#updateDeployedContractInstances(privateLogs, block.data.number, Operation.Delete),
1091
- this.#updateUpdatedContractInstances(publicLogs, block.data.number, Operation.Delete),
1595
+ this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Delete),
1596
+ this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Delete),
1597
+ this.#updateUpdatedContractInstances(
1598
+ publicLogs,
1599
+ block.block.header.globalVariables.timestamp,
1600
+ Operation.Delete,
1601
+ ),
1092
1602
  ])
1093
1603
  ).every(Boolean);
1094
1604
  }),
1095
1605
 
1096
- this.store.deleteLogs(blocks.map(b => b.data)),
1606
+ this.store.deleteLogs(blocks.map(b => b.block)),
1097
1607
  this.store.unwindBlocks(from, blocksToUnwind),
1098
1608
  ]);
1099
1609
 
1100
1610
  return opResults.every(Boolean);
1101
1611
  }
1102
1612
 
1103
- getBlocks(from: number, limit: number): Promise<L1Published<L2Block>[]> {
1104
- return this.store.getBlocks(from, limit);
1613
+ getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
1614
+ return this.store.getPublishedBlocks(from, limit);
1615
+ }
1616
+ getPublishedBlock(number: number): Promise<PublishedL2Block | undefined> {
1617
+ return this.store.getPublishedBlock(number);
1618
+ }
1619
+ getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1620
+ return this.store.getPublishedBlockByHash(blockHash);
1621
+ }
1622
+ getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1623
+ return this.store.getPublishedBlockByArchive(archive);
1105
1624
  }
1106
1625
  getBlockHeaders(from: number, limit: number): Promise<BlockHeader[]> {
1107
1626
  return this.store.getBlockHeaders(from, limit);
1108
1627
  }
1109
- getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
1628
+ getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
1629
+ return this.store.getBlockHeaderByHash(blockHash);
1630
+ }
1631
+ getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1632
+ return this.store.getBlockHeaderByArchive(archive);
1633
+ }
1634
+ getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
1110
1635
  return this.store.getTxEffect(txHash);
1111
1636
  }
1112
1637
  getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
1113
1638
  return this.store.getSettledTxReceipt(txHash);
1114
1639
  }
1115
- addL1ToL2Messages(messages: DataRetrieval<InboxLeaf>): Promise<boolean> {
1640
+ addL1ToL2Messages(messages: InboxMessage[]): Promise<void> {
1116
1641
  return this.store.addL1ToL2Messages(messages);
1117
1642
  }
1118
- getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
1643
+ getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
1119
1644
  return this.store.getL1ToL2Messages(blockNumber);
1120
1645
  }
1121
1646
  getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise<bigint | undefined> {
@@ -1124,11 +1649,8 @@ class ArchiverStoreHelper
1124
1649
  getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
1125
1650
  return this.store.getPrivateLogs(from, limit);
1126
1651
  }
1127
- getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
1128
- return this.store.getLogsByTags(tags);
1129
- }
1130
- findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
1131
- return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
1652
+ getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
1653
+ return this.store.getLogsByTags(tags, logsPerTag);
1132
1654
  }
1133
1655
  getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
1134
1656
  return this.store.getPublicLogs(filter);
@@ -1148,8 +1670,8 @@ class ArchiverStoreHelper
1148
1670
  setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
1149
1671
  return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
1150
1672
  }
1151
- setMessageSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
1152
- return this.store.setMessageSynchedL1BlockNumber(l1BlockNumber);
1673
+ setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void> {
1674
+ return this.store.setMessageSynchedL1Block(l1Block);
1153
1675
  }
1154
1676
  getSynchPoint(): Promise<ArchiverL1SynchPoint> {
1155
1677
  return this.store.getSynchPoint();
@@ -1160,22 +1682,41 @@ class ArchiverStoreHelper
1160
1682
  getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined> {
1161
1683
  return this.store.getBytecodeCommitment(contractClassId);
1162
1684
  }
1163
- getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
1164
- return this.store.getContractInstance(address);
1685
+ getContractInstance(address: AztecAddress, timestamp: UInt64): Promise<ContractInstanceWithAddress | undefined> {
1686
+ return this.store.getContractInstance(address, timestamp);
1165
1687
  }
1166
1688
  getContractClassIds(): Promise<Fr[]> {
1167
1689
  return this.store.getContractClassIds();
1168
1690
  }
1169
- registerContractFunctionSignatures(address: AztecAddress, signatures: string[]): Promise<void> {
1170
- return this.store.registerContractFunctionSignatures(address, signatures);
1691
+ registerContractFunctionSignatures(signatures: string[]): Promise<void> {
1692
+ return this.store.registerContractFunctionSignatures(signatures);
1171
1693
  }
1172
- getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
1173
- return this.store.getContractFunctionName(address, selector);
1694
+ getDebugFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
1695
+ return this.store.getDebugFunctionName(address, selector);
1174
1696
  }
1175
1697
  getTotalL1ToL2MessageCount(): Promise<bigint> {
1176
1698
  return this.store.getTotalL1ToL2MessageCount();
1177
1699
  }
1178
- estimateSize(): Promise<{ mappingSize: number; actualSize: number; numItems: number }> {
1700
+ estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }> {
1179
1701
  return this.store.estimateSize();
1180
1702
  }
1703
+ rollbackL1ToL2MessagesToL2Block(targetBlockNumber: number): Promise<void> {
1704
+ return this.store.rollbackL1ToL2MessagesToL2Block(targetBlockNumber);
1705
+ }
1706
+ iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
1707
+ return this.store.iterateL1ToL2Messages(range);
1708
+ }
1709
+ removeL1ToL2Messages(startIndex: bigint): Promise<void> {
1710
+ return this.store.removeL1ToL2Messages(startIndex);
1711
+ }
1712
+ getLastL1ToL2Message(): Promise<InboxMessage | undefined> {
1713
+ return this.store.getLastL1ToL2Message();
1714
+ }
1715
+ getPendingChainValidationStatus(): Promise<ValidateBlockResult | undefined> {
1716
+ return this.store.getPendingChainValidationStatus();
1717
+ }
1718
+ setPendingChainValidationStatus(status: ValidateBlockResult | undefined): Promise<void> {
1719
+ this.#log.debug(`Setting pending chain validation status to valid ${status?.valid}`, status);
1720
+ return this.store.setPendingChainValidationStatus(status);
1721
+ }
1181
1722
  }