@aztec/archiver 0.0.1-commit.9593d84 → 0.0.1-commit.96bb3f7

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 (128) hide show
  1. package/README.md +14 -14
  2. package/dest/archiver/archiver.d.ts +105 -85
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +949 -255
  5. package/dest/archiver/archiver_store.d.ts +109 -49
  6. package/dest/archiver/archiver_store.d.ts.map +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
  8. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  9. package/dest/archiver/archiver_store_test_suite.js +1871 -390
  10. package/dest/archiver/config.d.ts +5 -4
  11. package/dest/archiver/config.d.ts.map +1 -1
  12. package/dest/archiver/config.js +12 -5
  13. package/dest/archiver/errors.d.ts +25 -1
  14. package/dest/archiver/errors.d.ts.map +1 -1
  15. package/dest/archiver/errors.js +37 -0
  16. package/dest/archiver/index.d.ts +2 -2
  17. package/dest/archiver/index.d.ts.map +1 -1
  18. package/dest/archiver/instrumentation.d.ts +3 -1
  19. package/dest/archiver/instrumentation.d.ts.map +1 -1
  20. package/dest/archiver/instrumentation.js +22 -59
  21. package/dest/archiver/kv_archiver_store/block_store.d.ts +60 -20
  22. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  23. package/dest/archiver/kv_archiver_store/block_store.js +345 -89
  24. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
  25. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  26. package/dest/archiver/kv_archiver_store/contract_class_store.js +1 -1
  27. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +2 -2
  28. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  29. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +49 -58
  30. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  31. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +68 -48
  32. package/dest/archiver/kv_archiver_store/log_store.d.ts +12 -16
  33. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  34. package/dest/archiver/kv_archiver_store/log_store.js +149 -84
  35. package/dest/archiver/kv_archiver_store/message_store.d.ts +6 -5
  36. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  37. package/dest/archiver/kv_archiver_store/message_store.js +15 -14
  38. package/dest/archiver/l1/bin/retrieve-calldata.d.ts +3 -0
  39. package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +1 -0
  40. package/dest/archiver/l1/bin/retrieve-calldata.js +149 -0
  41. package/dest/archiver/l1/calldata_retriever.d.ts +112 -0
  42. package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -0
  43. package/dest/archiver/l1/calldata_retriever.js +471 -0
  44. package/dest/archiver/l1/data_retrieval.d.ts +90 -0
  45. package/dest/archiver/l1/data_retrieval.d.ts.map +1 -0
  46. package/dest/archiver/{data_retrieval.js → l1/data_retrieval.js} +52 -107
  47. package/dest/archiver/l1/debug_tx.d.ts +19 -0
  48. package/dest/archiver/l1/debug_tx.d.ts.map +1 -0
  49. package/dest/archiver/l1/debug_tx.js +73 -0
  50. package/dest/archiver/l1/spire_proposer.d.ts +70 -0
  51. package/dest/archiver/l1/spire_proposer.d.ts.map +1 -0
  52. package/dest/archiver/l1/spire_proposer.js +157 -0
  53. package/dest/archiver/l1/trace_tx.d.ts +97 -0
  54. package/dest/archiver/l1/trace_tx.d.ts.map +1 -0
  55. package/dest/archiver/l1/trace_tx.js +91 -0
  56. package/dest/archiver/l1/types.d.ts +12 -0
  57. package/dest/archiver/l1/types.d.ts.map +1 -0
  58. package/dest/archiver/l1/types.js +3 -0
  59. package/dest/archiver/l1/validate_trace.d.ts +29 -0
  60. package/dest/archiver/l1/validate_trace.d.ts.map +1 -0
  61. package/dest/archiver/l1/validate_trace.js +150 -0
  62. package/dest/archiver/structs/inbox_message.d.ts +4 -4
  63. package/dest/archiver/structs/inbox_message.d.ts.map +1 -1
  64. package/dest/archiver/structs/inbox_message.js +6 -5
  65. package/dest/archiver/structs/published.d.ts +1 -2
  66. package/dest/archiver/structs/published.d.ts.map +1 -1
  67. package/dest/archiver/validation.d.ts +4 -4
  68. package/dest/archiver/validation.d.ts.map +1 -1
  69. package/dest/archiver/validation.js +1 -1
  70. package/dest/factory.d.ts +2 -2
  71. package/dest/factory.d.ts.map +1 -1
  72. package/dest/factory.js +4 -3
  73. package/dest/index.d.ts +2 -2
  74. package/dest/index.d.ts.map +1 -1
  75. package/dest/index.js +1 -1
  76. package/dest/test/mock_archiver.d.ts +16 -8
  77. package/dest/test/mock_archiver.d.ts.map +1 -1
  78. package/dest/test/mock_archiver.js +19 -14
  79. package/dest/test/mock_l1_to_l2_message_source.d.ts +7 -6
  80. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  81. package/dest/test/mock_l1_to_l2_message_source.js +21 -11
  82. package/dest/test/mock_l2_block_source.d.ts +23 -8
  83. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  84. package/dest/test/mock_l2_block_source.js +95 -35
  85. package/dest/test/mock_structs.d.ts +3 -2
  86. package/dest/test/mock_structs.d.ts.map +1 -1
  87. package/dest/test/mock_structs.js +9 -8
  88. package/package.json +17 -16
  89. package/src/archiver/archiver.ts +751 -344
  90. package/src/archiver/archiver_store.ts +123 -48
  91. package/src/archiver/archiver_store_test_suite.ts +1943 -365
  92. package/src/archiver/config.ts +17 -12
  93. package/src/archiver/errors.ts +64 -0
  94. package/src/archiver/index.ts +1 -1
  95. package/src/archiver/instrumentation.ts +24 -59
  96. package/src/archiver/kv_archiver_store/block_store.ts +475 -106
  97. package/src/archiver/kv_archiver_store/contract_class_store.ts +1 -1
  98. package/src/archiver/kv_archiver_store/contract_instance_store.ts +1 -1
  99. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +92 -72
  100. package/src/archiver/kv_archiver_store/log_store.ts +209 -99
  101. package/src/archiver/kv_archiver_store/message_store.ts +21 -18
  102. package/src/archiver/l1/README.md +98 -0
  103. package/src/archiver/l1/bin/retrieve-calldata.ts +182 -0
  104. package/src/archiver/l1/calldata_retriever.ts +641 -0
  105. package/src/archiver/{data_retrieval.ts → l1/data_retrieval.ts} +101 -165
  106. package/src/archiver/l1/debug_tx.ts +99 -0
  107. package/src/archiver/l1/spire_proposer.ts +160 -0
  108. package/src/archiver/l1/trace_tx.ts +128 -0
  109. package/src/archiver/l1/types.ts +13 -0
  110. package/src/archiver/l1/validate_trace.ts +211 -0
  111. package/src/archiver/structs/inbox_message.ts +8 -8
  112. package/src/archiver/structs/published.ts +0 -1
  113. package/src/archiver/validation.ts +6 -6
  114. package/src/factory.ts +4 -3
  115. package/src/index.ts +1 -1
  116. package/src/test/fixtures/debug_traceTransaction-multicall3.json +88 -0
  117. package/src/test/fixtures/debug_traceTransaction-multiplePropose.json +153 -0
  118. package/src/test/fixtures/debug_traceTransaction-proxied.json +122 -0
  119. package/src/test/fixtures/trace_transaction-multicall3.json +65 -0
  120. package/src/test/fixtures/trace_transaction-multiplePropose.json +319 -0
  121. package/src/test/fixtures/trace_transaction-proxied.json +128 -0
  122. package/src/test/fixtures/trace_transaction-randomRevert.json +216 -0
  123. package/src/test/mock_archiver.ts +22 -16
  124. package/src/test/mock_l1_to_l2_message_source.ts +18 -11
  125. package/src/test/mock_l2_block_source.ts +115 -37
  126. package/src/test/mock_structs.ts +10 -9
  127. package/dest/archiver/data_retrieval.d.ts +0 -79
  128. package/dest/archiver/data_retrieval.d.ts.map +0 -1
