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