@aztec/archiver 0.0.1-commit.d1f2d6c → 0.0.1-commit.d20b825a7

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 (120) hide show
  1. package/README.md +12 -6
  2. package/dest/archiver.d.ts +16 -10
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +110 -122
  5. package/dest/config.d.ts +5 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +15 -3
  8. package/dest/errors.d.ts +55 -10
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +74 -15
  11. package/dest/factory.d.ts +5 -4
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +34 -29
  14. package/dest/index.d.ts +4 -2
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +3 -1
  17. package/dest/l1/bin/retrieve-calldata.js +36 -33
  18. package/dest/l1/calldata_retriever.d.ts +73 -50
  19. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  20. package/dest/l1/calldata_retriever.js +191 -259
  21. package/dest/l1/data_retrieval.d.ts +26 -17
  22. package/dest/l1/data_retrieval.d.ts.map +1 -1
  23. package/dest/l1/data_retrieval.js +43 -48
  24. package/dest/l1/spire_proposer.d.ts +5 -5
  25. package/dest/l1/spire_proposer.d.ts.map +1 -1
  26. package/dest/l1/spire_proposer.js +9 -17
  27. package/dest/l1/validate_historical_logs.d.ts +23 -0
  28. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  29. package/dest/l1/validate_historical_logs.js +108 -0
  30. package/dest/l1/validate_trace.d.ts +6 -3
  31. package/dest/l1/validate_trace.d.ts.map +1 -1
  32. package/dest/l1/validate_trace.js +13 -9
  33. package/dest/modules/data_source_base.d.ts +17 -10
  34. package/dest/modules/data_source_base.d.ts.map +1 -1
  35. package/dest/modules/data_source_base.js +39 -77
  36. package/dest/modules/data_store_updater.d.ts +50 -26
  37. package/dest/modules/data_store_updater.d.ts.map +1 -1
  38. package/dest/modules/data_store_updater.js +169 -130
  39. package/dest/modules/instrumentation.d.ts +21 -3
  40. package/dest/modules/instrumentation.d.ts.map +1 -1
  41. package/dest/modules/instrumentation.js +58 -18
  42. package/dest/modules/l1_synchronizer.d.ts +10 -9
  43. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  44. package/dest/modules/l1_synchronizer.js +285 -157
  45. package/dest/modules/validation.d.ts +1 -1
  46. package/dest/modules/validation.d.ts.map +1 -1
  47. package/dest/modules/validation.js +2 -2
  48. package/dest/store/block_store.d.ts +85 -36
  49. package/dest/store/block_store.d.ts.map +1 -1
  50. package/dest/store/block_store.js +433 -162
  51. package/dest/store/contract_class_store.d.ts +2 -3
  52. package/dest/store/contract_class_store.d.ts.map +1 -1
  53. package/dest/store/contract_class_store.js +16 -72
  54. package/dest/store/contract_instance_store.d.ts +1 -1
  55. package/dest/store/contract_instance_store.d.ts.map +1 -1
  56. package/dest/store/contract_instance_store.js +6 -2
  57. package/dest/store/kv_archiver_store.d.ts +76 -32
  58. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  59. package/dest/store/kv_archiver_store.js +92 -37
  60. package/dest/store/l2_tips_cache.d.ts +20 -0
  61. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  62. package/dest/store/l2_tips_cache.js +109 -0
  63. package/dest/store/log_store.d.ts +6 -3
  64. package/dest/store/log_store.d.ts.map +1 -1
  65. package/dest/store/log_store.js +151 -56
  66. package/dest/store/message_store.d.ts +5 -1
  67. package/dest/store/message_store.d.ts.map +1 -1
  68. package/dest/store/message_store.js +21 -9
  69. package/dest/test/fake_l1_state.d.ts +24 -1
  70. package/dest/test/fake_l1_state.d.ts.map +1 -1
  71. package/dest/test/fake_l1_state.js +145 -28
  72. package/dest/test/index.js +3 -1
  73. package/dest/test/mock_archiver.d.ts +1 -1
  74. package/dest/test/mock_archiver.d.ts.map +1 -1
  75. package/dest/test/mock_archiver.js +3 -2
  76. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  77. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  78. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  79. package/dest/test/mock_l2_block_source.d.ts +31 -10
  80. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  81. package/dest/test/mock_l2_block_source.js +163 -92
  82. package/dest/test/mock_structs.d.ts +6 -2
  83. package/dest/test/mock_structs.d.ts.map +1 -1
  84. package/dest/test/mock_structs.js +20 -6
  85. package/dest/test/noop_l1_archiver.d.ts +26 -0
  86. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  87. package/dest/test/noop_l1_archiver.js +74 -0
  88. package/package.json +14 -13
  89. package/src/archiver.ts +150 -146
  90. package/src/config.ts +22 -2
  91. package/src/errors.ts +116 -26
  92. package/src/factory.ts +49 -26
  93. package/src/index.ts +3 -1
  94. package/src/l1/README.md +25 -68
  95. package/src/l1/bin/retrieve-calldata.ts +46 -39
  96. package/src/l1/calldata_retriever.ts +250 -379
  97. package/src/l1/data_retrieval.ts +59 -70
  98. package/src/l1/spire_proposer.ts +7 -15
  99. package/src/l1/validate_historical_logs.ts +140 -0
  100. package/src/l1/validate_trace.ts +24 -6
  101. package/src/modules/data_source_base.ts +81 -101
  102. package/src/modules/data_store_updater.ts +202 -160
  103. package/src/modules/instrumentation.ts +71 -19
  104. package/src/modules/l1_synchronizer.ts +365 -197
  105. package/src/modules/validation.ts +2 -2
  106. package/src/store/block_store.ts +546 -206
  107. package/src/store/contract_class_store.ts +16 -110
  108. package/src/store/contract_instance_store.ts +8 -5
  109. package/src/store/kv_archiver_store.ts +143 -53
  110. package/src/store/l2_tips_cache.ts +134 -0
  111. package/src/store/log_store.ts +225 -67
  112. package/src/store/message_store.ts +27 -10
  113. package/src/structs/inbox_message.ts +1 -1
  114. package/src/test/fake_l1_state.ts +193 -32
  115. package/src/test/index.ts +3 -0
  116. package/src/test/mock_archiver.ts +3 -2
  117. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  118. package/src/test/mock_l2_block_source.ts +217 -90
  119. package/src/test/mock_structs.ts +42 -12
  120. package/src/test/noop_l1_archiver.ts +117 -0
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@aztec/archiver",
3
- "version": "0.0.1-commit.d1f2d6c",
3
+ "version": "0.0.1-commit.d20b825a7",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
7
7
  "./test": "./dest/test/index.js",