@@ -1,24 +1,23 @@
1
- import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
1
+ import type { BlobClientInterface } from '@aztec/blob-client/client';
2
+ import { GENESIS_BLOCK_HEADER_HASH, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
3
  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';
4
+ import { createEthereumChain } from '@aztec/ethereum/chain';
5
+ import { BlockTagTooOldError, InboxContract, RollupContract } from '@aztec/ethereum/contracts';
6
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
7
+ import type { L1BlockId } from '@aztec/ethereum/l1-types';
8
+ import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
11
9
  import { maxBigint } from '@aztec/foundation/bigint';
12
- import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
10
+ import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
13
11
  import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
14
12
  import { merge, pick } from '@aztec/foundation/collection';
13
+ import { Fr } from '@aztec/foundation/curves/bn254';
15
14
  import type { EthAddress } from '@aztec/foundation/eth-address';
16
- import { Fr } from '@aztec/foundation/fields';
17
15
  import { type Logger, createLogger } from '@aztec/foundation/log';
18
16
  import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
19
17
  import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
20
18
  import { count } from '@aztec/foundation/string';
21
19
  import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
20
+ import { isDefined } from '@aztec/foundation/types';
22
21
  import type { CustomRange } from '@aztec/kv-store';
23
22
  import { RollupAbi } from '@aztec/l1-artifacts';
24
23
  import {
@@ -34,14 +33,19 @@ import type { FunctionSelector } from '@aztec/stdlib/abi';
34
33
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
35
34
  import {
36
35
  type ArchiverEmitter,
36
+ type CheckpointId,
37
+ CheckpointedL2Block,
38
+ CommitteeAttestation,
39
+ GENESIS_CHECKPOINT_HEADER_HASH,
37
40
  L2Block,
38
- type L2BlockId,
41
+ L2BlockNew,
42
+ type L2BlockSink,
39
43
  type L2BlockSource,
40
44
  L2BlockSourceEvents,
41
45
  type L2Tips,
42
46
  PublishedL2Block,
43
47
  } from '@aztec/stdlib/block';
44
- import type { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
48
+ import { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
45
49
  import {
46
50
  type ContractClassPublic,
47
51
  type ContractDataSource,
@@ -62,8 +66,16 @@ import {
62
66
  } from '@aztec/stdlib/epoch-helpers';
63
67
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
64
68
  import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
65
- import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
66
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
69
+ import {
70
+ ContractClassLog,
71
+ type LogFilter,
72
+ type PrivateLog,
73
+ type PublicLog,
74
+ type SiloedTag,
75
+ Tag,
76
+ TxScopedL2Log,
77
+ } from '@aztec/stdlib/logs';
78
+ import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
67
79
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
68
80
  import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
69
81
  import type { UInt64 } from '@aztec/stdlib/types';
@@ -71,6 +83,7 @@ import {
71
83
  type TelemetryClient,
72
84
  type Traceable,
73
85
  type Tracer,
86
+ execInSpan,
74
87
  getTelemetryClient,
75
88
  trackSpan,
76
89
  } from '@aztec/telemetry-client';
@@ -81,25 +94,34 @@ import { type GetContractReturnType, type Hex, createPublicClient, fallback, htt
81
94
 
82
95
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
83
96
  import type { ArchiverConfig } from './config.js';
97
+ import { InitialCheckpointNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
98
+ import { ArchiverInstrumentation } from './instrumentation.js';
99
+ import type { CheckpointData } from './kv_archiver_store/block_store.js';
84
100
  import {
85
101
  retrieveCheckpointsFromRollup,
86
102
  retrieveL1ToL2Message,
87
103
  retrieveL1ToL2Messages,
88
104
  retrievedToPublishedCheckpoint,
89
- } from './data_retrieval.js';
90
- import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
91
- import { ArchiverInstrumentation } from './instrumentation.js';
105
+ } from './l1/data_retrieval.js';
106
+ import { validateAndLogTraceAvailability } from './l1/validate_trace.js';
92
107
  import type { InboxMessage } from './structs/inbox_message.js';
93
- import { type ValidateBlockResult, validateCheckpointAttestations } from './validation.js';
108
+ import { type ValidateCheckpointResult, validateCheckpointAttestations } from './validation.js';
94
109
 
95
110
  /**
96
111
  * Helper interface to combine all sources this archiver implementation provides.
97
112
  */
98
113
  export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
99
114
 
115
+ /** Request to add a block to the archiver, queued for processing by the sync loop. */
116
+ type AddBlockRequest = {
117
+ block: L2BlockNew;
118
+ resolve: () => void;
119
+ reject: (err: Error) => void;
120
+ };
121
+
100
122
  export type ArchiverDeps = {
101
123
  telemetry?: TelemetryClient;
102
- blobSinkClient: BlobSinkClientInterface;
124
+ blobClient: BlobClientInterface;
103
125
  epochCache?: EpochCache;
104
126
  dateProvider?: DateProvider;
105
127
  };
@@ -108,17 +130,18 @@ function mapArchiverConfig(config: Partial<ArchiverConfig>) {
108
130
  return {
109
131
  pollingIntervalMs: config.archiverPollingIntervalMS,
110
132
  batchSize: config.archiverBatchSize,
111
- skipValidateBlockAttestations: config.skipValidateBlockAttestations,
133
+ skipValidateCheckpointAttestations: config.skipValidateCheckpointAttestations,
112
134
  maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds,
135
+ ethereumAllowNoDebugHosts: config.ethereumAllowNoDebugHosts,
113
136
  };
114
137
  }
115
138
 
116
139
  type RollupStatus = {
117
- provenCheckpointNumber: number;
140
+ provenCheckpointNumber: CheckpointNumber;
118
141
  provenArchive: Hex;
119
- pendingCheckpointNumber: number;
142
+ pendingCheckpointNumber: CheckpointNumber;
120
143
  pendingArchive: Hex;
121
- validationResult: ValidateBlockResult | undefined;
144
+ validationResult: ValidateCheckpointResult | undefined;
122
145
  lastRetrievedCheckpoint?: PublishedCheckpoint;
123
146
  lastL1BlockWithCheckpoint?: bigint;
124
147
  };
@@ -128,7 +151,10 @@ type RollupStatus = {
128
151
  * Responsible for handling robust L1 polling so that other components do not need to
129
152
  * concern themselves with it.
130
153
  */
131
- export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implements ArchiveSource, Traceable {
154
+ export class Archiver
155
+ extends (EventEmitter as new () => ArchiverEmitter)
156
+ implements ArchiveSource, L2BlockSink, Traceable
157
+ {
132
158
  /** A loop in which we will be continually fetching new checkpoints. */
133
159
  private runningPromise: RunningPromise;
134
160
 
@@ -142,11 +168,15 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
142
168
  private initialSyncComplete: boolean = false;
143
169
  private initialSyncPromise: PromiseWithResolvers<void>;
144
170
 
171
+ /** Queue of blocks to be added to the store, processed by the sync loop. */
172
+ private blockQueue: AddBlockRequest[] = [];
173
+
145
174
  public readonly tracer: Tracer;
146
175
 
147
176
  /**
148
177
  * Creates a new instance of the Archiver.
149
178
  * @param publicClient - A client for interacting with the Ethereum node.
179
+ * @param debugClient - A client for interacting with the Ethereum node for debug/trace methods.
150
180
  * @param rollupAddress - Ethereum address of the rollup contract.
151
181
  * @param inboxAddress - Ethereum address of the inbox contract.
152
182
  * @param registryAddress - Ethereum address of the registry contract.
@@ -156,15 +186,20 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
156
186
  */
157
187
  constructor(
158
188
  private readonly publicClient: ViemPublicClient,
159
- private readonly l1Addresses: { rollupAddress: EthAddress; inboxAddress: EthAddress; registryAddress: EthAddress },
189
+ private readonly debugClient: ViemPublicDebugClient,
190
+ private readonly l1Addresses: Pick<
191
+ L1ContractAddresses,
192
+ 'rollupAddress' | 'inboxAddress' | 'registryAddress' | 'governanceProposerAddress' | 'slashFactoryAddress'
193
+ > & { slashingProposerAddress: EthAddress },
160
194
  readonly dataStore: ArchiverDataStore,
161
195
  private config: {
162
196
  pollingIntervalMs: number;
163
197
  batchSize: number;
164
- skipValidateBlockAttestations?: boolean;
198
+ skipValidateCheckpointAttestations?: boolean;
165
199
  maxAllowedEthClientDriftSeconds: number;
200
+ ethereumAllowNoDebugHosts?: boolean;
166
201
  },
167
- private readonly blobSinkClient: BlobSinkClientInterface,
202
+ private readonly blobClient: BlobClientInterface,
168
203
  private readonly epochCache: EpochCache,
169
204
  private readonly dateProvider: DateProvider,
170
205
  private readonly instrumentation: ArchiverInstrumentation,
@@ -206,18 +241,28 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
206
241
  const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
207
242
  const publicClient = createPublicClient({
208
243
  chain: chain.chainInfo,
209
- transport: fallback(config.l1RpcUrls.map(url => http(url))),
244
+ transport: fallback(config.l1RpcUrls.map(url => http(url, { batch: false }))),
210
245
  pollingInterval: config.viemPollingIntervalMS,
211
246
  });
212
247
 
248
+ // Create debug client using debug RPC URLs if available, otherwise fall back to regular RPC URLs
249
+ const debugRpcUrls = config.l1DebugRpcUrls.length > 0 ? config.l1DebugRpcUrls : config.l1RpcUrls;
250
+ const debugClient = createPublicClient({
251
+ chain: chain.chainInfo,
252
+ transport: fallback(debugRpcUrls.map(url => http(url, { batch: false }))),
253
+ pollingInterval: config.viemPollingIntervalMS,
254
+ }) as ViemPublicDebugClient;
255
+
213
256
  const rollup = new RollupContract(publicClient, config.l1Contracts.rollupAddress);
214
257
 
215
- const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, genesisArchiveRoot] = await Promise.all([
216
- rollup.getL1StartBlock(),
217
- rollup.getL1GenesisTime(),
218
- rollup.getProofSubmissionEpochs(),
219
- rollup.getGenesisArchiveTreeRoot(),
220
- ] as const);
258
+ const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, genesisArchiveRoot, slashingProposerAddress] =
259
+ await Promise.all([
260
+ rollup.getL1StartBlock(),
261
+ rollup.getL1GenesisTime(),
262
+ rollup.getProofSubmissionEpochs(),
263
+ rollup.getGenesisArchiveTreeRoot(),
264
+ rollup.getSlashingProposerAddress(),
265
+ ] as const);
221
266
 
222
267
  const l1StartBlockHash = await publicClient
223
268
  .getBlock({ blockNumber: l1StartBlock, includeTransactions: false })
@@ -233,11 +278,16 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
233
278
  slotDuration,
234
279
  ethereumSlotDuration,
235
280
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
236
- genesisArchiveRoot: Fr.fromHexString(genesisArchiveRoot),
281
+ genesisArchiveRoot: Fr.fromString(genesisArchiveRoot.toString()),
237
282
  };
238
283
 
239
284
  const opts = merge(
240
- { pollingIntervalMs: 10_000, batchSize: 100, maxAllowedEthClientDriftSeconds: 300 },
285
+ {
286
+ pollingIntervalMs: 10_000,
287
+ batchSize: 100,
288
+ maxAllowedEthClientDriftSeconds: 300,
289
+ ethereumAllowNoDebugHosts: false,
290
+ },
241
291
  mapArchiverConfig(config),
242
292
  );
243
293
 
@@ -246,10 +296,11 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
246
296
 
247
297
  const archiver = new Archiver(
248
298
  publicClient,
249
- config.l1Contracts,
299
+ debugClient,
300
+ { ...config.l1Contracts, slashingProposerAddress },
250
301
  archiverStore,
251
302
  opts,
252
- deps.blobSinkClient,
303
+ deps.blobClient,
253
304
  epochCache,
254
305
  deps.dateProvider ?? new DateProvider(),
255
306
  await ArchiverInstrumentation.new(telemetry, () => archiverStore.estimateSize()),
@@ -273,16 +324,17 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
273
324
  throw new Error('Archiver is already running');
274
325
  }
275
326
 
276
- await this.blobSinkClient.testSources();
327
+ await this.blobClient.testSources();
277
328
  await this.testEthereumNodeSynced();
329
+ await validateAndLogTraceAvailability(this.debugClient, this.config.ethereumAllowNoDebugHosts ?? false);
278
330
 
279
331
  // Log initial state for the archiver
280
332
  const { l1StartBlock } = this.l1constants;
281
333
  const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
282
- const currentL2Block = await this.getBlockNumber();
334
+ const currentL2Checkpoint = await this.getSynchedCheckpointNumber();
283
335
  this.log.info(
284
- `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo} and L2 block ${currentL2Block}`,
285
- { blocksSynchedTo, messagesSynchedTo, currentL2Block },
336
+ `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo} and L2 checkpoint ${currentL2Checkpoint}`,
337
+ { blocksSynchedTo, messagesSynchedTo, currentL2Checkpoint },
286
338
  );
287
339
 
288
340
  // Start sync loop, and return the wait for initial sync if we are asked to block until synced
@@ -296,6 +348,51 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
296
348
  return this.runningPromise.trigger();
297
349
  }
298
350
 
351
+ /**
352
+ * Queues a block to be added to the archiver store and triggers processing.
353
+ * The block will be processed by the sync loop.
354
+ * Implements the L2BlockSink interface.
355
+ * @param block - The L2 block to add.
356
+ * @returns A promise that resolves when the block has been added to the store, or rejects on error.
357
+ */
358
+ public addBlock(block: L2BlockNew): Promise<void> {
359
+ return new Promise<void>((resolve, reject) => {
360
+ this.blockQueue.push({ block, resolve, reject });
361
+ this.log.debug(`Queued block ${block.number} for processing`);
362
+ // Trigger an immediate sync, but don't wait for it - the promise resolves when the block is processed
363
+ this.syncImmediate().catch(err => {
364
+ this.log.error(`Sync immediate call failed: ${err}`);
365
+ });
366
+ });
367
+ }
368
+
369
+ /**
370
+ * Processes all queued blocks, adding them to the store.
371
+ * Called at the beginning of each sync iteration.
372
+ * Blocks are processed in the order they were queued.
373
+ */
374
+ private async processQueuedBlocks(): Promise<void> {
375
+ if (this.blockQueue.length === 0) {
376
+ return;
377
+ }
378
+
379
+ // Take all blocks from the queue
380
+ const queuedItems = this.blockQueue.splice(0, this.blockQueue.length);
381
+ this.log.debug(`Processing ${queuedItems.length} queued block(s)`);
382
+
383
+ // Process each block individually to properly resolve/reject each promise
384
+ for (const { block, resolve, reject } of queuedItems) {
385
+ try {
386
+ await this.store.addBlocks([block]);
387
+ this.log.debug(`Added block ${block.number} to store`);
388
+ resolve();
389
+ } catch (err: any) {
390
+ this.log.error(`Failed to add block ${block.number} to store: ${err.message}`);
391
+ reject(err);
392
+ }
393
+ }
394
+ }
395
+
299
396
  public waitForInitialSync() {
300
397
  return this.initialSyncPromise.promise;
301
398
  }
@@ -315,11 +412,8 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
315
412
  }
316
413
  }
317
414
 
318
- /**
319
- * Fetches logs from L1 contracts and processes them.
320
- */
321
- @trackSpan('Archiver.sync')
322
- private async sync() {
415
+ @trackSpan('Archiver.syncFromL1')
416
+ private async syncFromL1() {
323
417
  /**
324
418
  * We keep track of three "pointers" to L1 blocks:
325
419
  * 1. the last L1 block that published an L2 block
@@ -405,7 +499,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
405
499
  // past it, since otherwise we'll keep downloading it and reprocessing it on every iteration until
406
500
  // we get a valid checkpoint to advance the syncpoint.
407
501
  if (!rollupStatus.validationResult?.valid && rollupStatus.lastL1BlockWithCheckpoint !== undefined) {
408
- await this.store.setBlockSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
502
+ await this.store.setCheckpointSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
409
503
  }
410
504
 
411
505
  // And lastly we check if we are missing any checkpoints behind us due to a possible L1 reorg.
@@ -439,6 +533,17 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
439
533
  }
440
534
  }
441
535
 
536
+ /**
537
+ * Fetches logs from L1 contracts and processes them.
538
+ */
539
+ @trackSpan('Archiver.sync')
540
+ private async sync() {
541
+ // Process any queued blocks first, before doing L1 sync
542
+ await this.processQueuedBlocks();
543
+ // Now perform L1 sync
544
+ await this.syncFromL1();
545
+ }
546
+
442
547
  /** Queries the rollup contract on whether a prune can be executed on the immediate next L1 block. */
443
548
  private async canPrune(currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
444
549
  const time = (currentL1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
@@ -454,8 +559,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
454
559
  }
455
560
 
456
561
  /** Checks if there'd be a reorg for the next checkpoint submission and start pruning now. */
562
+ @trackSpan('Archiver.handleEpochPrune')
457
563
  private async handleEpochPrune(
458
- provenCheckpointNumber: number,
564
+ provenCheckpointNumber: CheckpointNumber,
459
565
  currentL1BlockNumber: bigint,
460
566
  currentL1Timestamp: bigint,
461
567
  ) {
@@ -465,9 +571,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
465
571
 
466
572
  if (canPrune) {
467
573
  const timer = new Timer();
468
- const pruneFrom = provenCheckpointNumber + 1;
574
+ const pruneFrom = CheckpointNumber(provenCheckpointNumber + 1);
469
575
 
470
- const header = await this.getCheckpointHeader(Number(pruneFrom));
576
+ const header = await this.getCheckpointHeader(pruneFrom);
471
577
  if (header === undefined) {
472
578
  throw new Error(`Missing checkpoint header ${pruneFrom}`);
473
579
  }
@@ -477,13 +583,23 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
477
583
 
478
584
  const checkpointsToUnwind = localPendingCheckpointNumber - provenCheckpointNumber;
479
585
 
480
- const checkpoints = await this.getCheckpoints(Number(provenCheckpointNumber) + 1, Number(checkpointsToUnwind));
586
+ const checkpointPromises = Array.from({ length: checkpointsToUnwind })
587
+ .fill(0)
588
+ .map((_, i) => this.store.getCheckpointData(CheckpointNumber(i + pruneFrom)));
589
+ const checkpoints = await Promise.all(checkpointPromises);
590
+
591
+ const blockPromises = await Promise.all(
592
+ checkpoints
593
+ .filter(isDefined)
594
+ .map(cp => this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber))),
595
+ );
596
+ const newBlocks = blockPromises.filter(isDefined).flat();
481
597
 
482
598
  // Emit an event for listening services to react to the chain prune
483
599
  this.emit(L2BlockSourceEvents.L2PruneDetected, {
484
600
  type: L2BlockSourceEvents.L2PruneDetected,
485
601
  epochNumber: pruneFromEpochNumber,
486
- blocks: checkpoints.flatMap(c => L2Block.fromCheckpoint(c)),
602
+ blocks: newBlocks,
487
603
  });
488
604
 
489
605
  this.log.debug(
@@ -514,6 +630,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
514
630
  return [nextStart, nextEnd];
515
631
  }
516
632
 
633
+ @trackSpan('Archiver.handleL1ToL2Messages')
517
634
  private async handleL1ToL2Messages(
518
635
  messagesSyncPoint: L1BlockId,
519
636
  currentL1BlockNumber: bigint,
@@ -594,7 +711,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
594
711
  // Log stats for messages retrieved (if any).
595
712
  if (messageCount > 0) {
596
713
  this.log.info(
597
- `Retrieved ${messageCount} new L1 to L2 messages up to message with index ${lastMessage?.index} for L2 block ${lastMessage?.l2BlockNumber}`,
714
+ `Retrieved ${messageCount} new L1 to L2 messages up to message with index ${lastMessage?.index} for checkpoint ${lastMessage?.checkpointNumber}`,
598
715
  { lastMessage, messageCount },
599
716
  );
600
717
  }
@@ -672,23 +789,25 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
672
789
  return Buffer32.fromString(block.hash);
673
790
  }
674
791
 
792
+ @trackSpan('Archiver.handleCheckpoints')
675
793
  private async handleCheckpoints(blocksSynchedTo: bigint, currentL1BlockNumber: bigint): Promise<RollupStatus> {
676
794
  const localPendingCheckpointNumber = await this.getSynchedCheckpointNumber();
677
- const initialValidationResult: ValidateBlockResult | undefined = await this.store.getPendingChainValidationStatus();
678
- const [
679
- rollupProvenCheckpointNumber,
680
- provenArchive,
681
- rollupPendingCheckpointNumber,
682
- pendingArchive,
683
- archiveForLocalPendingCheckpointNumber,
684
- ] = await this.rollup.status(BigInt(localPendingCheckpointNumber), { blockNumber: currentL1BlockNumber });
685
- const provenCheckpointNumber = Number(rollupProvenCheckpointNumber);
686
- const pendingCheckpointNumber = Number(rollupPendingCheckpointNumber);
687
- const rollupStatus = {
795
+ const initialValidationResult: ValidateCheckpointResult | undefined =
796
+ await this.store.getPendingChainValidationStatus();
797
+ const {
688
798
  provenCheckpointNumber,
689
799
  provenArchive,
690
800
  pendingCheckpointNumber,
691
801
  pendingArchive,
802
+ archiveOfMyCheckpoint: archiveForLocalPendingCheckpointNumber,
803
+ } = await execInSpan(this.tracer, 'Archiver.getRollupStatus', () =>
804
+ this.rollup.status(localPendingCheckpointNumber, { blockNumber: currentL1BlockNumber }),
805
+ );
806
+ const rollupStatus: RollupStatus = {
807
+ provenCheckpointNumber,
808
+ provenArchive: provenArchive.toString(),
809
+ pendingCheckpointNumber,
810
+ pendingArchive: pendingArchive.toString(),
692
811
  validationResult: initialValidationResult,
693
812
  };
694
813
  this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
@@ -711,17 +830,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
711
830
  }
712
831
  }
713
832
 
714
- const localCheckpointForDestinationProvenCheckpointNumber = await this.getCheckpoint(provenCheckpointNumber);
833
+ const localCheckpointForDestinationProvenCheckpointNumber =
834
+ await this.store.getCheckpointData(provenCheckpointNumber);
715
835
 
716
836
  // Sanity check. I've hit what seems to be a state where the proven checkpoint is set to a value greater than the latest
717
837
  // synched checkpoint when requesting L2Tips from the archiver. This is the only place where the proven checkpoint is set.
718
838
  const synched = await this.getSynchedCheckpointNumber();
719
839
  if (
720
840
  localCheckpointForDestinationProvenCheckpointNumber &&
721
- synched < localCheckpointForDestinationProvenCheckpointNumber.number
841
+ synched < localCheckpointForDestinationProvenCheckpointNumber.checkpointNumber
722
842
  ) {
723
843
  this.log.error(
724
- `Hit local checkpoint greater than last synched checkpoint: ${localCheckpointForDestinationProvenCheckpointNumber.number} > ${synched}`,
844
+ `Hit local checkpoint greater than last synched checkpoint: ${localCheckpointForDestinationProvenCheckpointNumber.checkpointNumber} > ${synched}`,
725
845
  );
726
846
  }
727
847
 
@@ -731,38 +851,39 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
731
851
  }`,
732
852
  );
733
853
 
734
- const lastProvenBlockNumber = await this.getLastBlockNumberInCheckpoint(provenCheckpointNumber);
735
854
  if (
736
855
  localCheckpointForDestinationProvenCheckpointNumber &&
737
- provenArchive === localCheckpointForDestinationProvenCheckpointNumber.archive.root.toString()
856
+ provenArchive.equals(localCheckpointForDestinationProvenCheckpointNumber.archive.root)
738
857
  ) {
739
858
  const localProvenCheckpointNumber = await this.getProvenCheckpointNumber();
740
859
  if (localProvenCheckpointNumber !== provenCheckpointNumber) {
741
860
  await this.setProvenCheckpointNumber(provenCheckpointNumber);
742
- this.log.info(`Updated proven chain to checkpoint ${provenCheckpointNumber}`, {
743
- provenCheckpointNumber,
744
- });
861
+ this.log.info(`Updated proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
745
862
  const provenSlotNumber = localCheckpointForDestinationProvenCheckpointNumber.header.slotNumber;
746
863
  const provenEpochNumber: EpochNumber = getEpochAtSlot(provenSlotNumber, this.l1constants);
864
+ const lastBlockNumberInCheckpoint =
865
+ localCheckpointForDestinationProvenCheckpointNumber.startBlock +
866
+ localCheckpointForDestinationProvenCheckpointNumber.numBlocks -
867
+ 1;
747
868
 
748
869
  this.emit(L2BlockSourceEvents.L2BlockProven, {
749
870
  type: L2BlockSourceEvents.L2BlockProven,
750
- blockNumber: BigInt(lastProvenBlockNumber),
871
+ blockNumber: BlockNumber(lastBlockNumberInCheckpoint),
751
872
  slotNumber: provenSlotNumber,
752
873
  epochNumber: provenEpochNumber,
753
874
  });
875
+ this.instrumentation.updateLastProvenBlock(lastBlockNumberInCheckpoint);
754
876
  } else {
755
877
  this.log.trace(`Proven checkpoint ${provenCheckpointNumber} already stored.`);
756
878
  }
757
879
  }
758
- this.instrumentation.updateLastProvenBlock(lastProvenBlockNumber);
759
880
  };
760
881
 
761
882
  // This is an edge case that we only hit if there are no proposed checkpoints.
762
883
  // If we have 0 checkpoints locally and there are no checkpoints onchain there is nothing to do.
763
884
  const noCheckpoints = localPendingCheckpointNumber === 0 && pendingCheckpointNumber === 0;
764
885
  if (noCheckpoints) {
765
- await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
886
+ await this.store.setCheckpointSynchedL1BlockNumber(currentL1BlockNumber);
766
887
  this.log.debug(
767
888
  `No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no checkpoints on chain`,
768
889
  );
@@ -774,13 +895,13 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
774
895
  // Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
775
896
  // are any state that could be impacted by it. If we have no checkpoints, there is no impact.
776
897
  if (localPendingCheckpointNumber > 0) {
777
- const localPendingCheckpoint = await this.getCheckpoint(localPendingCheckpointNumber);
898
+ const localPendingCheckpoint = await this.store.getCheckpointData(localPendingCheckpointNumber);
778
899
  if (localPendingCheckpoint === undefined) {
779
900
  throw new Error(`Missing checkpoint ${localPendingCheckpointNumber}`);
780
901
  }
781
902
 
782
903
  const localPendingArchiveRoot = localPendingCheckpoint.archive.root.toString();
783
- const noCheckpointSinceLast = localPendingCheckpoint && pendingArchive === localPendingArchiveRoot;
904
+ const noCheckpointSinceLast = localPendingCheckpoint && pendingArchive.toString() === localPendingArchiveRoot;
784
905
  if (noCheckpointSinceLast) {
785
906
  // We believe the following line causes a problem when we encounter L1 re-orgs.
786
907
  // Basically, by setting the synched L1 block number here, we are saying that we have
@@ -794,7 +915,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
794
915
  return rollupStatus;
795
916
  }
796
917
 
797
- const localPendingCheckpointInChain = archiveForLocalPendingCheckpointNumber === localPendingArchiveRoot;
918
+ const localPendingCheckpointInChain = archiveForLocalPendingCheckpointNumber.equals(
919
+ localPendingCheckpoint.archive.root,
920
+ );
798
921
  if (!localPendingCheckpointInChain) {
799
922
  // If our local pending checkpoint tip is not in the chain on L1 a "prune" must have happened
800
923
  // or the L1 have reorged.
@@ -807,20 +930,20 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
807
930
 
808
931
  let tipAfterUnwind = localPendingCheckpointNumber;
809
932
  while (true) {
810
- const candidateCheckpoint = await this.getCheckpoint(tipAfterUnwind);
933
+ const candidateCheckpoint = await this.store.getCheckpointData(tipAfterUnwind);
811
934
  if (candidateCheckpoint === undefined) {
812
935
  break;
813
936
  }
814
937
 
815
- const archiveAtContract = await this.rollup.archiveAt(BigInt(candidateCheckpoint.number));
938
+ const archiveAtContract = await this.rollup.archiveAt(candidateCheckpoint.checkpointNumber);
816
939
  this.log.trace(
817
- `Checking local checkpoint ${candidateCheckpoint.number} with archive ${candidateCheckpoint.archive.root}`,
940
+ `Checking local checkpoint ${candidateCheckpoint.checkpointNumber} with archive ${candidateCheckpoint.archive.root}`,
818
941
  {
819
942
  archiveAtContract,
820
943
  archiveLocal: candidateCheckpoint.archive.root.toString(),
821
944
  },
822
945
  );
823
- if (archiveAtContract === candidateCheckpoint.archive.root.toString()) {
946
+ if (archiveAtContract.equals(candidateCheckpoint.archive.root)) {
824
947
  break;
825
948
  }
826
949
  tipAfterUnwind--;
@@ -849,14 +972,20 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
849
972
 
850
973
  this.log.trace(`Retrieving checkpoints from L1 block ${searchStartBlock} to ${searchEndBlock}`);
851
974
 
852
- // TODO(md): Retrieve from blob sink then from consensus client, then from peers
853
- const retrievedCheckpoints = await retrieveCheckpointsFromRollup(
854
- this.rollup.getContract() as GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
855
- this.publicClient,
856
- this.blobSinkClient,
857
- searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
858
- searchEndBlock,
859
- this.log,
975
+ // TODO(md): Retrieve from blob client then from consensus client, then from peers
976
+ const retrievedCheckpoints = await execInSpan(this.tracer, 'Archiver.retrieveCheckpointsFromRollup', () =>
977
+ retrieveCheckpointsFromRollup(
978
+ this.rollup.getContract() as GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
979
+ this.publicClient,
980
+ this.debugClient,
981
+ this.blobClient,
982
+ searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
983
+ searchEndBlock,
984
+ this.l1Addresses,
985
+ this.instrumentation,
986
+ this.log,
987
+ !this.initialSyncComplete, // isHistoricalSync
988
+ ),
860
989
  );
861
990
 
862
991
  if (retrievedCheckpoints.length === 0) {
@@ -879,7 +1008,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
879
1008
  const validCheckpoints: PublishedCheckpoint[] = [];
880
1009
 
881
1010
  for (const published of publishedCheckpoints) {
882
- const validationResult = this.config.skipValidateBlockAttestations
1011
+ const validationResult = this.config.skipValidateCheckpointAttestations
883
1012
  ? { valid: true as const }
884
1013
  : await validateCheckpointAttestations(published, this.epochCache, this.l1constants, this.log);
885
1014
 
@@ -892,7 +1021,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
892
1021
  rollupStatus.validationResult?.valid !== validationResult.valid ||
893
1022
  (!rollupStatus.validationResult.valid &&
894
1023
  !validationResult.valid &&
895
- rollupStatus.validationResult.block.blockNumber === validationResult.block.blockNumber)
1024
+ rollupStatus.validationResult.checkpoint.checkpointNumber === validationResult.checkpoint.checkpointNumber)
896
1025
  ) {
897
1026
  rollupStatus.validationResult = validationResult;
898
1027
  }
@@ -904,9 +1033,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
904
1033
  ...pick(validationResult, 'reason'),
905
1034
  });
906
1035
 
907
- // Emit event for invalid block detection
908
- this.emit(L2BlockSourceEvents.InvalidAttestationsBlockDetected, {
909
- type: L2BlockSourceEvents.InvalidAttestationsBlockDetected,
1036
+ // Emit event for invalid checkpoint detection
1037
+ this.emit(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, {
1038
+ type: L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
910
1039
  validationResult,
911
1040
  });
912
1041
 
@@ -916,6 +1045,25 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
916
1045
  continue;
917
1046
  }
918
1047
 
1048
+ // Check the inHash of the checkpoint against the l1->l2 messages.
1049
+ // The messages should've been synced up to the currentL1BlockNumber and must be available for the published
1050
+ // checkpoints we just retrieved.
1051
+ const l1ToL2Messages = await this.getL1ToL2Messages(published.checkpoint.number);
1052
+ const computedInHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
1053
+ const publishedInHash = published.checkpoint.header.inHash;
1054
+ if (!computedInHash.equals(publishedInHash)) {
1055
+ this.log.fatal(`Mismatch inHash for checkpoint ${published.checkpoint.number}`, {
1056
+ checkpointHash: published.checkpoint.hash(),
1057
+ l1BlockNumber: published.l1.blockNumber,
1058
+ computedInHash,
1059
+ publishedInHash,
1060
+ });
1061
+ // Throwing an error since this is most likely caused by a bug.
1062
+ throw new Error(
1063
+ `Mismatch inHash for checkpoint ${published.checkpoint.number}. Expected ${computedInHash} but got ${publishedInHash}`,
1064
+ );
1065
+ }
1066
+
919
1067
  validCheckpoints.push(published);
920
1068
  this.log.debug(
921
1069
  `Ingesting new checkpoint ${published.checkpoint.number} with ${published.checkpoint.blocks.length} blocks`,
@@ -931,25 +1079,28 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
931
1079
  try {
932
1080
  const updatedValidationResult =
933
1081
  rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
934
- const [processDuration] = await elapsed(() => this.addCheckpoints(validCheckpoints, updatedValidationResult));
1082
+ const [processDuration] = await elapsed(() =>
1083
+ execInSpan(this.tracer, 'Archiver.addCheckpoints', () =>
1084
+ this.addCheckpoints(validCheckpoints, updatedValidationResult),
1085
+ ),
1086
+ );
935
1087
  this.instrumentation.processNewBlocks(
936
1088
  processDuration / validCheckpoints.length,
937
1089
  validCheckpoints.flatMap(c => c.checkpoint.blocks),
938
1090
  );
939
1091
  } catch (err) {
940
- if (err instanceof InitialBlockNumberNotSequentialError) {
941
- const { previousBlockNumber, newBlockNumber } = err;
942
- const previousBlock = previousBlockNumber
943
- ? await this.store.getPublishedBlock(previousBlockNumber)
1092
+ if (err instanceof InitialCheckpointNumberNotSequentialError) {
1093
+ const { previousCheckpointNumber, newCheckpointNumber } = err;
1094
+ const previousCheckpoint = previousCheckpointNumber
1095
+ ? await this.store.getCheckpointData(CheckpointNumber(previousCheckpointNumber))
944
1096
  : undefined;
945
- const updatedL1SyncPoint = previousBlock?.l1.blockNumber ?? this.l1constants.l1StartBlock;
1097
+ const updatedL1SyncPoint = previousCheckpoint?.l1.blockNumber ?? this.l1constants.l1StartBlock;
946
1098
  await this.store.setBlockSynchedL1BlockNumber(updatedL1SyncPoint);
947
1099
  this.log.warn(
948
- `Attempting to insert block ${newBlockNumber} with previous block ${previousBlockNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
1100
+ `Attempting to insert checkpoint ${newCheckpointNumber} with previous block ${previousCheckpointNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
949
1101
  {
950
- previousBlockNumber,
951
- previousBlockHash: await previousBlock?.block.hash(),
952
- newBlockNumber,
1102
+ previousCheckpointNumber,
1103
+ newCheckpointNumber,
953
1104
  updatedL1SyncPoint,
954
1105
  },
955
1106
  );
@@ -969,7 +1120,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
969
1120
  });
970
1121
  }
971
1122
  lastRetrievedCheckpoint = validCheckpoints.at(-1) ?? lastRetrievedCheckpoint;
972
- lastL1BlockWithCheckpoint = publishedCheckpoints.at(-1)?.l1.blockNumber ?? lastL1BlockWithCheckpoint;
1123
+ lastL1BlockWithCheckpoint = retrievedCheckpoints.at(-1)?.l1.blockNumber ?? lastL1BlockWithCheckpoint;
973
1124
  } while (searchEndBlock < currentL1BlockNumber);
974
1125
 
975
1126
  // Important that we update AFTER inserting the blocks.
@@ -994,13 +1145,16 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
994
1145
  // We suspect an L1 reorg that added checkpoints *behind* us. If that is the case, it must have happened between
995
1146
  // the last checkpoint we saw and the current one, so we reset the last synched L1 block number. In the edge case
996
1147
  // we don't have one, we go back 2 L1 epochs, which is the deepest possible reorg (assuming Casper is working).
997
- const latestLocalCheckpoint =
998
- lastRetrievedCheckpoint ??
999
- (latestLocalCheckpointNumber > 0
1000
- ? await this.getPublishedCheckpoints(latestLocalCheckpointNumber, 1).then(([c]) => c)
1001
- : undefined);
1002
- const targetL1BlockNumber = latestLocalCheckpoint?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
1003
- const latestLocalCheckpointArchive = latestLocalCheckpoint?.checkpoint.archive.root.toString();
1148
+ let latestLocalCheckpointArchive: string | undefined = undefined;
1149
+ let targetL1BlockNumber = maxBigint(currentL1BlockNumber - 64n, 0n);
1150
+ if (lastRetrievedCheckpoint) {
1151
+ latestLocalCheckpointArchive = lastRetrievedCheckpoint.checkpoint.archive.root.toString();
1152
+ targetL1BlockNumber = lastRetrievedCheckpoint.l1.blockNumber;
1153
+ } else if (latestLocalCheckpointNumber > 0) {
1154
+ const checkpoint = await this.store.getRangeOfCheckpoints(latestLocalCheckpointNumber, 1).then(([c]) => c);
1155
+ latestLocalCheckpointArchive = checkpoint.archive.root.toString();
1156
+ targetL1BlockNumber = checkpoint.l1.blockNumber;
1157
+ }
1004
1158
  this.log.warn(
1005
1159
  `Failed to reach checkpoint ${pendingCheckpointNumber} at ${currentL1BlockNumber} (latest is ${latestLocalCheckpointNumber}). ` +
1006
1160
  `Rolling back last synched L1 block number to ${targetL1BlockNumber}.`,
@@ -1086,15 +1240,22 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1086
1240
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1087
1241
  const blocks: L2Block[] = [];
1088
1242
 
1089
- // Walk the list of blocks backwards and filter by slots matching the requested epoch.
1090
- // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1091
- let block = await this.getBlock(await this.store.getSynchedL2BlockNumber());
1092
- const slot = (b: L2Block) => b.header.globalVariables.slotNumber;
1093
- while (block && slot(block) >= start) {
1094
- if (slot(block) <= end) {
1095
- blocks.push(block);
1243
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1244
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1245
+ let checkpoint = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1246
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1247
+ while (checkpoint && slot(checkpoint) >= start) {
1248
+ if (slot(checkpoint) <= end) {
1249
+ // push the blocks on backwards
1250
+ const endBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
1251
+ for (let i = endBlock; i >= checkpoint.startBlock; i--) {
1252
+ const block = await this.getBlock(BlockNumber(i));
1253
+ if (block) {
1254
+ blocks.push(block);
1255
+ }
1256
+ }
1096
1257
  }
1097
- block = await this.getBlock(block.number - 1);
1258
+ checkpoint = await this.store.getCheckpointData(CheckpointNumber(checkpoint.checkpointNumber - 1));
1098
1259
  }
1099
1260
 
1100
1261
  return blocks.reverse();
@@ -1104,16 +1265,22 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1104
1265
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1105
1266
  const blocks: BlockHeader[] = [];
1106
1267
 
1107
- // Walk the list of blocks backwards and filter by slots matching the requested epoch.
1108
- // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1109
- let number = await this.store.getSynchedL2BlockNumber();
1110
- let header = await this.getBlockHeader(number);
1111
- const slot = (b: BlockHeader) => b.globalVariables.slotNumber;
1112
- while (header && slot(header) >= start) {
1113
- if (slot(header) <= end) {
1114
- blocks.push(header);
1268
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1269
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1270
+ let checkpoint = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1271
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1272
+ while (checkpoint && slot(checkpoint) >= start) {
1273
+ if (slot(checkpoint) <= end) {
1274
+ // push the blocks on backwards
1275
+ const endBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
1276
+ for (let i = endBlock; i >= checkpoint.startBlock; i--) {
1277
+ const block = await this.getBlockHeader(BlockNumber(i));
1278
+ if (block) {
1279
+ blocks.push(block);
1280
+ }
1281
+ }
1115
1282
  }
1116
- header = await this.getBlockHeader(--number);
1283
+ checkpoint = await this.store.getCheckpointData(CheckpointNumber(checkpoint.checkpointNumber - 1));
1117
1284
  }
1118
1285
  return blocks.reverse();
1119
1286
  }
@@ -1151,102 +1318,53 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1151
1318
  return this.initialSyncComplete;
1152
1319
  }
1153
1320
 
1154
- public async getPublishedCheckpoints(from: number, limit: number, proven?: boolean): Promise<PublishedCheckpoint[]> {
1155
- const blocks = await this.getPublishedBlocks(from, limit, proven);
1156
- return blocks.map(b => b.toPublishedCheckpoint());
1157
- }
1158
-
1159
- public async getCheckpoints(from: number, limit: number, proven?: boolean): Promise<Checkpoint[]> {
1160
- const published = await this.getPublishedCheckpoints(from, limit, proven);
1161
- return published.map(p => p.checkpoint);
1162
- }
1163
-
1164
- public async getCheckpoint(number: number): Promise<Checkpoint | undefined> {
1165
- if (number < 0) {
1321
+ public async getCheckpointHeader(number: CheckpointNumber | 'latest'): Promise<CheckpointHeader | undefined> {
1322
+ if (number === 'latest') {
1166
1323
  number = await this.getSynchedCheckpointNumber();
1167
1324
  }
1168
1325
  if (number === 0) {
1169
1326
  return undefined;
1170
1327
  }
1171
- const published = await this.getPublishedCheckpoints(number, 1);
1172
- return published[0]?.checkpoint;
1173
- }
1174
-
1175
- public async getCheckpointHeader(number: number | 'latest'): Promise<CheckpointHeader | undefined> {
1176
- if (number === 'latest') {
1177
- number = await this.getSynchedCheckpointNumber();
1178
- }
1179
- if (number === 0) {
1328
+ const checkpoint = await this.store.getCheckpointData(number);
1329
+ if (!checkpoint) {
1180
1330
  return undefined;
1181
1331
  }
1182
- const checkpoint = await this.getCheckpoint(number);
1183
- return checkpoint?.header;
1332
+ return checkpoint.header;
1184
1333
  }
1185
1334
 
1186
- public getCheckpointNumber(): Promise<number> {
1335
+ public getCheckpointNumber(): Promise<CheckpointNumber> {
1187
1336
  return this.getSynchedCheckpointNumber();
1188
1337
  }
1189
1338
 
1190
- public getSynchedCheckpointNumber(): Promise<number> {
1191
- // TODO: Checkpoint number will no longer be the same as the block number once we support multiple blocks per checkpoint.
1192
- return this.store.getSynchedL2BlockNumber();
1339
+ public getSynchedCheckpointNumber(): Promise<CheckpointNumber> {
1340
+ return this.store.getSynchedCheckpointNumber();
1193
1341
  }
1194
1342
 
1195
- public getProvenCheckpointNumber(): Promise<number> {
1196
- // TODO: Proven checkpoint number will no longer be the same as the proven block number once we support multiple blocks per checkpoint.
1197
- return this.store.getProvenL2BlockNumber();
1343
+ public getProvenCheckpointNumber(): Promise<CheckpointNumber> {
1344
+ return this.store.getProvenCheckpointNumber();
1198
1345
  }
1199
1346
 
1200
- public setProvenCheckpointNumber(checkpointNumber: number): Promise<void> {
1201
- // TODO: Proven checkpoint number will no longer be the same as the proven block number once we support multiple blocks per checkpoint.
1202
- return this.store.setProvenL2BlockNumber(checkpointNumber);
1347
+ public setProvenCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
1348
+ return this.store.setProvenCheckpointNumber(checkpointNumber);
1203
1349
  }
1204
1350
 
1205
- public unwindCheckpoints(from: number, checkpointsToUnwind: number): Promise<boolean> {
1206
- // TODO: This only works if we have one block per checkpoint.
1207
- return this.store.unwindBlocks(from, checkpointsToUnwind);
1351
+ public unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
1352
+ return this.store.unwindCheckpoints(from, checkpointsToUnwind);
1208
1353
  }
1209
1354
 
1210
- public getLastBlockNumberInCheckpoint(checkpointNumber: number): Promise<number> {
1211
- // TODO: Checkpoint number will no longer be the same as the block number once we support multiple blocks per checkpoint.
1212
- return Promise.resolve(checkpointNumber);
1355
+ public async getLastBlockNumberInCheckpoint(checkpointNumber: CheckpointNumber): Promise<BlockNumber | undefined> {
1356
+ const checkpointData = await this.store.getCheckpointData(checkpointNumber);
1357
+ if (!checkpointData) {
1358
+ return undefined;
1359
+ }
1360
+ return BlockNumber(checkpointData.startBlock + checkpointData.numBlocks - 1);
1213
1361
  }
1214
1362
 
1215
1363
  public addCheckpoints(
1216
1364
  checkpoints: PublishedCheckpoint[],
1217
- pendingChainValidationStatus?: ValidateBlockResult,
1365
+ pendingChainValidationStatus?: ValidateCheckpointResult,
1218
1366
  ): Promise<boolean> {
1219
- return this.store.addBlocks(
1220
- checkpoints.map(p => PublishedL2Block.fromPublishedCheckpoint(p)),
1221
- pendingChainValidationStatus,
1222
- );
1223
- }
1224
-
1225
- /**
1226
- * Gets up to `limit` amount of L2 blocks starting from `from`.
1227
- * @param from - Number of the first block to return (inclusive).
1228
- * @param limit - The number of blocks to return.
1229
- * @param proven - If true, only return blocks that have been proven.
1230
- * @returns The requested L2 blocks.
1231
- */
1232
- public getBlocks(from: number, limit: number, proven?: boolean): Promise<L2Block[]> {
1233
- return this.getPublishedBlocks(from, limit, proven).then(blocks => blocks.map(b => b.block));
1234
- }
1235
-
1236
- /** Equivalent to getBlocks but includes publish data. */
1237
- public async getPublishedBlocks(from: number, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
1238
- const limitWithProven = proven
1239
- ? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
1240
- : limit;
1241
- return limitWithProven === 0 ? [] : await this.store.getPublishedBlocks(from, limitWithProven);
1242
- }
1243
-
1244
- public getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1245
- return this.store.getPublishedBlockByHash(blockHash);
1246
- }
1247
-
1248
- public getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1249
- return this.store.getPublishedBlockByArchive(archive);
1367
+ return this.store.addCheckpoints(checkpoints, pendingChainValidationStatus);
1250
1368
  }
1251
1369
 
1252
1370
  public getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
@@ -1262,7 +1380,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1262
1380
  * @param number - The block number to return.
1263
1381
  * @returns The requested L2 block.
1264
1382
  */
1265
- public async getBlock(number: number): Promise<L2Block | undefined> {
1383
+ public async getL2BlockNew(number: BlockNumber): Promise<L2BlockNew | undefined> {
1266
1384
  // If the number provided is -ve, then return the latest block.
1267
1385
  if (number < 0) {
1268
1386
  number = await this.store.getSynchedL2BlockNumber();
@@ -1270,11 +1388,21 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1270
1388
  if (number === 0) {
1271
1389
  return undefined;
1272
1390
  }
1273
- const publishedBlock = await this.store.getPublishedBlock(number);
1274
- return publishedBlock?.block;
1391
+ const publishedBlock = await this.store.store.getBlock(number);
1392
+ return publishedBlock;
1275
1393
  }
1276
1394
 
1277
- public async getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
1395
+ public async getL2BlocksNew(from: BlockNumber, limit: number, proven?: boolean): Promise<L2BlockNew[]> {
1396
+ const blocks = await this.store.store.getBlocks(from, limit);
1397
+
1398
+ if (proven === true) {
1399
+ const provenBlockNumber = await this.store.getProvenBlockNumber();
1400
+ return blocks.filter(b => b.number <= provenBlockNumber);
1401
+ }
1402
+ return blocks;
1403
+ }
1404
+
1405
+ public async getBlockHeader(number: BlockNumber | 'latest'): Promise<BlockHeader | undefined> {
1278
1406
  if (number === 'latest') {
1279
1407
  number = await this.store.getSynchedL2BlockNumber();
1280
1408
  }
@@ -1285,6 +1413,38 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1285
1413
  return headers.length === 0 ? undefined : headers[0];
1286
1414
  }
1287
1415
 
1416
+ getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
1417
+ return this.store.getCheckpointedBlock(number);
1418
+ }
1419
+
1420
+ public async getCheckpointedBlocks(
1421
+ from: BlockNumber,
1422
+ limit: number,
1423
+ proven?: boolean,
1424
+ ): Promise<CheckpointedL2Block[]> {
1425
+ const blocks = await this.store.store.getCheckpointedBlocks(from, limit);
1426
+
1427
+ if (proven === true) {
1428
+ const provenBlockNumber = await this.store.getProvenBlockNumber();
1429
+ return blocks.filter(b => b.block.number <= provenBlockNumber);
1430
+ }
1431
+ return blocks;
1432
+ }
1433
+
1434
+ getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
1435
+ return this.store.getCheckpointedBlockByHash(blockHash);
1436
+ }
1437
+
1438
+ getProvenBlockNumber(): Promise<BlockNumber> {
1439
+ return this.store.getProvenBlockNumber();
1440
+ }
1441
+ getCheckpointedBlockNumber(): Promise<BlockNumber> {
1442
+ return this.store.getCheckpointedL2BlockNumber();
1443
+ }
1444
+ getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
1445
+ return this.store.getCheckpointedBlockByArchive(archive);
1446
+ }
1447
+
1288
1448
  public getTxEffect(txHash: TxHash) {
1289
1449
  return this.store.getTxEffect(txHash);
1290
1450
  }
@@ -1293,24 +1453,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1293
1453
  return this.store.getSettledTxReceipt(txHash);
1294
1454
  }
1295
1455
 
1296
- /**
1297
- * Retrieves all private logs from up to `limit` blocks, starting from the block number `from`.
1298
- * @param from - The block number from which to begin retrieving logs.
1299
- * @param limit - The maximum number of blocks to retrieve logs from.
1300
- * @returns An array of private logs from the specified range of blocks.
1301
- */
1302
- public getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
1303
- return this.store.getPrivateLogs(from, limit);
1456
+ getPrivateLogsByTags(tags: SiloedTag[]): Promise<TxScopedL2Log[][]> {
1457
+ return this.store.getPrivateLogsByTags(tags);
1304
1458
  }
1305
1459
 
1306
- /**
1307
- * Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
1308
- * @param tags - The tags to filter the logs by.
1309
- * @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
1310
- * that tag.
1311
- */
1312
- getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
1313
- return this.store.getLogsByTags(tags);
1460
+ getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise<TxScopedL2Log[][]> {
1461
+ return this.store.getPublicLogsByTagsFromContract(contractAddress, tags);
1314
1462
  }
1315
1463
 
1316
1464
  /**
@@ -1333,19 +1481,11 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1333
1481
 
1334
1482
  /**
1335
1483
  * Gets the number of the latest L2 block processed by the block source implementation.
1484
+ * This includes both checkpointed and uncheckpointed blocks.
1336
1485
  * @returns The number of the latest L2 block processed by the block source implementation.
1337
1486
  */
1338
- public getBlockNumber(): Promise<number> {
1339
- return this.store.getSynchedL2BlockNumber();
1340
- }
1341
-
1342
- public getProvenBlockNumber(): Promise<number> {
1343
- return this.store.getProvenL2BlockNumber();
1344
- }
1345
-
1346
- /** Forcefully updates the last proven block number. Use for testing. */
1347
- public setProvenBlockNumber(blockNumber: number): Promise<void> {
1348
- return this.store.setProvenL2BlockNumber(blockNumber);
1487
+ public getBlockNumber(): Promise<BlockNumber> {
1488
+ return this.store.getLatestBlockNumber();
1349
1489
  }
1350
1490
 
1351
1491
  public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
@@ -1373,12 +1513,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1373
1513
  }
1374
1514
 
1375
1515
  /**
1376
- * Gets L1 to L2 message (to be) included in a given block.
1377
- * @param blockNumber - L2 block number to get messages for.
1516
+ * Gets L1 to L2 message (to be) included in a given checkpoint.
1517
+ * @param checkpointNumber - Checkpoint number to get messages for.
1378
1518
  * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found).
1379
1519
  */
1380
- getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
1381
- return this.store.getL1ToL2Messages(blockNumber);
1520
+ getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
1521
+ return this.store.getL1ToL2Messages(checkpointNumber);
1382
1522
  }
1383
1523
 
1384
1524
  /**
@@ -1402,7 +1542,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1402
1542
  return this.store.getDebugFunctionName(address, selector);
1403
1543
  }
1404
1544
 
1405
- async getPendingChainValidationStatus(): Promise<ValidateBlockResult> {
1545
+ async getPendingChainValidationStatus(): Promise<ValidateCheckpointResult> {
1406
1546
  return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
1407
1547
  }
1408
1548
 
@@ -1411,84 +1551,145 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1411
1551
  }
1412
1552
 
1413
1553
  async getL2Tips(): Promise<L2Tips> {
1414
- const [latestBlockNumber, provenBlockNumber] = await Promise.all([
1554
+ const [latestBlockNumber, provenBlockNumber, checkpointedBlockNumber] = await Promise.all([
1415
1555
  this.getBlockNumber(),
1416
1556
  this.getProvenBlockNumber(),
1557
+ this.getCheckpointedBlockNumber(),
1417
1558
  ] as const);
1418
1559
 
1419
1560
  // TODO(#13569): Compute proper finalized block number based on L1 finalized block.
1420
1561
  // We just force it 2 epochs worth of proven data for now.
1421
1562
  // NOTE: update end-to-end/src/e2e_epochs/epochs_empty_blocks.test.ts as that uses finalized blocks in computations
1422
- const finalizedBlockNumber = Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0);
1423
-
1424
- const [latestBlockHeader, provenBlockHeader, finalizedBlockHeader] = await Promise.all([
1425
- latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
1426
- provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined,
1427
- finalizedBlockNumber > 0 ? this.getBlockHeader(finalizedBlockNumber) : undefined,
1428
- ] as const);
1429
-
1430
- if (latestBlockNumber > 0 && !latestBlockHeader) {
1563
+ const finalizedBlockNumber = BlockNumber(Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0));
1564
+
1565
+ const beforeInitialblockNumber = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
1566
+
1567
+ // Get the latest block header and checkpointed blocks for proven, finalised and checkpointed blocks
1568
+ const [latestBlockHeader, provenCheckpointedBlock, finalizedCheckpointedBlock, checkpointedBlock] =
1569
+ await Promise.all([
1570
+ latestBlockNumber > beforeInitialblockNumber ? this.getBlockHeader(latestBlockNumber) : undefined,
1571
+ provenBlockNumber > beforeInitialblockNumber ? this.getCheckpointedBlock(provenBlockNumber) : undefined,
1572
+ finalizedBlockNumber > beforeInitialblockNumber ? this.getCheckpointedBlock(finalizedBlockNumber) : undefined,
1573
+ checkpointedBlockNumber > beforeInitialblockNumber
1574
+ ? this.getCheckpointedBlock(checkpointedBlockNumber)
1575
+ : undefined,
1576
+ ] as const);
1577
+
1578
+ if (latestBlockNumber > beforeInitialblockNumber && !latestBlockHeader) {
1431
1579
  throw new Error(`Failed to retrieve latest block header for block ${latestBlockNumber}`);
1432
1580
  }
1433
1581
 
1434
- if (provenBlockNumber > 0 && !provenBlockHeader) {
1582
+ // Checkpointed blocks must exist for proven, finalized and checkpointed tips if they are beyond the initial block number.
1583
+ if (checkpointedBlockNumber > beforeInitialblockNumber && !checkpointedBlock?.block.header) {
1584
+ throw new Error(
1585
+ `Failed to retrieve checkpointed block header for block ${checkpointedBlockNumber} (latest block is ${latestBlockNumber})`,
1586
+ );
1587
+ }
1588
+
1589
+ if (provenBlockNumber > beforeInitialblockNumber && !provenCheckpointedBlock?.block.header) {
1435
1590
  throw new Error(
1436
- `Failed to retrieve proven block header for block ${provenBlockNumber} (latest block is ${latestBlockNumber})`,
1591
+ `Failed to retrieve proven checkpointed for block ${provenBlockNumber} (latest block is ${latestBlockNumber})`,
1437
1592
  );
1438
1593
  }
1439
1594
 
1440
- if (finalizedBlockNumber > 0 && !finalizedBlockHeader) {
1595
+ if (finalizedBlockNumber > beforeInitialblockNumber && !finalizedCheckpointedBlock?.block.header) {
1441
1596
  throw new Error(
1442
1597
  `Failed to retrieve finalized block header for block ${finalizedBlockNumber} (latest block is ${latestBlockNumber})`,
1443
1598
  );
1444
1599
  }
1445
1600
 
1446
- const latestBlockHeaderHash = await latestBlockHeader?.hash();
1447
- const provenBlockHeaderHash = await provenBlockHeader?.hash();
1448
- const finalizedBlockHeaderHash = await finalizedBlockHeader?.hash();
1601
+ const latestBlockHeaderHash = (await latestBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1602
+ const provenBlockHeaderHash = (await provenCheckpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1603
+ const finalizedBlockHeaderHash =
1604
+ (await finalizedCheckpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1605
+ const checkpointedBlockHeaderHash = (await checkpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1606
+
1607
+ // Now attempt to retrieve checkpoints for proven, finalised and checkpointed blocks
1608
+ const [[provenBlockCheckpoint], [finalizedBlockCheckpoint], [checkpointedBlockCheckpoint]] = await Promise.all([
1609
+ provenCheckpointedBlock !== undefined
1610
+ ? await this.getPublishedCheckpoints(provenCheckpointedBlock?.checkpointNumber, 1)
1611
+ : [undefined],
1612
+ finalizedCheckpointedBlock !== undefined
1613
+ ? await this.getPublishedCheckpoints(finalizedCheckpointedBlock?.checkpointNumber, 1)
1614
+ : [undefined],
1615
+ checkpointedBlock !== undefined
1616
+ ? await this.getPublishedCheckpoints(checkpointedBlock?.checkpointNumber, 1)
1617
+ : [undefined],
1618
+ ]);
1619
+
1620
+ const initialcheckpointId: CheckpointId = {
1621
+ number: CheckpointNumber.ZERO,
1622
+ hash: GENESIS_CHECKPOINT_HEADER_HASH.toString(),
1623
+ };
1624
+
1625
+ const makeCheckpointId = (checkpoint: PublishedCheckpoint | undefined) => {
1626
+ if (checkpoint === undefined) {
1627
+ return initialcheckpointId;
1628
+ }
1629
+ return {
1630
+ number: checkpoint.checkpoint.number,
1631
+ hash: checkpoint.checkpoint.hash().toString(),
1632
+ };
1633
+ };
1449
1634
 
1450
- return {
1451
- latest: {
1635
+ const l2Tips: L2Tips = {
1636
+ proposed: {
1452
1637
  number: latestBlockNumber,
1453
- hash: latestBlockHeaderHash?.toString(),
1454
- } as L2BlockId,
1638
+ hash: latestBlockHeaderHash.toString(),
1639
+ },
1455
1640
  proven: {
1456
- number: provenBlockNumber,
1457
- hash: provenBlockHeaderHash?.toString(),
1458
- } as L2BlockId,
1641
+ block: {
1642
+ number: provenBlockNumber,
1643
+ hash: provenBlockHeaderHash.toString(),
1644
+ },
1645
+ checkpoint: makeCheckpointId(provenBlockCheckpoint),
1646
+ },
1459
1647
  finalized: {
1460
- number: finalizedBlockNumber,
1461
- hash: finalizedBlockHeaderHash?.toString(),
1462
- } as L2BlockId,
1648
+ block: {
1649
+ number: finalizedBlockNumber,
1650
+ hash: finalizedBlockHeaderHash.toString(),
1651
+ },
1652
+ checkpoint: makeCheckpointId(finalizedBlockCheckpoint),
1653
+ },
1654
+ checkpointed: {
1655
+ block: {
1656
+ number: checkpointedBlockNumber,
1657
+ hash: checkpointedBlockHeaderHash.toString(),
1658
+ },
1659
+ checkpoint: makeCheckpointId(checkpointedBlockCheckpoint),
1660
+ },
1463
1661
  };
1662
+
1663
+ return l2Tips;
1464
1664
  }
1465
1665
 
1466
- public async rollbackTo(targetL2BlockNumber: number): Promise<void> {
1666
+ public async rollbackTo(targetL2BlockNumber: BlockNumber): Promise<void> {
1667
+ // TODO(pw/mbps): This still assumes 1 block per checkpoint
1467
1668
  const currentBlocks = await this.getL2Tips();
1468
- const currentL2Block = currentBlocks.latest.number;
1469
- const currentProvenBlock = currentBlocks.proven.number;
1470
- // const currentFinalizedBlock = currentBlocks.finalized.number;
1669
+ const currentL2Block = currentBlocks.proposed.number;
1670
+ const currentProvenBlock = currentBlocks.proven.block.number;
1471
1671
 
1472
1672
  if (targetL2BlockNumber >= currentL2Block) {
1473
1673
  throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
1474
1674
  }
1475
1675
  const blocksToUnwind = currentL2Block - targetL2BlockNumber;
1476
- const targetL2Block = await this.store.getPublishedBlock(targetL2BlockNumber);
1676
+ const targetL2Block = await this.store.getCheckpointedBlock(targetL2BlockNumber);
1477
1677
  if (!targetL2Block) {
1478
1678
  throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
1479
1679
  }
1480
1680
  const targetL1BlockNumber = targetL2Block.l1.blockNumber;
1681
+ const targetCheckpointNumber = CheckpointNumber.fromBlockNumber(targetL2BlockNumber);
1481
1682
  const targetL1BlockHash = await this.getL1BlockHash(targetL1BlockNumber);
1482
- this.log.info(`Unwinding ${blocksToUnwind} blocks from L2 block ${currentL2Block}`);
1483
- await this.store.unwindBlocks(currentL2Block, blocksToUnwind);
1484
- this.log.info(`Unwinding L1 to L2 messages to ${targetL2BlockNumber}`);
1485
- await this.store.rollbackL1ToL2MessagesToL2Block(targetL2BlockNumber);
1683
+ this.log.info(`Unwinding ${blocksToUnwind} checkpoints from L2 block ${currentL2Block}`);
1684
+ await this.store.unwindCheckpoints(CheckpointNumber(currentL2Block), blocksToUnwind);
1685
+ this.log.info(`Unwinding L1 to L2 messages to checkpoint ${targetCheckpointNumber}`);
1686
+ await this.store.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
1486
1687
  this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
1487
1688
  await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
1488
1689
  await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
1489
1690
  if (targetL2BlockNumber < currentProvenBlock) {
1490
1691
  this.log.info(`Clearing proven L2 block number`);
1491
- await this.store.setProvenL2BlockNumber(0);
1692
+ await this.store.setProvenCheckpointNumber(CheckpointNumber.ZERO);
1492
1693
  }
1493
1694
  // TODO(palla/reorg): Set the finalized block when we add support for it.
1494
1695
  // if (targetL2BlockNumber < currentFinalizedBlock) {
@@ -1496,6 +1697,150 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1496
1697
  // await this.store.setFinalizedL2BlockNumber(0);
1497
1698
  // }
1498
1699
  }
1700
+
1701
+ public async getPublishedCheckpoints(
1702
+ checkpointNumber: CheckpointNumber,
1703
+ limit: number,
1704
+ ): Promise<PublishedCheckpoint[]> {
1705
+ const checkpoints = await this.store.getRangeOfCheckpoints(checkpointNumber, limit);
1706
+ const blocks = (
1707
+ await Promise.all(checkpoints.map(ch => this.store.getBlocksForCheckpoint(ch.checkpointNumber)))
1708
+ ).filter(isDefined);
1709
+
1710
+ const fullCheckpoints: PublishedCheckpoint[] = [];
1711
+ for (let i = 0; i < checkpoints.length; i++) {
1712
+ const blocksForCheckpoint = blocks[i];
1713
+ const checkpoint = checkpoints[i];
1714
+ const fullCheckpoint = new Checkpoint(
1715
+ checkpoint.archive,
1716
+ checkpoint.header,
1717
+ blocksForCheckpoint,
1718
+ checkpoint.checkpointNumber,
1719
+ );
1720
+ const publishedCheckpoint = new PublishedCheckpoint(
1721
+ fullCheckpoint,
1722
+ checkpoint.l1,
1723
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1724
+ );
1725
+ fullCheckpoints.push(publishedCheckpoint);
1726
+ }
1727
+ return fullCheckpoints;
1728
+ }
1729
+
1730
+ public async getCheckpointsForEpoch(epochNumber: EpochNumber): Promise<Checkpoint[]> {
1731
+ const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1732
+ const checkpoints: Checkpoint[] = [];
1733
+
1734
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1735
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1736
+ let checkpointData = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1737
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1738
+ while (checkpointData && slot(checkpointData) >= start) {
1739
+ if (slot(checkpointData) <= end) {
1740
+ // push the checkpoints on backwards
1741
+ const [checkpoint] = await this.getPublishedCheckpoints(checkpointData.checkpointNumber, 1);
1742
+ checkpoints.push(checkpoint.checkpoint);
1743
+ }
1744
+ checkpointData = await this.store.getCheckpointData(CheckpointNumber(checkpointData.checkpointNumber - 1));
1745
+ }
1746
+
1747
+ return checkpoints.reverse();
1748
+ }
1749
+
1750
+ /* Legacy APIs */
1751
+
1752
+ public async getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1753
+ const checkpointedBlock = await this.store.getCheckpointedBlockByHash(blockHash);
1754
+ return this.buildOldBlockFromCheckpointedBlock(checkpointedBlock);
1755
+ }
1756
+ public async getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1757
+ const checkpointedBlock = await this.store.getCheckpointedBlockByArchive(archive);
1758
+ return this.buildOldBlockFromCheckpointedBlock(checkpointedBlock);
1759
+ }
1760
+
1761
+ /**
1762
+ * Gets up to `limit` amount of L2 blocks starting from `from`.
1763
+ * @param from - Number of the first block to return (inclusive).
1764
+ * @param limit - The number of blocks to return.
1765
+ * @param proven - If true, only return blocks that have been proven.
1766
+ * @returns The requested L2 blocks.
1767
+ */
1768
+ public async getBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<L2Block[]> {
1769
+ const publishedBlocks = await this.getPublishedBlocks(from, limit, proven);
1770
+ return publishedBlocks.map(x => x.block);
1771
+ }
1772
+
1773
+ public async getPublishedBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
1774
+ const checkpoints = await this.store.getRangeOfCheckpoints(CheckpointNumber(from), limit);
1775
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber();
1776
+ const blocks = (
1777
+ await Promise.all(checkpoints.map(ch => this.store.getBlocksForCheckpoint(ch.checkpointNumber)))
1778
+ ).filter(isDefined);
1779
+
1780
+ const olbBlocks: PublishedL2Block[] = [];
1781
+ for (let i = 0; i < checkpoints.length; i++) {
1782
+ const blockForCheckpoint = blocks[i][0];
1783
+ const checkpoint = checkpoints[i];
1784
+ if (checkpoint.checkpointNumber > provenCheckpointNumber && proven === true) {
1785
+ // this checkpointisn't proven and we only want proven
1786
+ continue;
1787
+ }
1788
+ const oldCheckpoint = new Checkpoint(
1789
+ blockForCheckpoint.archive,
1790
+ checkpoint.header,
1791
+ [blockForCheckpoint],
1792
+ checkpoint.checkpointNumber,
1793
+ );
1794
+ const oldBlock = L2Block.fromCheckpoint(oldCheckpoint);
1795
+ const publishedBlock = new PublishedL2Block(
1796
+ oldBlock,
1797
+ checkpoint.l1,
1798
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1799
+ );
1800
+ olbBlocks.push(publishedBlock);
1801
+ }
1802
+ return olbBlocks;
1803
+ }
1804
+
1805
+ private async buildOldBlockFromCheckpointedBlock(
1806
+ checkpointedBlock: CheckpointedL2Block | undefined,
1807
+ ): Promise<PublishedL2Block | undefined> {
1808
+ if (!checkpointedBlock) {
1809
+ return undefined;
1810
+ }
1811
+ const checkpoint = await this.store.getCheckpointData(checkpointedBlock.checkpointNumber);
1812
+ if (!checkpoint) {
1813
+ return checkpoint;
1814
+ }
1815
+ const fullCheckpoint = new Checkpoint(
1816
+ checkpointedBlock?.block.archive,
1817
+ checkpoint?.header,
1818
+ [checkpointedBlock.block],
1819
+ checkpoint.checkpointNumber,
1820
+ );
1821
+ const oldBlock = L2Block.fromCheckpoint(fullCheckpoint);
1822
+ const published = new PublishedL2Block(
1823
+ oldBlock,
1824
+ checkpoint.l1,
1825
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1826
+ );
1827
+ return published;
1828
+ }
1829
+
1830
+ public async getBlock(number: BlockNumber): Promise<L2Block | undefined> {
1831
+ // If the number provided is -ve, then return the latest block.
1832
+ if (number < 0) {
1833
+ number = await this.store.getSynchedL2BlockNumber();
1834
+ }
1835
+ if (number === 0) {
1836
+ return undefined;
1837
+ }
1838
+ const publishedBlocks = await this.getPublishedBlocks(number, 1);
1839
+ if (publishedBlocks.length === 0) {
1840
+ return undefined;
1841
+ }
1842
+ return publishedBlocks[0].block;
1843
+ }
1499
1844
  }
1500
1845
 
1501
1846
  enum Operation {
@@ -1526,17 +1871,20 @@ export class ArchiverStoreHelper
1526
1871
  | 'close'
1527
1872
  | 'transactionAsync'
1528
1873
  | 'addBlocks'
1874
+ | 'getBlock'
1875
+ | 'getBlocks'
1876
+ | 'getCheckpointedBlocks'
1529
1877
  >
1530
1878
  {
1531
1879
  #log = createLogger('archiver:block-helper');
1532
1880
 
1533
- constructor(protected readonly store: ArchiverDataStore) {}
1881
+ constructor(public readonly store: ArchiverDataStore) {}
1534
1882
 
1535
1883
  /**
1536
1884
  * Extracts and stores contract classes out of ContractClassPublished events emitted by the class registry contract.
1537
1885
  * @param allLogs - All logs emitted in a bunch of blocks.
1538
1886
  */
1539
- async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
1887
+ async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: BlockNumber, operation: Operation) {
1540
1888
  const contractClassPublishedEvents = allLogs
1541
1889
  .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
1542
1890
  .map(log => ContractClassPublishedEvent.fromLog(log));
@@ -1561,7 +1909,7 @@ export class ArchiverStoreHelper
1561
1909
  * Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
1562
1910
  * @param allLogs - All logs emitted in a bunch of blocks.
1563
1911
  */
1564
- async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: number, operation: Operation) {
1912
+ async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: BlockNumber, operation: Operation) {
1565
1913
  const contractInstances = allLogs
1566
1914
  .filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
1567
1915
  .map(log => ContractInstancePublishedEvent.fromLog(log))
@@ -1614,7 +1962,7 @@ export class ArchiverStoreHelper
1614
1962
  * @param _blockNum - The block number
1615
1963
  * @returns
1616
1964
  */
1617
- async #storeBroadcastedIndividualFunctions(allLogs: ContractClassLog[], _blockNum: number) {
1965
+ async #storeBroadcastedIndividualFunctions(allLogs: ContractClassLog[], _blockNum: BlockNumber) {
1618
1966
  // Filter out private and utility function broadcast events
1619
1967
  const privateFnEvents = allLogs
1620
1968
  .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
@@ -1668,7 +2016,23 @@ export class ArchiverStoreHelper
1668
2016
  return true;
1669
2017
  }
1670
2018
 
1671
- public addBlocks(blocks: PublishedL2Block[], pendingChainValidationStatus?: ValidateBlockResult): Promise<boolean> {
2019
+ private async addBlockDataToDB(block: L2BlockNew) {
2020
+ const contractClassLogs = block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
2021
+ // ContractInstancePublished event logs are broadcast in privateLogs.
2022
+ const privateLogs = block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
2023
+ const publicLogs = block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
2024
+
2025
+ return (
2026
+ await Promise.all([
2027
+ this.#updatePublishedContractClasses(contractClassLogs, block.number, Operation.Store),
2028
+ this.#updateDeployedContractInstances(privateLogs, block.number, Operation.Store),
2029
+ this.#updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, Operation.Store),
2030
+ this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.number),
2031
+ ])
2032
+ ).every(Boolean);
2033
+ }
2034
+
2035
+ public addBlocks(blocks: L2BlockNew[], pendingChainValidationStatus?: ValidateCheckpointResult): Promise<boolean> {
1672
2036
  // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1673
2037
  // or if the previous block is not in the store.
1674
2038
  return this.store.transactionAsync(async () => {
@@ -1678,25 +2042,10 @@ export class ArchiverStoreHelper
1678
2042
  // Update the pending chain validation status if provided
1679
2043
  pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
1680
2044
  // Add any logs emitted during the retrieved blocks
1681
- this.store.addLogs(blocks.map(block => block.block)),
2045
+ this.store.addLogs(blocks),
1682
2046
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1683
- ...blocks.map(async block => {
1684
- const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1685
- // ContractInstancePublished event logs are broadcast in privateLogs.
1686
- const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1687
- const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1688
- return (
1689
- await Promise.all([
1690
- this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Store),
1691
- this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
1692
- this.#updateUpdatedContractInstances(
1693
- publicLogs,
1694
- block.block.header.globalVariables.timestamp,
1695
- Operation.Store,
1696
- ),
1697
- this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.block.number),
1698
- ])
1699
- ).every(Boolean);
2047
+ ...blocks.map(block => {
2048
+ return this.addBlockDataToDB(block);
1700
2049
  }),
1701
2050
  ]);
1702
2051
 
@@ -1704,61 +2053,104 @@ export class ArchiverStoreHelper
1704
2053
  });
1705
2054
  }
1706
2055
 
1707
- public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
1708
- const last = await this.getSynchedL2BlockNumber();
1709
- if (from != last) {
1710
- throw new Error(`Cannot unwind blocks from block ${from} when the last block is ${last}`);
2056
+ public addCheckpoints(
2057
+ checkpoints: PublishedCheckpoint[],
2058
+ pendingChainValidationStatus?: ValidateCheckpointResult,
2059
+ ): Promise<boolean> {
2060
+ // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
2061
+ // or if the previous block is not in the store.
2062
+ return this.store.transactionAsync(async () => {
2063
+ await this.store.addCheckpoints(checkpoints);
2064
+ const allBlocks = checkpoints.flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks);
2065
+
2066
+ const opResults = await Promise.all([
2067
+ // Update the pending chain validation status if provided
2068
+ pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
2069
+ // Add any logs emitted during the retrieved blocks
2070
+ this.store.addLogs(allBlocks),
2071
+ // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
2072
+ ...allBlocks.map(block => {
2073
+ return this.addBlockDataToDB(block);
2074
+ }),
2075
+ ]);
2076
+
2077
+ return opResults.every(Boolean);
2078
+ });
2079
+ }
2080
+
2081
+ public async unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
2082
+ if (checkpointsToUnwind <= 0) {
2083
+ throw new Error(`Cannot unwind ${checkpointsToUnwind} blocks`);
1711
2084
  }
1712
- if (blocksToUnwind <= 0) {
1713
- throw new Error(`Cannot unwind ${blocksToUnwind} blocks`);
2085
+
2086
+ const last = await this.getSynchedCheckpointNumber();
2087
+ if (from != last) {
2088
+ throw new Error(`Cannot unwind checkpoints from checkpoint ${from} when the last checkpoint is ${last}`);
1714
2089
  }
1715
2090
 
1716
- // from - blocksToUnwind = the new head, so + 1 for what we need to remove
1717
- const blocks = await this.getPublishedBlocks(from - blocksToUnwind + 1, blocksToUnwind);
2091
+ const blocks = [];
2092
+ const lastCheckpointNumber = from + checkpointsToUnwind - 1;
2093
+ for (let checkpointNumber = from; checkpointNumber <= lastCheckpointNumber; checkpointNumber++) {
2094
+ const blocksForCheckpoint = await this.store.getBlocksForCheckpoint(checkpointNumber);
2095
+ if (!blocksForCheckpoint) {
2096
+ continue;
2097
+ }
2098
+ blocks.push(...blocksForCheckpoint);
2099
+ }
1718
2100
 
1719
2101
  const opResults = await Promise.all([
1720
2102
  // Prune rolls back to the last proven block, which is by definition valid
1721
2103
  this.store.setPendingChainValidationStatus({ valid: true }),
1722
2104
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1723
2105
  ...blocks.map(async block => {
1724
- const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
2106
+ const contractClassLogs = block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1725
2107
  // ContractInstancePublished event logs are broadcast in privateLogs.
1726
- const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1727
- const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
2108
+ const privateLogs = block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
2109
+ const publicLogs = block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1728
2110
 
1729
2111
  return (
1730
2112
  await Promise.all([
1731
- this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Delete),
1732
- this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Delete),
1733
- this.#updateUpdatedContractInstances(
1734
- publicLogs,
1735
- block.block.header.globalVariables.timestamp,
1736
- Operation.Delete,
1737
- ),
2113
+ this.#updatePublishedContractClasses(contractClassLogs, block.number, Operation.Delete),
2114
+ this.#updateDeployedContractInstances(privateLogs, block.number, Operation.Delete),
2115
+ this.#updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, Operation.Delete),
1738
2116
  ])
1739
2117
  ).every(Boolean);
1740
2118
  }),
1741
2119
 
1742
- this.store.deleteLogs(blocks.map(b => b.block)),
1743
- this.store.unwindBlocks(from, blocksToUnwind),
2120
+ this.store.deleteLogs(blocks),
2121
+ this.store.unwindCheckpoints(from, checkpointsToUnwind),
1744
2122
  ]);
1745
2123
 
1746
2124
  return opResults.every(Boolean);
1747
2125
  }
1748
2126
 
1749
- getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
1750
- return this.store.getPublishedBlocks(from, limit);
2127
+ getCheckpointData(checkpointNumber: CheckpointNumber): Promise<CheckpointData | undefined> {
2128
+ return this.store.getCheckpointData(checkpointNumber);
2129
+ }
2130
+
2131
+ getRangeOfCheckpoints(from: CheckpointNumber, limit: number): Promise<CheckpointData[]> {
2132
+ return this.store.getRangeOfCheckpoints(from, limit);
2133
+ }
2134
+
2135
+ getCheckpointedL2BlockNumber(): Promise<BlockNumber> {
2136
+ return this.store.getCheckpointedL2BlockNumber();
2137
+ }
2138
+ getSynchedCheckpointNumber(): Promise<CheckpointNumber> {
2139
+ return this.store.getSynchedCheckpointNumber();
1751
2140
  }
1752
- getPublishedBlock(number: number): Promise<PublishedL2Block | undefined> {
1753
- return this.store.getPublishedBlock(number);
2141
+ setCheckpointSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
2142
+ return this.store.setCheckpointSynchedL1BlockNumber(l1BlockNumber);
1754
2143
  }
1755
- getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1756
- return this.store.getPublishedBlockByHash(blockHash);
2144
+ getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
2145
+ return this.store.getCheckpointedBlock(number);
1757
2146
  }
1758
- getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1759
- return this.store.getPublishedBlockByArchive(archive);
2147
+ getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
2148
+ return this.store.getCheckpointedBlockByHash(blockHash);
1760
2149
  }
1761
- getBlockHeaders(from: number, limit: number): Promise<BlockHeader[]> {
2150
+ getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
2151
+ return this.store.getCheckpointedBlockByArchive(archive);
2152
+ }
2153
+ getBlockHeaders(from: BlockNumber, limit: number): Promise<BlockHeader[]> {
1762
2154
  return this.store.getBlockHeaders(from, limit);
1763
2155
  }
1764
2156
  getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
@@ -1767,6 +2159,18 @@ export class ArchiverStoreHelper
1767
2159
  getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1768
2160
  return this.store.getBlockHeaderByArchive(archive);
1769
2161
  }
2162
+ getBlockByHash(blockHash: Fr): Promise<L2BlockNew | undefined> {
2163
+ return this.store.getBlockByHash(blockHash);
2164
+ }
2165
+ getBlockByArchive(archive: Fr): Promise<L2BlockNew | undefined> {
2166
+ return this.store.getBlockByArchive(archive);
2167
+ }
2168
+ getLatestBlockNumber(): Promise<BlockNumber> {
2169
+ return this.store.getLatestBlockNumber();
2170
+ }
2171
+ getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2BlockNew[] | undefined> {
2172
+ return this.store.getBlocksForCheckpoint(checkpointNumber);
2173
+ }
1770
2174
  getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
1771
2175
  return this.store.getTxEffect(txHash);
1772
2176
  }
@@ -1776,17 +2180,17 @@ export class ArchiverStoreHelper
1776
2180
  addL1ToL2Messages(messages: InboxMessage[]): Promise<void> {
1777
2181
  return this.store.addL1ToL2Messages(messages);
1778
2182
  }
1779
- getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
1780
- return this.store.getL1ToL2Messages(blockNumber);
2183
+ getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
2184
+ return this.store.getL1ToL2Messages(checkpointNumber);
1781
2185
  }
