@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
|
@@ -2,32 +2,37 @@ import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
|
2
2
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
3
3
|
import { InboxContract, type InboxContractState, RollupContract } from '@aztec/ethereum/contracts';
|
|
4
4
|
import type { L1BlockId } from '@aztec/ethereum/l1-types';
|
|
5
|
+
import { getFinalizedL1Block } from '@aztec/ethereum/queries';
|
|
5
6
|
import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
|
|
6
7
|
import { asyncPool } from '@aztec/foundation/async-pool';
|
|
7
8
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
8
9
|
import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
9
10
|
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
10
|
-
import { pick } from '@aztec/foundation/collection';
|
|
11
|
+
import { compactArray, partition, pick } from '@aztec/foundation/collection';
|
|
11
12
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
13
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
12
14
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
13
15
|
import { retryTimes } from '@aztec/foundation/retry';
|
|
14
16
|
import { count } from '@aztec/foundation/string';
|
|
15
17
|
import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
|
|
16
18
|
import { isDefined, isErrorClass } from '@aztec/foundation/types';
|
|
17
19
|
import { type ArchiverEmitter, L2BlockSourceEvents, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
18
|
-
import { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
20
|
+
import { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
19
21
|
import { type L1RollupConstants, getEpochAtSlot, getSlotAtNextL1Block } from '@aztec/stdlib/epoch-helpers';
|
|
20
22
|
import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
23
|
+
import type { CoordinationSignatureContext } from '@aztec/stdlib/p2p';
|
|
21
24
|
import { type Traceable, type Tracer, execInSpan, trackSpan } from '@aztec/telemetry-client';
|
|
22
25
|
|
|
23
26
|
import { InitialCheckpointNumberNotSequentialError } from '../errors.js';
|
|
24
27
|
import {
|
|
25
|
-
|
|
28
|
+
type RetrievedCheckpointFromCalldata,
|
|
29
|
+
getCheckpointBlobDataFromBlobs,
|
|
30
|
+
retrieveCheckpointCalldataFromRollup,
|
|
26
31
|
retrieveL1ToL2Message,
|
|
27
32
|
retrieveL1ToL2Messages,
|
|
28
33
|
retrievedToPublishedCheckpoint,
|
|
29
34
|
} from '../l1/data_retrieval.js';
|
|
30
|
-
import type
|
|
35
|
+
import { type ArchiverDataStores, getArchiverSynchPoint } from '../store/data_stores.js';
|
|
31
36
|
import type { L2TipsCache } from '../store/l2_tips_cache.js';
|
|
32
37
|
import { MessageStoreError } from '../store/message_store.js';
|
|
33
38
|
import type { InboxMessage } from '../structs/inbox_message.js';
|
|
@@ -62,10 +67,11 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
62
67
|
private readonly debugClient: ViemPublicDebugClient,
|
|
63
68
|
private readonly rollup: RollupContract,
|
|
64
69
|
private readonly inbox: InboxContract,
|
|
65
|
-
private readonly
|
|
70
|
+
private readonly stores: ArchiverDataStores,
|
|
66
71
|
private config: {
|
|
67
72
|
batchSize: number;
|
|
68
73
|
skipValidateCheckpointAttestations?: boolean;
|
|
74
|
+
skipPromoteProposedCheckpointDuringL1Sync?: boolean;
|
|
69
75
|
maxAllowedEthClientDriftSeconds: number;
|
|
70
76
|
},
|
|
71
77
|
private readonly blobClient: BlobClientInterface,
|
|
@@ -81,7 +87,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
81
87
|
l2TipsCache?: L2TipsCache,
|
|
82
88
|
private readonly log: Logger = createLogger('archiver:l1-sync'),
|
|
83
89
|
) {
|
|
84
|
-
this.updater = new ArchiverDataStoreUpdater(this.
|
|
90
|
+
this.updater = new ArchiverDataStoreUpdater(this.stores, l2TipsCache, {
|
|
85
91
|
rollupManaLimit: l1Constants.rollupManaLimit,
|
|
86
92
|
});
|
|
87
93
|
this.tracer = tracer;
|
|
@@ -91,6 +97,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
91
97
|
public setConfig(newConfig: {
|
|
92
98
|
batchSize: number;
|
|
93
99
|
skipValidateCheckpointAttestations?: boolean;
|
|
100
|
+
skipPromoteProposedCheckpointDuringL1Sync?: boolean;
|
|
94
101
|
maxAllowedEthClientDriftSeconds: number;
|
|
95
102
|
}) {
|
|
96
103
|
this.config = newConfig;
|
|
@@ -106,6 +113,13 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
106
113
|
return this.l1Timestamp;
|
|
107
114
|
}
|
|
108
115
|
|
|
116
|
+
private getSignatureContext(): CoordinationSignatureContext {
|
|
117
|
+
return {
|
|
118
|
+
chainId: this.publicClient.chain.id,
|
|
119
|
+
rollupAddress: EthAddress.fromString(this.rollup.address),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
109
123
|
/** Checks that the ethereum node we are connected to has a latest timestamp no more than the allowed drift. Throw if not. */
|
|
110
124
|
public async testEthereumNodeSynced(): Promise<void> {
|
|
111
125
|
const maxAllowedDelay = this.config.maxAllowedEthClientDriftSeconds;
|
|
@@ -148,14 +162,26 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
148
162
|
);
|
|
149
163
|
}
|
|
150
164
|
|
|
165
|
+
// Query finalized block on L1
|
|
166
|
+
const rawFinalizedL1Block = await getFinalizedL1Block(this.publicClient);
|
|
167
|
+
const finalizedL1Block: L1BlockId | undefined = rawFinalizedL1Block && {
|
|
168
|
+
l1BlockNumber: rawFinalizedL1Block.number,
|
|
169
|
+
l1BlockHash: Buffer32.fromString(rawFinalizedL1Block.hash),
|
|
170
|
+
};
|
|
171
|
+
|
|
151
172
|
// Load sync point for blocks defaulting to start block
|
|
152
|
-
const { blocksSynchedTo = this.l1Constants.l1StartBlock } = await this.
|
|
153
|
-
this.log.debug(`Starting new archiver sync iteration`, { blocksSynchedTo, currentL1BlockData });
|
|
173
|
+
const { blocksSynchedTo = this.l1Constants.l1StartBlock } = await getArchiverSynchPoint(this.stores);
|
|
174
|
+
this.log.debug(`Starting new archiver sync iteration`, { blocksSynchedTo, currentL1BlockData, finalizedL1Block });
|
|
154
175
|
|
|
155
176
|
// Sync L1 to L2 messages. We retry this a few times since there are error conditions that reset the sync point, requiring a new iteration.
|
|
156
177
|
// Note that we cannot just wait for the l1 synchronizer to loop again, since the synchronizer would report as synced up to the current L1
|
|
157
178
|
// block, when that wouldn't be the case, since L1 to L2 messages would need another iteration.
|
|
158
|
-
await retryTimes(
|
|
179
|
+
await retryTimes(
|
|
180
|
+
() => this.handleL1ToL2Messages(currentL1BlockData, finalizedL1Block),
|
|
181
|
+
'Handling L1 to L2 messages',
|
|
182
|
+
3,
|
|
183
|
+
0.1,
|
|
184
|
+
);
|
|
159
185
|
|
|
160
186
|
if (currentL1BlockNumber > blocksSynchedTo) {
|
|
161
187
|
// First we retrieve new checkpoints and L2 blocks and store them in the DB. This will also update the
|
|
@@ -180,7 +206,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
180
206
|
// past it, since otherwise we'll keep downloading it and reprocessing it on every iteration until
|
|
181
207
|
// we get a valid checkpoint to advance the syncpoint.
|
|
182
208
|
if (!rollupStatus.validationResult?.valid && rollupStatus.lastL1BlockWithCheckpoint !== undefined) {
|
|
183
|
-
await this.
|
|
209
|
+
await this.stores.blocks.setSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
// And lastly we check if we are missing any checkpoints behind us due to a possible L1 reorg.
|
|
@@ -195,7 +221,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
195
221
|
}
|
|
196
222
|
|
|
197
223
|
// Update the finalized L2 checkpoint based on L1 finality.
|
|
198
|
-
await this.updateFinalizedCheckpoint();
|
|
224
|
+
await this.updateFinalizedCheckpoint(finalizedL1Block);
|
|
199
225
|
|
|
200
226
|
// After syncing has completed, update the current l1 block number and timestamp,
|
|
201
227
|
// otherwise we risk announcing to the world that we've synced to a given point,
|
|
@@ -212,15 +238,18 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
212
238
|
});
|
|
213
239
|
}
|
|
214
240
|
|
|
215
|
-
/**
|
|
216
|
-
private async updateFinalizedCheckpoint(): Promise<void> {
|
|
241
|
+
/** Updates the finalized checkpoint using the pre-fetched finalized L1 block from the current sync iteration. */
|
|
242
|
+
private async updateFinalizedCheckpoint(finalizedL1Block: L1BlockId | undefined): Promise<void> {
|
|
217
243
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
244
|
+
if (!finalizedL1Block) {
|
|
245
|
+
this.log.trace(`Skipping finalized checkpoint update: L1 has no finalized block yet.`);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const finalizedL1BlockNumber = finalizedL1Block.l1BlockNumber;
|
|
220
249
|
const finalizedCheckpointNumber = await this.rollup.getProvenCheckpointNumber({
|
|
221
250
|
blockNumber: finalizedL1BlockNumber,
|
|
222
251
|
});
|
|
223
|
-
const localFinalizedCheckpointNumber = await this.
|
|
252
|
+
const localFinalizedCheckpointNumber = await this.stores.blocks.getFinalizedCheckpointNumber();
|
|
224
253
|
if (localFinalizedCheckpointNumber !== finalizedCheckpointNumber) {
|
|
225
254
|
await this.updater.setFinalizedCheckpointNumber(finalizedCheckpointNumber);
|
|
226
255
|
this.log.info(`Updated finalized chain to checkpoint ${finalizedCheckpointNumber}`, {
|
|
@@ -239,8 +268,8 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
239
268
|
/** Prune all proposed local blocks that should have been checkpointed by now. */
|
|
240
269
|
private async pruneUncheckpointedBlocks(currentL1Timestamp: bigint) {
|
|
241
270
|
const [lastCheckpointedBlockNumber, lastProposedBlockNumber] = await Promise.all([
|
|
242
|
-
this.
|
|
243
|
-
this.
|
|
271
|
+
this.stores.blocks.getCheckpointedL2BlockNumber(),
|
|
272
|
+
this.stores.blocks.getLatestL2BlockNumber(),
|
|
244
273
|
]);
|
|
245
274
|
|
|
246
275
|
// If there are no uncheckpointed blocks, we got nothing to do
|
|
@@ -254,7 +283,10 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
254
283
|
const firstUncheckpointedBlockNumber = BlockNumber(lastCheckpointedBlockNumber + 1);
|
|
255
284
|
|
|
256
285
|
// What's the slot of the first uncheckpointed block?
|
|
257
|
-
const [firstUncheckpointedBlockHeader] = await this.
|
|
286
|
+
const [firstUncheckpointedBlockHeader] = await this.stores.blocks.getBlockHeaders(
|
|
287
|
+
firstUncheckpointedBlockNumber,
|
|
288
|
+
1,
|
|
289
|
+
);
|
|
258
290
|
const firstUncheckpointedBlockSlot = firstUncheckpointedBlockHeader?.getSlot();
|
|
259
291
|
|
|
260
292
|
if (firstUncheckpointedBlockSlot === undefined || firstUncheckpointedBlockSlot >= slotAtNextL1Block) {
|
|
@@ -267,6 +299,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
267
299
|
`Pruning blocks after block ${lastCheckpointedBlockNumber} due to slot ${firstUncheckpointedBlockSlot} not being checkpointed`,
|
|
268
300
|
{ firstUncheckpointedBlockHeader: firstUncheckpointedBlockHeader.toInspect(), slotAtNextL1Block },
|
|
269
301
|
);
|
|
302
|
+
|
|
270
303
|
const prunedBlocks = await this.updater.removeUncheckpointedBlocksAfter(lastCheckpointedBlockNumber);
|
|
271
304
|
|
|
272
305
|
if (prunedBlocks.length > 0) {
|
|
@@ -300,7 +333,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
300
333
|
currentL1Timestamp: bigint,
|
|
301
334
|
): Promise<{ rollupCanPrune: boolean }> {
|
|
302
335
|
const rollupCanPrune = await this.canPrune(currentL1BlockNumber, currentL1Timestamp);
|
|
303
|
-
const localPendingCheckpointNumber = await this.
|
|
336
|
+
const localPendingCheckpointNumber = await this.stores.blocks.getLatestCheckpointNumber();
|
|
304
337
|
const canPrune = localPendingCheckpointNumber > provenCheckpointNumber && rollupCanPrune;
|
|
305
338
|
|
|
306
339
|
if (canPrune) {
|
|
@@ -321,12 +354,12 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
321
354
|
// promises when the gap between local pending and proven checkpoint numbers is large.
|
|
322
355
|
const BATCH_SIZE = 10;
|
|
323
356
|
const indices = Array.from({ length: checkpointsToUnwind }, (_, i) => CheckpointNumber(i + pruneFrom));
|
|
324
|
-
const checkpoints = (
|
|
325
|
-
|
|
326
|
-
);
|
|
357
|
+
const checkpoints = (
|
|
358
|
+
await asyncPool(BATCH_SIZE, indices, idx => this.stores.blocks.getCheckpointData(idx))
|
|
359
|
+
).filter(isDefined);
|
|
327
360
|
const newBlocks = (
|
|
328
361
|
await asyncPool(BATCH_SIZE, checkpoints, cp =>
|
|
329
|
-
this.
|
|
362
|
+
this.stores.blocks.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber)),
|
|
330
363
|
)
|
|
331
364
|
)
|
|
332
365
|
.filter(isDefined)
|
|
@@ -346,12 +379,12 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
346
379
|
this.log.warn(
|
|
347
380
|
`Removed ${count(checkpointsToUnwind, 'checkpoint')} after checkpoint ${provenCheckpointNumber} ` +
|
|
348
381
|
`due to predicted reorg at L1 block ${currentL1BlockNumber}. ` +
|
|
349
|
-
`Updated latest checkpoint is ${await this.
|
|
382
|
+
`Updated latest checkpoint is ${await this.stores.blocks.getLatestCheckpointNumber()}.`,
|
|
350
383
|
);
|
|
351
384
|
this.instrumentation.processPrune(timer.ms());
|
|
352
385
|
// TODO(palla/reorg): Do we need to set the block synched L1 block number here?
|
|
353
386
|
// Seems like the next iteration should handle this.
|
|
354
|
-
// await this.
|
|
387
|
+
// await this.stores.blocks.setSynchedL1BlockNumber(currentL1BlockNumber);
|
|
355
388
|
}
|
|
356
389
|
|
|
357
390
|
return { rollupCanPrune };
|
|
@@ -368,14 +401,17 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
368
401
|
}
|
|
369
402
|
|
|
370
403
|
@trackSpan('Archiver.handleL1ToL2Messages')
|
|
371
|
-
private async handleL1ToL2Messages(
|
|
404
|
+
private async handleL1ToL2Messages(
|
|
405
|
+
currentL1Block: L1BlockId,
|
|
406
|
+
finalizedL1Block: L1BlockId | undefined,
|
|
407
|
+
): Promise<boolean> {
|
|
372
408
|
// Load the syncpoint, which may have been updated in a previous iteration
|
|
373
409
|
const {
|
|
374
410
|
messagesSynchedTo = {
|
|
375
411
|
l1BlockNumber: this.l1Constants.l1StartBlock,
|
|
376
412
|
l1BlockHash: this.l1Constants.l1StartBlockHash,
|
|
377
413
|
},
|
|
378
|
-
} = await this.
|
|
414
|
+
} = await getArchiverSynchPoint(this.stores);
|
|
379
415
|
|
|
380
416
|
// Nothing to do if L1 block number has not moved forward
|
|
381
417
|
const currentL1BlockNumber = currentL1Block.l1BlockNumber;
|
|
@@ -385,10 +421,14 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
385
421
|
|
|
386
422
|
// Compare local message store state with the remote. If they match, we just advance the match pointer.
|
|
387
423
|
const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
|
|
388
|
-
const localLastMessage = await this.
|
|
424
|
+
const localLastMessage = await this.stores.messages.getLastMessage();
|
|
389
425
|
if (await this.localStateMatches(localLastMessage, remoteMessagesState)) {
|
|
390
426
|
this.log.trace(`Local L1 to L2 messages are already in sync with remote at L1 block ${currentL1BlockNumber}`);
|
|
391
|
-
await this.
|
|
427
|
+
await this.stores.messages.setMessageSyncState(
|
|
428
|
+
currentL1Block,
|
|
429
|
+
remoteMessagesState.treeInProgress,
|
|
430
|
+
finalizedL1Block,
|
|
431
|
+
);
|
|
392
432
|
return true;
|
|
393
433
|
}
|
|
394
434
|
|
|
@@ -404,7 +444,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
404
444
|
`Failed to store L1 to L2 messages retrieved from L1: ${error.message}. Rolling back syncpoint to retry.`,
|
|
405
445
|
{ inboxMessage: error.inboxMessage },
|
|
406
446
|
);
|
|
407
|
-
await this.rollbackL1ToL2Messages(remoteMessagesState
|
|
447
|
+
await this.rollbackL1ToL2Messages(remoteMessagesState);
|
|
408
448
|
return false;
|
|
409
449
|
}
|
|
410
450
|
throw error;
|
|
@@ -413,24 +453,28 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
413
453
|
// Note that, if there are no new messages to insert, but there was an L1 reorg that pruned out last messages,
|
|
414
454
|
// we'd notice by comparing our local state with the remote one again, and seeing they don't match even after
|
|
415
455
|
// our sync attempt. In this case, we also rollback our syncpoint, and trigger a retry.
|
|
416
|
-
const localLastMessageAfterSync = await this.
|
|
456
|
+
const localLastMessageAfterSync = await this.stores.messages.getLastMessage();
|
|
417
457
|
if (!(await this.localStateMatches(localLastMessageAfterSync, remoteMessagesState))) {
|
|
418
458
|
this.log.warn(
|
|
419
459
|
`Local L1 to L2 messages state does not match remote after sync attempt. Rolling back syncpoint to retry.`,
|
|
420
460
|
{ localLastMessageAfterSync, remoteMessagesState },
|
|
421
461
|
);
|
|
422
|
-
await this.rollbackL1ToL2Messages(remoteMessagesState
|
|
462
|
+
await this.rollbackL1ToL2Messages(remoteMessagesState);
|
|
423
463
|
return false;
|
|
424
464
|
}
|
|
425
465
|
|
|
426
466
|
// Advance the syncpoint after a successful sync
|
|
427
|
-
await this.
|
|
467
|
+
await this.stores.messages.setMessageSyncState(
|
|
468
|
+
currentL1Block,
|
|
469
|
+
remoteMessagesState.treeInProgress,
|
|
470
|
+
finalizedL1Block,
|
|
471
|
+
);
|
|
428
472
|
return true;
|
|
429
473
|
}
|
|
430
474
|
|
|
431
475
|
/** Checks if the local rolling hash and message count matches the remote state */
|
|
432
476
|
private async localStateMatches(localLastMessage: InboxMessage | undefined, remoteState: InboxContractState) {
|
|
433
|
-
const localMessageCount = await this.
|
|
477
|
+
const localMessageCount = await this.stores.messages.getTotalL1ToL2MessageCount();
|
|
434
478
|
this.log.trace(`Comparing local and remote inbox state`, { localMessageCount, localLastMessage, remoteState });
|
|
435
479
|
|
|
436
480
|
return (
|
|
@@ -452,7 +496,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
452
496
|
this.log.trace(`Retrieving L1 to L2 messages in L1 blocks ${searchStartBlock}-${searchEndBlock}`);
|
|
453
497
|
const messages = await retrieveL1ToL2Messages(this.inbox, searchStartBlock, searchEndBlock);
|
|
454
498
|
const timer = new Timer();
|
|
455
|
-
await this.
|
|
499
|
+
await this.stores.messages.addL1ToL2Messages(messages);
|
|
456
500
|
const perMsg = timer.ms() / messages.length;
|
|
457
501
|
this.instrumentation.processNewMessages(messages.length, perMsg);
|
|
458
502
|
for (const msg of messages) {
|
|
@@ -474,18 +518,49 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
474
518
|
* Rolls back local L1 to L2 messages to the last common message with L1, and updates the syncpoint to the L1 block of that message.
|
|
475
519
|
* If no common message is found, rolls back all messages and sets the syncpoint to the start block.
|
|
476
520
|
*/
|
|
477
|
-
private async rollbackL1ToL2Messages(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
521
|
+
private async rollbackL1ToL2Messages(remoteMessagesState: InboxContractState): Promise<L1BlockId> {
|
|
522
|
+
const { treeInProgress: remoteTreeInProgress, messagesRollingHash: remoteRollingHash } = remoteMessagesState;
|
|
523
|
+
|
|
524
|
+
const messagesFinalizedL1Block = await this.stores.messages.getMessagesFinalizedL1Block();
|
|
525
|
+
const finalizedL1BlockNumber = messagesFinalizedL1Block?.l1BlockNumber;
|
|
526
|
+
|
|
527
|
+
// Slowly go back through our messages until we find the last common message. We could query the logs in
|
|
528
|
+
// batch as an optimization, but the depth of the reorg should not be deep, and this is a very rare case,
|
|
529
|
+
// so it's fine to query one log at a time.
|
|
481
530
|
let commonMsg: undefined | InboxMessage;
|
|
482
531
|
let messagesToDelete = 0;
|
|
483
532
|
this.log.verbose(`Searching most recent common L1 to L2 message`);
|
|
484
|
-
for await (const localMsg of this.
|
|
533
|
+
for await (const localMsg of this.stores.messages.iterateL1ToL2Messages({ reverse: true })) {
|
|
534
|
+
const logCtx = { remoteMsg: undefined as InboxMessage | undefined, localMsg, remoteMessagesState };
|
|
535
|
+
|
|
536
|
+
// First check if the local message rolling hash matches the current rolling hash of the inbox contract,
|
|
537
|
+
// which means we just need to rollback some local messages and we should be back in sync. This means there
|
|
538
|
+
// was an L1 reorg that removed some of the messages we had, but no new messages were added compared.
|
|
539
|
+
if (localMsg.rollingHash.equals(remoteRollingHash)) {
|
|
540
|
+
this.log.info(
|
|
541
|
+
`Found common L1 to L2 message at index ${localMsg.index} on L1 block ${localMsg.l1BlockNumber} matching current remote state`,
|
|
542
|
+
logCtx,
|
|
543
|
+
);
|
|
544
|
+
commonMsg = localMsg;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Messages at or below the finalized L1 block cannot have been reorged — accept as common without querying L1.
|
|
549
|
+
if (finalizedL1BlockNumber !== undefined && localMsg.l1BlockNumber <= finalizedL1BlockNumber) {
|
|
550
|
+
this.log.info(`Found common L1 to L2 message at finalized L1 block ${localMsg.l1BlockNumber}`, logCtx);
|
|
551
|
+
commonMsg = localMsg;
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// If there's no match with the current remote state, check if the message exists on the inbox contract at all
|
|
556
|
+
// by looking at the inbox events. If the L1 reorg *added* new messages in addition to deleting existing ones,
|
|
557
|
+
// then the current remote state's rolling hash will not match anything we have locally, so we need to check existence
|
|
558
|
+
// of individual messages via logs. Note we use logs and not historical queries so we don't have to depend on
|
|
559
|
+
// an archival rpc node, since the message could be from a long time ago if we're catching up with syncing.
|
|
485
560
|
const remoteMsg = await retrieveL1ToL2Message(this.inbox, localMsg);
|
|
486
|
-
|
|
561
|
+
logCtx.remoteMsg = remoteMsg;
|
|
487
562
|
if (remoteMsg && remoteMsg.rollingHash.equals(localMsg.rollingHash)) {
|
|
488
|
-
this.log.
|
|
563
|
+
this.log.info(
|
|
489
564
|
`Found most recent common L1 to L2 message at index ${localMsg.index} on L1 block ${localMsg.l1BlockNumber}`,
|
|
490
565
|
logCtx,
|
|
491
566
|
);
|
|
@@ -505,17 +580,29 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
505
580
|
if (messagesToDelete > 0) {
|
|
506
581
|
const lastGoodIndex = commonMsg?.index;
|
|
507
582
|
this.log.warn(`Rolling back all local L1 to L2 messages after index ${lastGoodIndex ?? 'initial'}`);
|
|
508
|
-
await this.
|
|
583
|
+
await this.stores.messages.removeL1ToL2Messages(lastGoodIndex !== undefined ? lastGoodIndex + 1n : 0n);
|
|
509
584
|
}
|
|
510
585
|
|
|
511
586
|
// Update the syncpoint so the loop below reprocesses the changed messages. We go to the block before
|
|
512
587
|
// the last common one, so we force reprocessing it, in case new messages were added on that same L1 block
|
|
513
|
-
// after the last common message.
|
|
514
|
-
|
|
515
|
-
const
|
|
588
|
+
// after the last common message. Cap at the finalized L1 block: messages at or below finalized cannot
|
|
589
|
+
// have been reorged, so there is no need to walk back any further than that.
|
|
590
|
+
const syncPointL1BlockNumber = maxBigint(
|
|
591
|
+
...compactArray([
|
|
592
|
+
commonMsg ? commonMsg.l1BlockNumber - 1n : undefined,
|
|
593
|
+
finalizedL1BlockNumber,
|
|
594
|
+
this.l1Constants.l1StartBlock,
|
|
595
|
+
]),
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
const syncPointL1BlockHash =
|
|
599
|
+
syncPointL1BlockNumber === finalizedL1BlockNumber
|
|
600
|
+
? messagesFinalizedL1Block!.l1BlockHash
|
|
601
|
+
: await this.getL1BlockHash(syncPointL1BlockNumber);
|
|
602
|
+
|
|
516
603
|
const messagesSyncPoint = { l1BlockNumber: syncPointL1BlockNumber, l1BlockHash: syncPointL1BlockHash };
|
|
517
|
-
await this.
|
|
518
|
-
this.log.verbose(`Updated messages syncpoint to L1 block ${
|
|
604
|
+
await this.stores.messages.setMessageSyncState(messagesSyncPoint, remoteTreeInProgress);
|
|
605
|
+
this.log.verbose(`Updated messages syncpoint to L1 block ${messagesSyncPoint.l1BlockNumber}`, {
|
|
519
606
|
...messagesSyncPoint,
|
|
520
607
|
remoteTreeInProgress,
|
|
521
608
|
});
|
|
@@ -536,9 +623,9 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
536
623
|
currentL1BlockNumber: bigint,
|
|
537
624
|
initialSyncComplete: boolean,
|
|
538
625
|
): Promise<RollupStatus> {
|
|
539
|
-
const localPendingCheckpointNumber = await this.
|
|
626
|
+
const localPendingCheckpointNumber = await this.stores.blocks.getLatestCheckpointNumber();
|
|
540
627
|
const initialValidationResult: ValidateCheckpointResult | undefined =
|
|
541
|
-
await this.
|
|
628
|
+
await this.stores.blocks.getPendingChainValidationStatus();
|
|
542
629
|
const {
|
|
543
630
|
provenCheckpointNumber,
|
|
544
631
|
provenArchive,
|
|
@@ -568,7 +655,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
568
655
|
// we need to set it to zero. This is an edge case because we dont have a checkpoint zero (initial checkpoint is one),
|
|
569
656
|
// so localCheckpointForDestinationProvenCheckpointNumber would not be found below.
|
|
570
657
|
if (provenCheckpointNumber === 0) {
|
|
571
|
-
const localProvenCheckpointNumber = await this.
|
|
658
|
+
const localProvenCheckpointNumber = await this.stores.blocks.getProvenCheckpointNumber();
|
|
572
659
|
if (localProvenCheckpointNumber !== provenCheckpointNumber) {
|
|
573
660
|
await this.updater.setProvenCheckpointNumber(provenCheckpointNumber);
|
|
574
661
|
this.log.info(`Rolled back proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
|
|
@@ -576,11 +663,11 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
576
663
|
}
|
|
577
664
|
|
|
578
665
|
const localCheckpointForDestinationProvenCheckpointNumber =
|
|
579
|
-
await this.
|
|
666
|
+
await this.stores.blocks.getCheckpointData(provenCheckpointNumber);
|
|
580
667
|
|
|
581
668
|
// Sanity check. I've hit what seems to be a state where the proven checkpoint is set to a value greater than the latest
|
|
582
669
|
// synched checkpoint when requesting L2Tips from the archiver. This is the only place where the proven checkpoint is set.
|
|
583
|
-
const synched = await this.
|
|
670
|
+
const synched = await this.stores.blocks.getLatestCheckpointNumber();
|
|
584
671
|
if (
|
|
585
672
|
localCheckpointForDestinationProvenCheckpointNumber &&
|
|
586
673
|
synched < localCheckpointForDestinationProvenCheckpointNumber.checkpointNumber
|
|
@@ -600,7 +687,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
600
687
|
localCheckpointForDestinationProvenCheckpointNumber &&
|
|
601
688
|
provenArchive.equals(localCheckpointForDestinationProvenCheckpointNumber.archive.root)
|
|
602
689
|
) {
|
|
603
|
-
const localProvenCheckpointNumber = await this.
|
|
690
|
+
const localProvenCheckpointNumber = await this.stores.blocks.getProvenCheckpointNumber();
|
|
604
691
|
if (localProvenCheckpointNumber !== provenCheckpointNumber) {
|
|
605
692
|
await this.updater.setProvenCheckpointNumber(provenCheckpointNumber);
|
|
606
693
|
this.log.info(`Updated proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
|
|
@@ -628,7 +715,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
628
715
|
// If we have 0 checkpoints locally and there are no checkpoints onchain there is nothing to do.
|
|
629
716
|
const noCheckpoints = localPendingCheckpointNumber === 0 && pendingCheckpointNumber === 0;
|
|
630
717
|
if (noCheckpoints) {
|
|
631
|
-
await this.
|
|
718
|
+
await this.stores.blocks.setSynchedL1BlockNumber(currentL1BlockNumber);
|
|
632
719
|
this.log.debug(
|
|
633
720
|
`No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no checkpoints on chain`,
|
|
634
721
|
);
|
|
@@ -640,7 +727,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
640
727
|
// Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
|
|
641
728
|
// are any state that could be impacted by it. If we have no checkpoints, there is no impact.
|
|
642
729
|
if (localPendingCheckpointNumber > 0) {
|
|
643
|
-
const localPendingCheckpoint = await this.
|
|
730
|
+
const localPendingCheckpoint = await this.stores.blocks.getCheckpointData(localPendingCheckpointNumber);
|
|
644
731
|
if (localPendingCheckpoint === undefined) {
|
|
645
732
|
throw new Error(`Missing checkpoint ${localPendingCheckpointNumber}`);
|
|
646
733
|
}
|
|
@@ -655,7 +742,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
655
742
|
// However, in the re-org scenario, our L1 node is temporarily lying to us and we end up potentially missing checkpoints.
|
|
656
743
|
// We must only set this block number based on actually retrieved logs.
|
|
657
744
|
// TODO(#8621): Tackle this properly when we handle L1 Re-orgs.
|
|
658
|
-
// await this.
|
|
745
|
+
// await this.stores.blocks.setSynchedL1BlockNumber(currentL1BlockNumber);
|
|
659
746
|
this.log.debug(`No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
660
747
|
return rollupStatus;
|
|
661
748
|
}
|
|
@@ -675,7 +762,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
675
762
|
|
|
676
763
|
let tipAfterUnwind = localPendingCheckpointNumber;
|
|
677
764
|
while (true) {
|
|
678
|
-
const candidateCheckpoint = await this.
|
|
765
|
+
const candidateCheckpoint = await this.stores.blocks.getCheckpointData(tipAfterUnwind);
|
|
679
766
|
if (candidateCheckpoint === undefined) {
|
|
680
767
|
break;
|
|
681
768
|
}
|
|
@@ -700,7 +787,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
700
787
|
this.log.warn(
|
|
701
788
|
`Removed ${count(checkpointsToRemove, 'checkpoint')} after checkpoint ${tipAfterUnwind} ` +
|
|
702
789
|
`due to mismatched checkpoint hashes at L1 block ${currentL1BlockNumber}. ` +
|
|
703
|
-
`Updated L2 latest checkpoint is ${await this.
|
|
790
|
+
`Updated L2 latest checkpoint is ${await this.stores.blocks.getLatestCheckpointNumber()}.`,
|
|
704
791
|
);
|
|
705
792
|
}
|
|
706
793
|
}
|
|
@@ -717,22 +804,20 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
717
804
|
|
|
718
805
|
this.log.trace(`Retrieving checkpoints from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
719
806
|
|
|
720
|
-
//
|
|
721
|
-
const
|
|
722
|
-
|
|
807
|
+
// First fetch calldata only, no blobs yet, since we may be able to just get that data out of the proposed chain
|
|
808
|
+
const calldataCheckpoints = await execInSpan(this.tracer, 'Archiver.retrieveCheckpointCalldataFromRollup', () =>
|
|
809
|
+
retrieveCheckpointCalldataFromRollup(
|
|
723
810
|
this.rollup,
|
|
724
811
|
this.publicClient,
|
|
725
812
|
this.debugClient,
|
|
726
|
-
this.blobClient,
|
|
727
813
|
searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
|
|
728
814
|
searchEndBlock,
|
|
729
815
|
this.instrumentation,
|
|
730
816
|
this.log,
|
|
731
|
-
!initialSyncComplete, // isHistoricalSync
|
|
732
817
|
),
|
|
733
818
|
);
|
|
734
819
|
|
|
735
|
-
if (
|
|
820
|
+
if (calldataCheckpoints.length === 0) {
|
|
736
821
|
// We are not calling `setBlockSynchedL1BlockNumber` because it may cause sync issues if based off infura.
|
|
737
822
|
// See further details in earlier comments.
|
|
738
823
|
this.log.trace(`Retrieved no new checkpoints from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
@@ -740,21 +825,56 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
740
825
|
}
|
|
741
826
|
|
|
742
827
|
this.log.debug(
|
|
743
|
-
`Retrieved ${
|
|
828
|
+
`Retrieved ${calldataCheckpoints.length} new checkpoint calldata between L1 blocks ${searchStartBlock} and ${searchEndBlock}`,
|
|
744
829
|
{
|
|
745
|
-
lastProcessedCheckpoint:
|
|
830
|
+
lastProcessedCheckpoint: calldataCheckpoints[calldataCheckpoints.length - 1].l1,
|
|
746
831
|
searchStartBlock,
|
|
747
832
|
searchEndBlock,
|
|
748
833
|
},
|
|
749
834
|
);
|
|
750
835
|
|
|
751
|
-
|
|
836
|
+
// Check if the last checkpoint matches a local pending entry (so we can skip blob fetch).
|
|
837
|
+
// We only check the last one; if it matches, the blob fetch is skipped for that entry.
|
|
838
|
+
// TODO(palla/pipelining): We may have more than a single checkpoint to promote
|
|
839
|
+
const lastCalldataCheckpoint = calldataCheckpoints[calldataCheckpoints.length - 1];
|
|
840
|
+
const promoteResult = await this.tryBuildPublishedCheckpointFromProposed(lastCalldataCheckpoint);
|
|
841
|
+
const checkpointToPromote = promoteResult && !('diverged' in promoteResult) ? promoteResult : undefined;
|
|
842
|
+
const evictProposedFrom =
|
|
843
|
+
promoteResult && 'diverged' in promoteResult ? promoteResult.fromCheckpointNumber : undefined;
|
|
844
|
+
|
|
845
|
+
// Then fetch blobs in parallel and build the full published checkpoints
|
|
846
|
+
const toFetchBlobs = checkpointToPromote ? calldataCheckpoints.slice(0, -1) : calldataCheckpoints;
|
|
847
|
+
const blobFetched = await asyncPool(10, toFetchBlobs, async checkpoint =>
|
|
848
|
+
retrievedToPublishedCheckpoint({
|
|
849
|
+
...checkpoint,
|
|
850
|
+
checkpointBlobData: await getCheckpointBlobDataFromBlobs(
|
|
851
|
+
this.blobClient,
|
|
852
|
+
checkpoint.l1.blockHash,
|
|
853
|
+
checkpoint.blobHashes,
|
|
854
|
+
checkpoint.checkpointNumber,
|
|
855
|
+
this.log,
|
|
856
|
+
!initialSyncComplete,
|
|
857
|
+
checkpoint.parentBeaconBlockRoot,
|
|
858
|
+
checkpoint.l1.timestamp,
|
|
859
|
+
),
|
|
860
|
+
}),
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// And add the promoted checkpoint to the list of all checkpoints
|
|
864
|
+
const publishedCheckpoints = checkpointToPromote ? [...blobFetched, checkpointToPromote] : blobFetched;
|
|
752
865
|
const validCheckpoints: PublishedCheckpoint[] = [];
|
|
753
866
|
|
|
867
|
+
// Now loop through all checkpoints and validate their attestations
|
|
754
868
|
for (const published of publishedCheckpoints) {
|
|
755
869
|
const validationResult = this.config.skipValidateCheckpointAttestations
|
|
756
870
|
? { valid: true as const }
|
|
757
|
-
: await validateCheckpointAttestations(
|
|
871
|
+
: await validateCheckpointAttestations(
|
|
872
|
+
published,
|
|
873
|
+
this.epochCache,
|
|
874
|
+
this.l1Constants,
|
|
875
|
+
this.getSignatureContext(),
|
|
876
|
+
this.log,
|
|
877
|
+
);
|
|
758
878
|
|
|
759
879
|
// Only update the validation result if it has changed, so we can keep track of the first invalid checkpoint
|
|
760
880
|
// in case there is a sequence of more than one invalid checkpoint, as we need to invalidate the first one.
|
|
@@ -792,7 +912,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
792
912
|
// Check the inHash of the checkpoint against the l1->l2 messages.
|
|
793
913
|
// The messages should've been synced up to the currentL1BlockNumber and must be available for the published
|
|
794
914
|
// checkpoints we just retrieved.
|
|
795
|
-
const l1ToL2Messages = await this.
|
|
915
|
+
const l1ToL2Messages = await this.stores.messages.getL1ToL2Messages(published.checkpoint.number);
|
|
796
916
|
const computedInHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
797
917
|
const publishedInHash = published.checkpoint.header.inHash;
|
|
798
918
|
if (!computedInHash.equals(publishedInHash)) {
|
|
@@ -831,15 +951,35 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
831
951
|
try {
|
|
832
952
|
const updatedValidationResult =
|
|
833
953
|
rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
|
|
954
|
+
|
|
955
|
+
// Split valid checkpoints: the promoted one (if any) is persisted via the proposed-promotion path,
|
|
956
|
+
// the rest via addCheckpoints. Both paths run within the same store transaction for atomicity.
|
|
957
|
+
const [[maybeValidCheckpointToPromote], checkpointsToAdd] = partition(
|
|
958
|
+
validCheckpoints,
|
|
959
|
+
c => c.checkpoint.number === checkpointToPromote?.checkpoint.number,
|
|
960
|
+
);
|
|
961
|
+
|
|
834
962
|
const [processDuration, result] = await elapsed(() =>
|
|
835
963
|
execInSpan(this.tracer, 'Archiver.addCheckpoints', () =>
|
|
836
|
-
this.updater.addCheckpoints(
|
|
964
|
+
this.updater.addCheckpoints(
|
|
965
|
+
checkpointsToAdd,
|
|
966
|
+
updatedValidationResult,
|
|
967
|
+
maybeValidCheckpointToPromote && {
|
|
968
|
+
l1: lastCalldataCheckpoint.l1,
|
|
969
|
+
attestations: lastCalldataCheckpoint.attestations,
|
|
970
|
+
checkpoint: maybeValidCheckpointToPromote,
|
|
971
|
+
},
|
|
972
|
+
evictProposedFrom,
|
|
973
|
+
),
|
|
837
974
|
),
|
|
838
975
|
);
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
976
|
+
|
|
977
|
+
if (checkpointsToAdd.length > 0) {
|
|
978
|
+
this.instrumentation.processNewCheckpointedBlocks(
|
|
979
|
+
processDuration / checkpointsToAdd.length,
|
|
980
|
+
checkpointsToAdd.flatMap(c => c.checkpoint.blocks),
|
|
981
|
+
);
|
|
982
|
+
}
|
|
843
983
|
|
|
844
984
|
// If blocks were pruned due to conflict with L1 checkpoints, emit event
|
|
845
985
|
if (result.prunedBlocks && result.prunedBlocks.length > 0) {
|
|
@@ -864,10 +1004,10 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
864
1004
|
if (err instanceof InitialCheckpointNumberNotSequentialError) {
|
|
865
1005
|
const { previousCheckpointNumber, newCheckpointNumber } = err;
|
|
866
1006
|
const previousCheckpoint = previousCheckpointNumber
|
|
867
|
-
? await this.
|
|
1007
|
+
? await this.stores.blocks.getCheckpointData(CheckpointNumber(previousCheckpointNumber))
|
|
868
1008
|
: undefined;
|
|
869
1009
|
const updatedL1SyncPoint = previousCheckpoint?.l1.blockNumber ?? this.l1Constants.l1StartBlock;
|
|
870
|
-
await this.
|
|
1010
|
+
await this.stores.blocks.setSynchedL1BlockNumber(updatedL1SyncPoint);
|
|
871
1011
|
this.log.warn(
|
|
872
1012
|
`Attempting to insert checkpoint ${newCheckpointNumber} with previous block ${previousCheckpointNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
|
|
873
1013
|
{
|
|
@@ -892,7 +1032,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
892
1032
|
});
|
|
893
1033
|
}
|
|
894
1034
|
lastRetrievedCheckpoint = validCheckpoints.at(-1) ?? lastRetrievedCheckpoint;
|
|
895
|
-
lastL1BlockWithCheckpoint =
|
|
1035
|
+
lastL1BlockWithCheckpoint = calldataCheckpoints.at(-1)?.l1.blockNumber ?? lastL1BlockWithCheckpoint;
|
|
896
1036
|
} while (searchEndBlock < currentL1BlockNumber);
|
|
897
1037
|
|
|
898
1038
|
// Important that we update AFTER inserting the blocks.
|
|
@@ -901,6 +1041,82 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
901
1041
|
return { ...rollupStatus, lastRetrievedCheckpoint, lastL1BlockWithCheckpoint };
|
|
902
1042
|
}
|
|
903
1043
|
|
|
1044
|
+
/**
|
|
1045
|
+
* Checks if a specific checkpoint matches a local pending entry, and if so, loads local data to build
|
|
1046
|
+
* a synthetic published checkpoint (skipping blob fetch).
|
|
1047
|
+
*
|
|
1048
|
+
* Returns { diverged: true, fromCheckpointNumber } when the L1 checkpoint does NOT match local pending
|
|
1049
|
+
* data for that number, so the caller can evict the entire pending suffix >= fromCheckpointNumber
|
|
1050
|
+
* (those entries chain off the now-invalid local state) within the same addCheckpoints transaction.
|
|
1051
|
+
*/
|
|
1052
|
+
private async tryBuildPublishedCheckpointFromProposed(
|
|
1053
|
+
calldataCheckpoint: RetrievedCheckpointFromCalldata | undefined,
|
|
1054
|
+
): Promise<PublishedCheckpoint | { diverged: true; fromCheckpointNumber: CheckpointNumber } | undefined> {
|
|
1055
|
+
if (this.config.skipPromoteProposedCheckpointDuringL1Sync || !calldataCheckpoint) {
|
|
1056
|
+
return undefined;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// Look up the specific pending entry for the checkpoint being mined, not just the tip
|
|
1060
|
+
const proposed = await this.stores.blocks.getProposedCheckpointByNumber(calldataCheckpoint.checkpointNumber);
|
|
1061
|
+
if (!proposed) {
|
|
1062
|
+
return undefined;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (
|
|
1066
|
+
!proposed.header.equals(calldataCheckpoint.header) ||
|
|
1067
|
+
!proposed.archive.root.equals(calldataCheckpoint.archiveRoot)
|
|
1068
|
+
) {
|
|
1069
|
+
this.log.warn(
|
|
1070
|
+
`Local proposed checkpoint ${proposed.checkpointNumber} does not match checkpoint retrieved from L1, overriding with L1 data`,
|
|
1071
|
+
{
|
|
1072
|
+
proposedCheckpointNumber: proposed.checkpointNumber,
|
|
1073
|
+
proposedHeader: proposed.header.toInspect(),
|
|
1074
|
+
proposedArchiveRoot: proposed.archive.root.toString(),
|
|
1075
|
+
calldataCheckpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1076
|
+
calldataHeader: calldataCheckpoint.header.toInspect(),
|
|
1077
|
+
calldataArchiveRoot: calldataCheckpoint.archiveRoot.toString(),
|
|
1078
|
+
},
|
|
1079
|
+
);
|
|
1080
|
+
// Return a divergence signal so the caller can evict pending >= this number
|
|
1081
|
+
return { diverged: true, fromCheckpointNumber: proposed.checkpointNumber };
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
this.log.debug(
|
|
1085
|
+
`Building published checkpoint from proposed ${calldataCheckpoint.checkpointNumber} (skipping blob fetch)`,
|
|
1086
|
+
{ proposedHeader: proposed.header.toInspect(), proposedArchiveRoot: proposed.archive.root.toString() },
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
const blocks = await this.stores.blocks.getBlocks(BlockNumber(proposed.startBlock), proposed.blockCount);
|
|
1090
|
+
if (blocks.length !== proposed.blockCount) {
|
|
1091
|
+
this.log.warn(
|
|
1092
|
+
`Local proposed checkpoint ${proposed.checkpointNumber} has wrong block count (expected ${proposed.blockCount} blocks starting at ${proposed.startBlock} but got ${blocks.length})`,
|
|
1093
|
+
{
|
|
1094
|
+
proposedCheckpointNumber: proposed.checkpointNumber,
|
|
1095
|
+
proposedStartBlock: proposed.startBlock,
|
|
1096
|
+
proposedBlockCount: proposed.blockCount,
|
|
1097
|
+
retrievedBlocks: blocks.map(b => b.number),
|
|
1098
|
+
},
|
|
1099
|
+
);
|
|
1100
|
+
return undefined;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const checkpoint = Checkpoint.from({
|
|
1104
|
+
archive: proposed.archive,
|
|
1105
|
+
header: proposed.header,
|
|
1106
|
+
blocks,
|
|
1107
|
+
number: proposed.checkpointNumber,
|
|
1108
|
+
feeAssetPriceModifier: proposed.feeAssetPriceModifier,
|
|
1109
|
+
});
|
|
1110
|
+
const promotedCheckpoint = PublishedCheckpoint.from({
|
|
1111
|
+
checkpoint,
|
|
1112
|
+
l1: calldataCheckpoint.l1,
|
|
1113
|
+
attestations: calldataCheckpoint.attestations,
|
|
1114
|
+
});
|
|
1115
|
+
this.instrumentation.processCheckpointPromoted();
|
|
1116
|
+
|
|
1117
|
+
return promotedCheckpoint;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
904
1120
|
private async checkForNewCheckpointsBeforeL1SyncPoint(
|
|
905
1121
|
status: RollupStatus,
|
|
906
1122
|
blocksSynchedTo: bigint,
|
|
@@ -910,7 +1126,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
910
1126
|
// Compare the last checkpoint we have (either retrieved in this round or loaded from store) with what the
|
|
911
1127
|
// rollup contract told us was the latest one (pinned at the currentL1BlockNumber).
|
|
912
1128
|
const latestLocalCheckpointNumber =
|
|
913
|
-
lastRetrievedCheckpoint?.checkpoint.number ?? (await this.
|
|
1129
|
+
lastRetrievedCheckpoint?.checkpoint.number ?? (await this.stores.blocks.getLatestCheckpointNumber());
|
|
914
1130
|
if (latestLocalCheckpointNumber < pendingCheckpointNumber) {
|
|
915
1131
|
// Here we have consumed all logs until the `currentL1Block` we pinned at the beginning of the archiver loop,
|
|
916
1132
|
// but still haven't reached the pending checkpoint according to the call to the rollup contract.
|
|
@@ -923,7 +1139,9 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
923
1139
|
latestLocalCheckpointArchive = lastRetrievedCheckpoint.checkpoint.archive.root.toString();
|
|
924
1140
|
targetL1BlockNumber = lastRetrievedCheckpoint.l1.blockNumber;
|
|
925
1141
|
} else if (latestLocalCheckpointNumber > 0) {
|
|
926
|
-
const checkpoint = await this.
|
|
1142
|
+
const checkpoint = await this.stores.blocks
|
|
1143
|
+
.getRangeOfCheckpoints(latestLocalCheckpointNumber, 1)
|
|
1144
|
+
.then(([c]) => c);
|
|
927
1145
|
latestLocalCheckpointArchive = checkpoint.archive.root.toString();
|
|
928
1146
|
targetL1BlockNumber = checkpoint.l1.blockNumber;
|
|
929
1147
|
}
|
|
@@ -938,7 +1156,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
938
1156
|
...status,
|
|
939
1157
|
},
|
|
940
1158
|
);
|
|
941
|
-
await this.
|
|
1159
|
+
await this.stores.blocks.setSynchedL1BlockNumber(targetL1BlockNumber);
|
|
942
1160
|
} else {
|
|
943
1161
|
this.log.trace(`No new checkpoints behind L1 sync point to retrieve.`, {
|
|
944
1162
|
latestLocalCheckpointNumber,
|
|
@@ -948,7 +1166,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
948
1166
|
}
|
|
949
1167
|
|
|
950
1168
|
private async getCheckpointHeader(number: CheckpointNumber) {
|
|
951
|
-
const checkpoint = await this.
|
|
1169
|
+
const checkpoint = await this.stores.blocks.getCheckpointData(number);
|
|
952
1170
|
if (!checkpoint) {
|
|
953
1171
|
return undefined;
|
|
954
1172
|
}
|