8
+ "./test/noop-l1": "./dest/test/noop_l1_archiver.js",
8
9
  "./config": "./dest/config.js"
9
10
  },
10
11
  "typedocOptions": {
@@ -64,18 +65,18 @@
64
65
  ]
65
66
  },
66
67
  "dependencies": {
67
- "@aztec/blob-client": "0.0.1-commit.d1f2d6c",
68
- "@aztec/blob-lib": "0.0.1-commit.d1f2d6c",
69
- "@aztec/constants": "0.0.1-commit.d1f2d6c",
70
- "@aztec/epoch-cache": "0.0.1-commit.d1f2d6c",
71
- "@aztec/ethereum": "0.0.1-commit.d1f2d6c",
72
- "@aztec/foundation": "0.0.1-commit.d1f2d6c",
73
- "@aztec/kv-store": "0.0.1-commit.d1f2d6c",
74
- "@aztec/l1-artifacts": "0.0.1-commit.d1f2d6c",
75
- "@aztec/noir-protocol-circuits-types": "0.0.1-commit.d1f2d6c",
76
- "@aztec/protocol-contracts": "0.0.1-commit.d1f2d6c",
77
- "@aztec/stdlib": "0.0.1-commit.d1f2d6c",
78
- "@aztec/telemetry-client": "0.0.1-commit.d1f2d6c",
68
+ "@aztec/blob-client": "0.0.1-commit.d20b825a7",
69
+ "@aztec/blob-lib": "0.0.1-commit.d20b825a7",
70
+ "@aztec/constants": "0.0.1-commit.d20b825a7",
71
+ "@aztec/epoch-cache": "0.0.1-commit.d20b825a7",
72
+ "@aztec/ethereum": "0.0.1-commit.d20b825a7",
73
+ "@aztec/foundation": "0.0.1-commit.d20b825a7",
74
+ "@aztec/kv-store": "0.0.1-commit.d20b825a7",
75
+ "@aztec/l1-artifacts": "0.0.1-commit.d20b825a7",
76
+ "@aztec/noir-protocol-circuits-types": "0.0.1-commit.d20b825a7",
77
+ "@aztec/protocol-contracts": "0.0.1-commit.d20b825a7",
78
+ "@aztec/stdlib": "0.0.1-commit.d20b825a7",
79
+ "@aztec/telemetry-client": "0.0.1-commit.d20b825a7",
79
80
  "lodash.groupby": "^4.6.0",
80
81
  "lodash.omit": "^4.5.0",
81
82
  "tslib": "^2.5.0",
package/src/archiver.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { BlobClientInterface } from '@aztec/blob-client/client';
2
- import { GENESIS_BLOCK_HEADER_HASH, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
3
2
  import { EpochCache } from '@aztec/epoch-cache';
4
3
  import { BlockTagTooOldError, RollupContract } from '@aztec/ethereum/contracts';
5
4
  import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
@@ -12,34 +11,34 @@ import { EthAddress } from '@aztec/foundation/eth-address';
12
11
  import { type Logger, createLogger } from '@aztec/foundation/log';
13
12
  import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
14
13
  import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
15
- import { DateProvider } from '@aztec/foundation/timer';
14
+ import { DateProvider, elapsed } from '@aztec/foundation/timer';
16
15
  import {
17
16
  type ArchiverEmitter,
18
- type CheckpointId,
19
- GENESIS_CHECKPOINT_HEADER_HASH,
20
17
  L2Block,
21
18
  type L2BlockSink,
22
19
  type L2Tips,
23
20
  type ValidateCheckpointResult,
24
21
  } from '@aztec/stdlib/block';
25
- import { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
22
+ import { type ProposedCheckpointInput, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
26
23
  import {
27
24
  type L1RollupConstants,
28
- getEpochNumberAtTimestamp,
29
- getSlotAtTimestamp,
25
+ getEpochAtSlot,
26
+ getSlotAtNextL1Block,
30
27
  getSlotRangeForEpoch,
31
28
  getTimestampRangeForEpoch,
32
29
  } from '@aztec/stdlib/epoch-helpers';
33
30
  import { type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
34
31
 
35
32
  import { type ArchiverConfig, mapArchiverConfig } from './config.js';
36
- import { NoBlobBodiesFoundError } from './errors.js';
33
+ import { BlockAlreadyCheckpointedError, NoBlobBodiesFoundError } from './errors.js';
34
+ import { validateAndLogHistoricalLogsAvailability } from './l1/validate_historical_logs.js';
37
35
  import { validateAndLogTraceAvailability } from './l1/validate_trace.js';
38
36
  import { ArchiverDataSourceBase } from './modules/data_source_base.js';
39
37
  import { ArchiverDataStoreUpdater } from './modules/data_store_updater.js';
40
38
  import type { ArchiverInstrumentation } from './modules/instrumentation.js';
41
39
  import type { ArchiverL1Synchronizer } from './modules/l1_synchronizer.js';
42
40
  import type { KVArchiverDataStore } from './store/kv_archiver_store.js';
41
+ import { L2TipsCache } from './store/l2_tips_cache.js';
43
42
 
44
43
  /** Export ArchiverEmitter for use in factory and tests. */
45
44
  export type { ArchiverEmitter };
@@ -68,7 +67,7 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
68
67
  public readonly events: ArchiverEmitter;
69
68
 
70
69
  /** A loop in which we will be continually fetching new checkpoints. */
71
- private runningPromise: RunningPromise;
70
+ protected runningPromise: RunningPromise;
72
71
 
73
72
  /** L1 synchronizer that handles fetching checkpoints and messages from L1. */
74
73
  private readonly synchronizer: ArchiverL1Synchronizer;
@@ -82,19 +81,23 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
82
81
  /** Helper to handle updates to the store */
83
82
  private readonly updater: ArchiverDataStoreUpdater;
84
83
 
84
+ /** In-memory cache for L2 chain tips. */
85
+ private readonly l2TipsCache: L2TipsCache;
86
+
85
87
  public readonly tracer: Tracer;
86
88
 
89
+ private readonly instrumentation: ArchiverInstrumentation;
90
+
87
91
  /**
88
92
  * Creates a new instance of the Archiver.
89
93
  * @param publicClient - A client for interacting with the Ethereum node.
90
94
  * @param debugClient - A client for interacting with the Ethereum node for debug/trace methods.
91
95
  * @param rollup - Rollup contract instance.
92
96
  * @param inbox - Inbox contract instance.
93
- * @param l1Addresses - L1 contract addresses (registry, governance proposer, slash factory, slashing proposer).
97
+ * @param l1Addresses - L1 contract addresses (registry, governance proposer, slashing proposer).
94
98
  * @param dataStore - An archiver data store for storage & retrieval of blocks, encrypted logs & contract data.
95
99
  * @param config - Archiver configuration options.
96
100
  * @param blobClient - Client for retrieving blob data.
97
- * @param epochCache - Cache for epoch-related data.
98
101
  * @param dateProvider - Provider for current date/time.
99
102
  * @param instrumentation - Instrumentation for metrics and tracing.
100
103
  * @param l1Constants - L1 rollup constants.
@@ -106,8 +109,10 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
106
109
  private readonly rollup: RollupContract,
107
110
  private readonly l1Addresses: Pick<
108
111
  L1ContractAddresses,
109
- 'registryAddress' | 'governanceProposerAddress' | 'slashFactoryAddress'
110
- > & { slashingProposerAddress: EthAddress },
112
+ 'rollupAddress' | 'registryAddress' | 'inboxAddress' | 'governanceProposerAddress'
113
+ > & {
114
+ slashingProposerAddress: EthAddress;
115
+ },
111
116
  readonly dataStore: KVArchiverDataStore,
112
117
  private config: {
113
118
  pollingIntervalMs: number;
@@ -115,21 +120,30 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
115
120
  skipValidateCheckpointAttestations?: boolean;
116
121
  maxAllowedEthClientDriftSeconds: number;
117
122
  ethereumAllowNoDebugHosts?: boolean;
123
+ skipHistoricalLogsCheck?: boolean;
118
124
  },
119
125
  private readonly blobClient: BlobClientInterface,
120
126
  instrumentation: ArchiverInstrumentation,
121
- protected override readonly l1Constants: L1RollupConstants & { l1StartBlockHash: Buffer32; genesisArchiveRoot: Fr },
127
+ protected override readonly l1Constants: L1RollupConstants & {
128
+ l1StartBlockHash: Buffer32;
129
+ genesisArchiveRoot: Fr;
130
+ },
122
131
  synchronizer: ArchiverL1Synchronizer,
123
132
  events: ArchiverEmitter,
133
+ l2TipsCache?: L2TipsCache,
124
134
  private readonly log: Logger = createLogger('archiver'),
125
135
  ) {
126
136
  super(dataStore, l1Constants);
127
137
 
128
138
  this.tracer = instrumentation.tracer;
139
+ this.instrumentation = instrumentation;
129
140
  this.initialSyncPromise = promiseWithResolvers();
130
141
  this.synchronizer = synchronizer;
131
142
  this.events = events;
132
- this.updater = new ArchiverDataStoreUpdater(this.dataStore);
143
+ this.l2TipsCache = l2TipsCache ?? new L2TipsCache(this.dataStore.blockStore);
144
+ this.updater = new ArchiverDataStoreUpdater(this.dataStore, this.l2TipsCache, {
145
+ rollupManaLimit: l1Constants.rollupManaLimit,
146
+ });
133
147
 
134
148
  // Running promise starts with a small interval inbetween runs, so all iterations needed for the initial sync
135
149
  // are done as fast as possible. This then gets updated once the initial sync completes.
@@ -158,7 +172,22 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
158
172
 
159
173
  await this.blobClient.testSources();
160
174
  await this.synchronizer.testEthereumNodeSynced();
161
- await validateAndLogTraceAvailability(this.debugClient, this.config.ethereumAllowNoDebugHosts ?? false);
175
+ await validateAndLogTraceAvailability(
176
+ this.debugClient,
177
+ this.config.ethereumAllowNoDebugHosts ?? false,
178
+ this.log.getBindings(),
179
+ );
180
+ await validateAndLogHistoricalLogsAvailability(
181
+ this.publicClient,
182
+ {
183
+ rollupAddress: this.l1Addresses.rollupAddress,
184
+ inboxAddress: this.l1Addresses.inboxAddress,
185
+ registryAddress: this.l1Addresses.registryAddress,
186
+ governanceProposerAddress: this.l1Addresses.governanceProposerAddress,
187
+ },
188
+ this.config.skipHistoricalLogsCheck ?? false,
189
+ this.log.getBindings(),
190
+ );
162
191
 
163
192
  // Log initial state for the archiver
164
193
  const { l1StartBlock } = this.l1Constants;
@@ -198,6 +227,10 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
198
227
  });
199
228
  }
200
229
 
230
+ public async setProposedCheckpoint(pending: ProposedCheckpointInput): Promise<void> {
231
+ await this.updater.setProposedCheckpoint(pending);
232
+ }
233
+
201
234
  /**
202
235
  * Processes all queued blocks, adding them to the store.
203
236
  * Called at the beginning of each sync iteration.
@@ -212,13 +245,34 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
212
245
  const queuedItems = this.blockQueue.splice(0, this.blockQueue.length);
213
246
  this.log.debug(`Processing ${queuedItems.length} queued block(s)`);
214
247
 
248
+ // Calculate slot threshold for validation
249
+ const l1Timestamp = this.synchronizer.getL1Timestamp();
250
+ const slotAtNextL1Block =
251
+ l1Timestamp === undefined ? undefined : getSlotAtNextL1Block(l1Timestamp, this.l1Constants);
252
+
215
253
  // Process each block individually to properly resolve/reject each promise
216
254
  for (const { block, resolve, reject } of queuedItems) {
255
+ const blockSlot = block.header.globalVariables.slotNumber;
256
+ if (slotAtNextL1Block !== undefined && blockSlot < slotAtNextL1Block) {
257
+ this.log.warn(
258
+ `Rejecting proposed block ${block.number} for past slot ${blockSlot} (current is ${slotAtNextL1Block})`,
259
+ { block: block.toBlockInfo(), l1Timestamp, slotAtNextL1Block },
260
+ );
261
+ reject(new Error(`Block ${block.number} is for past slot ${blockSlot} (current is ${slotAtNextL1Block})`));
262
+ continue;
263
+ }
264
+
217
265
  try {
218
- await this.updater.addBlocks([block]);
266
+ const [durationMs] = await elapsed(() => this.updater.addProposedBlock(block));
267
+ this.instrumentation.processNewProposedBlock(durationMs, block);
219
268
  this.log.debug(`Added block ${block.number} to store`);
220
269
  resolve();
221
270
  } catch (err: any) {
271
+ if (err instanceof BlockAlreadyCheckpointedError) {
272
+ this.log.debug(`Proposed block ${block.number} matches already checkpointed block, ignoring late proposal`);
273
+ resolve();
274
+ continue;
275
+ }
222
276
  this.log.error(`Failed to add block ${block.number} to store: ${err.message}`);
223
277
  reject(err);
224
278
  }
@@ -310,16 +364,49 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
310
364
  return Promise.resolve(this.synchronizer.getL1Timestamp());
311
365
  }
312
366
 
313
- public getL2SlotNumber(): Promise<SlotNumber | undefined> {
367
+ public async getSyncedL2SlotNumber(): Promise<SlotNumber | undefined> {
368
+ // The synced L2 slot is the latest slot for which we have all L1 data,
369
+ // either because we have seen all L1 blocks for that slot, or because
370
+ // we have seen the corresponding checkpoint.
371
+
372
+ let slotFromL1Sync: SlotNumber | undefined;
314
373
  const l1Timestamp = this.synchronizer.getL1Timestamp();
315
- return Promise.resolve(l1Timestamp === undefined ? undefined : getSlotAtTimestamp(l1Timestamp, this.l1Constants));
374
+ if (l1Timestamp !== undefined) {
375
+ const nextL1BlockSlot = getSlotAtNextL1Block(l1Timestamp, this.l1Constants);
376
+ if (Number(nextL1BlockSlot) > 0) {
377
+ slotFromL1Sync = SlotNumber.add(nextL1BlockSlot, -1);
378
+ }
379
+ }
380
+
381
+ let slotFromCheckpoint: SlotNumber | undefined;
382
+ const latestCheckpointNumber = await this.store.getSynchedCheckpointNumber();
383
+ if (latestCheckpointNumber > 0) {
384
+ const checkpointData = await this.store.getCheckpointData(latestCheckpointNumber);
385
+ if (checkpointData) {
386
+ slotFromCheckpoint = checkpointData.header.slotNumber;
387
+ }
388
+ }
389
+
390
+ if (slotFromL1Sync === undefined && slotFromCheckpoint === undefined) {
391
+ return undefined;
392
+ }
393
+ return SlotNumber(Math.max(slotFromL1Sync ?? 0, slotFromCheckpoint ?? 0));
316
394
  }
317
395
 
318
- public getL2EpochNumber(): Promise<EpochNumber | undefined> {
319
- const l1Timestamp = this.synchronizer.getL1Timestamp();
320
- return Promise.resolve(
321
- l1Timestamp === undefined ? undefined : getEpochNumberAtTimestamp(l1Timestamp, this.l1Constants),
322
- );
396
+ public async getSyncedL2EpochNumber(): Promise<EpochNumber | undefined> {
397
+ const syncedSlot = await this.getSyncedL2SlotNumber();
398
+ if (syncedSlot === undefined) {
399
+ return undefined;
400
+ }
401
+ // An epoch is fully synced when all its slots are synced.
402
+ // We check if syncedSlot is the last slot of its epoch; if so, that epoch is fully synced.
403
+ // Otherwise, only the previous epoch is fully synced.
404
+ const epoch = getEpochAtSlot(syncedSlot, this.l1Constants);
405
+ const [, endSlot] = getSlotRangeForEpoch(epoch, this.l1Constants);
406
+ if (syncedSlot >= endSlot) {
407
+ return epoch;
408
+ }
409
+ return Number(epoch) > 0 ? EpochNumber(Number(epoch) - 1) : undefined;
323
410
  }
324
411
 
325
412
  public async isEpochComplete(epochNumber: EpochNumber): Promise<boolean> {
@@ -358,8 +445,8 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
358
445
  return this.initialSyncComplete;
359
446
  }
360
447
 
361
- public unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
362
- return this.updater.unwindCheckpoints(from, checkpointsToUnwind);
448
+ public removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<boolean> {
449
+ return this.updater.removeCheckpointsAfter(checkpointNumber);
363
450
  }
364
451
 
365
452
  /** Used by TXE to add checkpoints directly without syncing from L1. */
@@ -367,119 +454,15 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
367
454
  checkpoints: PublishedCheckpoint[],
368
455
  pendingChainValidationStatus?: ValidateCheckpointResult,
369
456
  ): Promise<boolean> {
370
- await this.updater.setNewCheckpointData(checkpoints, pendingChainValidationStatus);
457
+ await this.updater.addCheckpoints(checkpoints, pendingChainValidationStatus);
371
458
  return true;
372
459
  }
373
460
 
374
- public async getL2Tips(): Promise<L2Tips> {
375
- const [latestBlockNumber, provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
376
- this.getBlockNumber(),
377
- this.getProvenBlockNumber(),
378
- this.getCheckpointedL2BlockNumber(),
379
- this.getFinalizedL2BlockNumber(),
380
- ] as const);
381
-
382
- const beforeInitialblockNumber = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
383
-
384
- // Get the latest block header and checkpointed blocks for proven, finalised and checkpointed blocks
385
- const [latestBlockHeader, provenCheckpointedBlock, finalizedCheckpointedBlock, checkpointedBlock] =
386
- await Promise.all([
387
- latestBlockNumber > beforeInitialblockNumber ? this.getBlockHeader(latestBlockNumber) : undefined,
388
- provenBlockNumber > beforeInitialblockNumber ? this.getCheckpointedBlock(provenBlockNumber) : undefined,
389
- finalizedBlockNumber > beforeInitialblockNumber ? this.getCheckpointedBlock(finalizedBlockNumber) : undefined,
390
- checkpointedBlockNumber > beforeInitialblockNumber
391
- ? this.getCheckpointedBlock(checkpointedBlockNumber)
392
- : undefined,
393
- ] as const);
394
-
395
- if (latestBlockNumber > beforeInitialblockNumber && !latestBlockHeader) {
396
- throw new Error(`Failed to retrieve latest block header for block ${latestBlockNumber}`);
397
- }
398
-
399
- // Checkpointed blocks must exist for proven, finalized and checkpointed tips if they are beyond the initial block number.
400
- if (checkpointedBlockNumber > beforeInitialblockNumber && !checkpointedBlock?.block.header) {
401
- throw new Error(
402
- `Failed to retrieve checkpointed block header for block ${checkpointedBlockNumber} (latest block is ${latestBlockNumber})`,
403
- );
404
- }
405
-
406
- if (provenBlockNumber > beforeInitialblockNumber && !provenCheckpointedBlock?.block.header) {
407
- throw new Error(
408
- `Failed to retrieve proven checkpointed for block ${provenBlockNumber} (latest block is ${latestBlockNumber})`,
409
- );
410
- }
411
-
412
- if (finalizedBlockNumber > beforeInitialblockNumber && !finalizedCheckpointedBlock?.block.header) {
413
- throw new Error(
414
- `Failed to retrieve finalized block header for block ${finalizedBlockNumber} (latest block is ${latestBlockNumber})`,
415
- );
416
- }
417
-
418
- const latestBlockHeaderHash = (await latestBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
419
- const provenBlockHeaderHash = (await provenCheckpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
420
- const finalizedBlockHeaderHash =
421
- (await finalizedCheckpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
422
- const checkpointedBlockHeaderHash = (await checkpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
423
-
424
- // Now attempt to retrieve checkpoints for proven, finalised and checkpointed blocks
425
- const [[provenBlockCheckpoint], [finalizedBlockCheckpoint], [checkpointedBlockCheckpoint]] = await Promise.all([
426
- provenCheckpointedBlock !== undefined
427
- ? await this.getCheckpoints(provenCheckpointedBlock?.checkpointNumber, 1)
428
- : [undefined],
429
- finalizedCheckpointedBlock !== undefined
430
- ? await this.getCheckpoints(finalizedCheckpointedBlock?.checkpointNumber, 1)
431
- : [undefined],
432
- checkpointedBlock !== undefined ? await this.getCheckpoints(checkpointedBlock?.checkpointNumber, 1) : [undefined],
433
- ]);
434
-
435
- const initialcheckpointId: CheckpointId = {
436
- number: CheckpointNumber.ZERO,
437
- hash: GENESIS_CHECKPOINT_HEADER_HASH.toString(),
438
- };
439
-
440
- const makeCheckpointId = (checkpoint: PublishedCheckpoint | undefined) => {
441
- if (checkpoint === undefined) {
442
- return initialcheckpointId;
443
- }
444
- return {
445
- number: checkpoint.checkpoint.number,
446
- hash: checkpoint.checkpoint.hash().toString(),
447
- };
448
- };
449
-
450
- const l2Tips: L2Tips = {
451
- proposed: {
452
- number: latestBlockNumber,
453
- hash: latestBlockHeaderHash.toString(),
454
- },
455
- proven: {
456
- block: {
457
- number: provenBlockNumber,
458
- hash: provenBlockHeaderHash.toString(),
459
- },
460
- checkpoint: makeCheckpointId(provenBlockCheckpoint),
461
- },
462
- finalized: {
463
- block: {
464
- number: finalizedBlockNumber,
465
- hash: finalizedBlockHeaderHash.toString(),
466
- },
467
- checkpoint: makeCheckpointId(finalizedBlockCheckpoint),
468
- },
469
- checkpointed: {
470
- block: {
471
- number: checkpointedBlockNumber,
472
- hash: checkpointedBlockHeaderHash.toString(),
473
- },
474
- checkpoint: makeCheckpointId(checkpointedBlockCheckpoint),
475
- },
476
- };
477
-
478
- return l2Tips;
461
+ public getL2Tips(): Promise<L2Tips> {
462
+ return this.l2TipsCache.getL2Tips();
479
463
  }
480
464
 
481
465
  public async rollbackTo(targetL2BlockNumber: BlockNumber): Promise<void> {
482
- // TODO(pw/mbps): This still assumes 1 block per checkpoint
483
466
  const currentBlocks = await this.getL2Tips();
484
467
  const currentL2Block = currentBlocks.proposed.number;
485
468
  const currentProvenBlock = currentBlocks.proven.block.number;
@@ -487,13 +470,29 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
487
470
  if (targetL2BlockNumber >= currentL2Block) {
488
471
  throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
489
472
  }
490
- const blocksToUnwind = currentL2Block - targetL2BlockNumber;
491
473
  const targetL2Block = await this.store.getCheckpointedBlock(targetL2BlockNumber);
492
474
  if (!targetL2Block) {
493
475
  throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
494
476
  }
477
+ const targetCheckpointNumber = targetL2Block.checkpointNumber;
478
+
479
+ // Rollback operates at checkpoint granularity: the target block must be the last block of its checkpoint.
480
+ const checkpointData = await this.store.getCheckpointData(targetCheckpointNumber);
481
+ if (checkpointData) {
482
+ const lastBlockInCheckpoint = BlockNumber(checkpointData.startBlock + checkpointData.blockCount - 1);
483
+ if (targetL2BlockNumber !== lastBlockInCheckpoint) {
484
+ const previousCheckpointBoundary =
485
+ checkpointData.startBlock > 1 ? BlockNumber(checkpointData.startBlock - 1) : BlockNumber(0);
486
+ throw new Error(
487
+ `Target L2 block ${targetL2BlockNumber} is not at a checkpoint boundary. ` +
488
+ `Checkpoint ${targetCheckpointNumber} spans blocks ${checkpointData.startBlock} to ${lastBlockInCheckpoint}. ` +
489
+ `Use block ${lastBlockInCheckpoint} to roll back to this checkpoint, ` +
490
+ `or block ${previousCheckpointBoundary} to roll back to the previous one.`,
491
+ );
492
+ }
493
+ }
494
+
495
495
  const targetL1BlockNumber = targetL2Block.l1.blockNumber;
496
- const targetCheckpointNumber = CheckpointNumber.fromBlockNumber(targetL2BlockNumber);
497
496
  const targetL1Block = await this.publicClient.getBlock({
498
497
  blockNumber: targetL1BlockNumber,
499
498
  includeTransactions: false,
@@ -502,21 +501,26 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
502
501
  throw new Error(`Missing L1 block ${targetL1BlockNumber}`);
503
502
  }
504
503
  const targetL1BlockHash = Buffer32.fromString(targetL1Block.hash);
505
- this.log.info(`Unwinding ${blocksToUnwind} checkpoints from L2 block ${currentL2Block}`);
506
- await this.updater.unwindCheckpoints(CheckpointNumber.fromBlockNumber(currentL2Block), blocksToUnwind);
507
- this.log.info(`Unwinding L1 to L2 messages to checkpoint ${targetCheckpointNumber}`);
504
+ this.log.info(
505
+ `Removing checkpoints after checkpoint ${targetCheckpointNumber} (target block ${targetL2BlockNumber})`,
506
+ );
507
+ await this.updater.removeCheckpointsAfter(targetCheckpointNumber);
508
+ this.log.info(`Rolling back L1 to L2 messages to checkpoint ${targetCheckpointNumber}`);
508
509
  await this.store.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
509
510
  this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
510
511
  await this.store.setCheckpointSynchedL1BlockNumber(targetL1BlockNumber);
511
- await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
512
+ await this.store.setMessageSyncState(
513
+ { l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash },
514
+ undefined,
515
+ );
512
516
  if (targetL2BlockNumber < currentProvenBlock) {
513
- this.log.info(`Clearing proven L2 block number`);
514
- await this.store.setProvenCheckpointNumber(CheckpointNumber.ZERO);
517
+ this.log.info(`Rolling back proven L2 checkpoint to ${targetCheckpointNumber}`);
518
+ await this.updater.setProvenCheckpointNumber(targetCheckpointNumber);
519
+ }
520
+ const currentFinalizedBlock = currentBlocks.finalized.block.number;
521
+ if (targetL2BlockNumber < currentFinalizedBlock) {
522
+ this.log.info(`Rolling back finalized L2 checkpoint to ${targetCheckpointNumber}`);
523
+ await this.updater.setFinalizedCheckpointNumber(targetCheckpointNumber);
515
524
  }
516
- // TODO(palla/reorg): Set the finalized block when we add support for it.
517
- // if (targetL2BlockNumber < currentFinalizedBlock) {
518
- // this.log.info(`Clearing finalized L2 block number`);
519
- // await this.store.setFinalizedL2BlockNumber(0);
520
- // }
521
525
  }
522
526
  }
package/src/config.ts CHANGED
@@ -8,7 +8,12 @@ import {
8
8
  getConfigFromMappings,
9
9
  numberConfigHelper,
10
10
  } from '@aztec/foundation/config';
11
- import { type ChainConfig, chainConfigMappings } from '@aztec/stdlib/config';
11
+ import {
12
+ type ChainConfig,
13
+ type PipelineConfig,
14
+ chainConfigMappings,
15
+ pipelineConfigMappings,
16
+ } from '@aztec/stdlib/config';
12
17
  import type { ArchiverSpecificConfig } from '@aztec/stdlib/interfaces/server';
13
18
 
14
19
  /**
@@ -21,11 +26,13 @@ import type { ArchiverSpecificConfig } from '@aztec/stdlib/interfaces/server';
21
26
  export type ArchiverConfig = ArchiverSpecificConfig &
22
27
  L1ReaderConfig &
23
28
  L1ContractsConfig &
29
+ PipelineConfig & // required to pass through to epoch cache
24
30
  BlobClientConfig &
25
31
  ChainConfig;
26
32
 
27
33
  export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
28
34
  ...blobClientConfigMapping,
35
+ ...pipelineConfigMappings,
29
36
  archiverPollingIntervalMS: {
30
37
  env: 'ARCHIVER_POLLING_INTERVAL_MS',
31
38
  description: 'The polling interval in ms for retrieving new L2 blocks and encrypted logs.',
@@ -43,13 +50,17 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
43
50
  },
44
51
  archiverStoreMapSizeKb: {
45
52
  env: 'ARCHIVER_STORE_MAP_SIZE_KB',
46
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
53
+ parseEnv: (val: string) => +val,
47
54
  description: 'The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKb.',
48
55
  },
49
56
  skipValidateCheckpointAttestations: {
50
57
  description: 'Skip validating checkpoint attestations (for testing purposes only)',
51
58
  ...booleanConfigHelper(false),
52
59
  },
60
+ skipPromoteProposedCheckpointDuringL1Sync: {
61
+ description: 'Skip promoting proposed checkpoints during L1 sync (for testing purposes only)',
62
+ ...booleanConfigHelper(false),
63
+ },
53
64
  maxAllowedEthClientDriftSeconds: {
54
65
  env: 'MAX_ALLOWED_ETH_CLIENT_DRIFT_SECONDS',
55
66
  description: 'Maximum allowed drift in seconds between the Ethereum client and current time.',
@@ -60,6 +71,13 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
60
71
  description: 'Whether to allow starting the archiver without debug/trace method support on Ethereum hosts',
61
72
  ...booleanConfigHelper(true),
62
73
  },
74
+ archiverSkipHistoricalLogsCheck: {
75
+ env: 'ARCHIVER_SKIP_HISTORICAL_LOGS_CHECK',
76
+ description:
77
+ 'Skip the startup check that probes the L1 RPC for historical Rollup contract logs. ' +
78
+ 'Set to true to bypass the check when the connected RPC node is known to prune old logs.',
79
+ ...booleanConfigHelper(false),
80
+ },
63
81
  ...chainConfigMappings,
64
82
  ...l1ReaderConfigMappings,
65
83
  viemPollingIntervalMS: {
@@ -89,7 +107,9 @@ export function mapArchiverConfig(config: Partial<ArchiverConfig>) {
89
107
  pollingIntervalMs: config.archiverPollingIntervalMS,
90
108
  batchSize: config.archiverBatchSize,
91
109
  skipValidateCheckpointAttestations: config.skipValidateCheckpointAttestations,
110
+ skipPromoteProposedCheckpointDuringL1Sync: config.skipPromoteProposedCheckpointDuringL1Sync,
92
111
  maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds,
93
112
  ethereumAllowNoDebugHosts: config.ethereumAllowNoDebugHosts,
113
+ skipHistoricalLogsCheck: config.archiverSkipHistoricalLogsCheck,
94
114
  };
95
115
  }