1782
2186
  getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise<bigint | undefined> {
1783
2187
  return this.store.getL1ToL2MessageIndex(l1ToL2Message);
1784
2188
  }
1785
- getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
1786
- return this.store.getPrivateLogs(from, limit);
2189
+ getPrivateLogsByTags(tags: SiloedTag[]): Promise<TxScopedL2Log[][]> {
2190
+ return this.store.getPrivateLogsByTags(tags);
1787
2191
  }
1788
- getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
1789
- return this.store.getLogsByTags(tags, logsPerTag);
2192
+ getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise<TxScopedL2Log[][]> {
2193
+ return this.store.getPublicLogsByTagsFromContract(contractAddress, tags);
1790
2194
  }
1791
2195
  getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
1792
2196
  return this.store.getPublicLogs(filter);
@@ -1794,17 +2198,20 @@ export class ArchiverStoreHelper
1794
2198
  getContractClassLogs(filter: LogFilter): Promise<GetContractClassLogsResponse> {
1795
2199
  return this.store.getContractClassLogs(filter);
1796
2200
  }
1797
- getSynchedL2BlockNumber(): Promise<number> {
1798
- return this.store.getSynchedL2BlockNumber();
2201
+ getSynchedL2BlockNumber(): Promise<BlockNumber> {
2202
+ return this.store.getLatestBlockNumber();
2203
+ }
2204
+ getProvenCheckpointNumber(): Promise<CheckpointNumber> {
2205
+ return this.store.getProvenCheckpointNumber();
1799
2206
  }
1800
- getProvenL2BlockNumber(): Promise<number> {
1801
- return this.store.getProvenL2BlockNumber();
2207
+ getProvenBlockNumber(): Promise<BlockNumber> {
2208
+ return this.store.getProvenBlockNumber();
1802
2209
  }
1803
- setProvenL2BlockNumber(l2BlockNumber: number): Promise<void> {
1804
- return this.store.setProvenL2BlockNumber(l2BlockNumber);
2210
+ setProvenCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
2211
+ return this.store.setProvenCheckpointNumber(checkpointNumber);
1805
2212
  }
1806
2213
  setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
1807
- return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
2214
+ return this.store.setCheckpointSynchedL1BlockNumber(l1BlockNumber);
1808
2215
  }
1809
2216
  setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void> {
1810
2217
  return this.store.setMessageSynchedL1Block(l1Block);
@@ -1836,8 +2243,8 @@ export class ArchiverStoreHelper
1836
2243
  estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }> {
1837
2244
  return this.store.estimateSize();
1838
2245
  }
1839
- rollbackL1ToL2MessagesToL2Block(targetBlockNumber: number): Promise<void> {
1840
- return this.store.rollbackL1ToL2MessagesToL2Block(targetBlockNumber);
2246
+ rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber: CheckpointNumber): Promise<void> {
2247
+ return this.store.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
1841
2248
  }
1842
2249
  iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
1843
2250
  return this.store.iterateL1ToL2Messages(range);
@@ -1848,10 +2255,10 @@ export class ArchiverStoreHelper
1848
2255
  getLastL1ToL2Message(): Promise<InboxMessage | undefined> {
1849
2256
  return this.store.getLastL1ToL2Message();
1850
2257
  }
1851
- getPendingChainValidationStatus(): Promise<ValidateBlockResult | undefined> {
2258
+ getPendingChainValidationStatus(): Promise<ValidateCheckpointResult | undefined> {
1852
2259
  return this.store.getPendingChainValidationStatus();
1853
2260
  }
1854
- setPendingChainValidationStatus(status: ValidateBlockResult | undefined): Promise<void> {
2261
+ setPendingChainValidationStatus(status: ValidateCheckpointResult | undefined): Promise<void> {
1855
2262
  this.#log.debug(`Setting pending chain validation status to valid ${status?.valid}`, status);
1856
2263
  return this.store.setPendingChainValidationStatus(status);
1857
2264
  }