@aztec/archiver 0.0.1-commit.2b2662070 → 0.0.1-commit.2c0ee1788
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 +17 -10
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +92 -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 +4 -1
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +5 -0
- 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 +182 -70
- 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 +59 -21
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +181 -66
- 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.js +1 -1
- package/dest/test/fake_l1_state.d.ts +7 -3
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +42 -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 -6
- package/package.json +13 -13
- package/src/archiver.ts +108 -50
- 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 +8 -0
- package/src/modules/l1_synchronizer.ts +241 -71
- package/src/modules/validation.ts +8 -7
- package/src/store/block_store.ts +204 -73
- 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 +1 -1
- package/src/test/fake_l1_state.ts +47 -24
- package/src/test/mock_l2_block_source.ts +23 -2
- package/src/test/noop_l1_archiver.ts +9 -6
- 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 { 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;
|
|
@@ -149,7 +163,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
// Load sync point for blocks defaulting to start block
|
|
152
|
-
const { blocksSynchedTo = this.l1Constants.l1StartBlock } = await this.
|
|
166
|
+
const { blocksSynchedTo = this.l1Constants.l1StartBlock } = await getArchiverSynchPoint(this.stores);
|
|
153
167
|
this.log.debug(`Starting new archiver sync iteration`, { blocksSynchedTo, currentL1BlockData });
|
|
154
168
|
|
|
155
169
|
// 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.
|
|
@@ -180,7 +194,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
180
194
|
// past it, since otherwise we'll keep downloading it and reprocessing it on every iteration until
|
|
181
195
|
// we get a valid checkpoint to advance the syncpoint.
|
|
182
196
|
if (!rollupStatus.validationResult?.valid && rollupStatus.lastL1BlockWithCheckpoint !== undefined) {
|
|
183
|
-
await this.
|
|
197
|
+
await this.stores.blocks.setSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
|
|
184
198
|
}
|
|
185
199
|
|
|
186
200
|
// And lastly we check if we are missing any checkpoints behind us due to a possible L1 reorg.
|
|
@@ -215,12 +229,16 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
215
229
|
/** Query L1 for its finalized block and update the finalized checkpoint accordingly. */
|
|
216
230
|
private async updateFinalizedCheckpoint(): Promise<void> {
|
|
217
231
|
try {
|
|
218
|
-
const finalizedL1Block = await this.publicClient
|
|
232
|
+
const finalizedL1Block = await getFinalizedL1Block(this.publicClient);
|
|
233
|
+
if (!finalizedL1Block) {
|
|
234
|
+
this.log.trace(`Skipping finalized checkpoint update: L1 has no finalized block yet.`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
219
237
|
const finalizedL1BlockNumber = finalizedL1Block.number;
|
|
220
238
|
const finalizedCheckpointNumber = await this.rollup.getProvenCheckpointNumber({
|
|
221
239
|
blockNumber: finalizedL1BlockNumber,
|
|
222
240
|
});
|
|
223
|
-
const localFinalizedCheckpointNumber = await this.
|
|
241
|
+
const localFinalizedCheckpointNumber = await this.stores.blocks.getFinalizedCheckpointNumber();
|
|
224
242
|
if (localFinalizedCheckpointNumber !== finalizedCheckpointNumber) {
|
|
225
243
|
await this.updater.setFinalizedCheckpointNumber(finalizedCheckpointNumber);
|
|
226
244
|
this.log.info(`Updated finalized chain to checkpoint ${finalizedCheckpointNumber}`, {
|
|
@@ -239,8 +257,8 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
239
257
|
/** Prune all proposed local blocks that should have been checkpointed by now. */
|
|
240
258
|
private async pruneUncheckpointedBlocks(currentL1Timestamp: bigint) {
|
|
241
259
|
const [lastCheckpointedBlockNumber, lastProposedBlockNumber] = await Promise.all([
|
|
242
|
-
this.
|
|
243
|
-
this.
|
|
260
|
+
this.stores.blocks.getCheckpointedL2BlockNumber(),
|
|
261
|
+
this.stores.blocks.getLatestL2BlockNumber(),
|
|
244
262
|
]);
|
|
245
263
|
|
|
246
264
|
// If there are no uncheckpointed blocks, we got nothing to do
|
|
@@ -254,7 +272,10 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
254
272
|
const firstUncheckpointedBlockNumber = BlockNumber(lastCheckpointedBlockNumber + 1);
|
|
255
273
|
|
|
256
274
|
// What's the slot of the first uncheckpointed block?
|
|
257
|
-
const [firstUncheckpointedBlockHeader] = await this.
|
|
275
|
+
const [firstUncheckpointedBlockHeader] = await this.stores.blocks.getBlockHeaders(
|
|
276
|
+
firstUncheckpointedBlockNumber,
|
|
277
|
+
1,
|
|
278
|
+
);
|
|
258
279
|
const firstUncheckpointedBlockSlot = firstUncheckpointedBlockHeader?.getSlot();
|
|
259
280
|
|
|
260
281
|
if (firstUncheckpointedBlockSlot === undefined || firstUncheckpointedBlockSlot >= slotAtNextL1Block) {
|
|
@@ -301,7 +322,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
301
322
|
currentL1Timestamp: bigint,
|
|
302
323
|
): Promise<{ rollupCanPrune: boolean }> {
|
|
303
324
|
const rollupCanPrune = await this.canPrune(currentL1BlockNumber, currentL1Timestamp);
|
|
304
|
-
const localPendingCheckpointNumber = await this.
|
|
325
|
+
const localPendingCheckpointNumber = await this.stores.blocks.getLatestCheckpointNumber();
|
|
305
326
|
const canPrune = localPendingCheckpointNumber > provenCheckpointNumber && rollupCanPrune;
|
|
306
327
|
|
|
307
328
|
if (canPrune) {
|
|
@@ -322,12 +343,12 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
322
343
|
// promises when the gap between local pending and proven checkpoint numbers is large.
|
|
323
344
|
const BATCH_SIZE = 10;
|
|
324
345
|
const indices = Array.from({ length: checkpointsToUnwind }, (_, i) => CheckpointNumber(i + pruneFrom));
|
|
325
|
-
const checkpoints = (
|
|
326
|
-
|
|
327
|
-
);
|
|
346
|
+
const checkpoints = (
|
|
347
|
+
await asyncPool(BATCH_SIZE, indices, idx => this.stores.blocks.getCheckpointData(idx))
|
|
348
|
+
).filter(isDefined);
|
|
328
349
|
const newBlocks = (
|
|
329
350
|
await asyncPool(BATCH_SIZE, checkpoints, cp =>
|
|
330
|
-
this.
|
|
351
|
+
this.stores.blocks.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber)),
|
|
331
352
|
)
|
|
332
353
|
)
|
|
333
354
|
.filter(isDefined)
|
|
@@ -347,12 +368,12 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
347
368
|
this.log.warn(
|
|
348
369
|
`Removed ${count(checkpointsToUnwind, 'checkpoint')} after checkpoint ${provenCheckpointNumber} ` +
|
|
349
370
|
`due to predicted reorg at L1 block ${currentL1BlockNumber}. ` +
|
|
350
|
-
`Updated latest checkpoint is ${await this.
|
|
371
|
+
`Updated latest checkpoint is ${await this.stores.blocks.getLatestCheckpointNumber()}.`,
|
|
351
372
|
);
|
|
352
373
|
this.instrumentation.processPrune(timer.ms());
|
|
353
374
|
// TODO(palla/reorg): Do we need to set the block synched L1 block number here?
|
|
354
375
|
// Seems like the next iteration should handle this.
|
|
355
|
-
// await this.
|
|
376
|
+
// await this.stores.blocks.setSynchedL1BlockNumber(currentL1BlockNumber);
|
|
356
377
|
}
|
|
357
378
|
|
|
358
379
|
return { rollupCanPrune };
|
|
@@ -376,7 +397,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
376
397
|
l1BlockNumber: this.l1Constants.l1StartBlock,
|
|
377
398
|
l1BlockHash: this.l1Constants.l1StartBlockHash,
|
|
378
399
|
},
|
|
379
|
-
} = await this.
|
|
400
|
+
} = await getArchiverSynchPoint(this.stores);
|
|
380
401
|
|
|
381
402
|
// Nothing to do if L1 block number has not moved forward
|
|
382
403
|
const currentL1BlockNumber = currentL1Block.l1BlockNumber;
|
|
@@ -386,10 +407,10 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
386
407
|
|
|
387
408
|
// Compare local message store state with the remote. If they match, we just advance the match pointer.
|
|
388
409
|
const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
|
|
389
|
-
const localLastMessage = await this.
|
|
410
|
+
const localLastMessage = await this.stores.messages.getLastMessage();
|
|
390
411
|
if (await this.localStateMatches(localLastMessage, remoteMessagesState)) {
|
|
391
412
|
this.log.trace(`Local L1 to L2 messages are already in sync with remote at L1 block ${currentL1BlockNumber}`);
|
|
392
|
-
await this.
|
|
413
|
+
await this.stores.messages.setMessageSyncState(currentL1Block, remoteMessagesState.treeInProgress);
|
|
393
414
|
return true;
|
|
394
415
|
}
|
|
395
416
|
|
|
@@ -405,7 +426,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
405
426
|
`Failed to store L1 to L2 messages retrieved from L1: ${error.message}. Rolling back syncpoint to retry.`,
|
|
406
427
|
{ inboxMessage: error.inboxMessage },
|
|
407
428
|
);
|
|
408
|
-
await this.rollbackL1ToL2Messages(remoteMessagesState
|
|
429
|
+
await this.rollbackL1ToL2Messages(remoteMessagesState);
|
|
409
430
|
return false;
|
|
410
431
|
}
|
|
411
432
|
throw error;
|
|
@@ -414,24 +435,24 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
414
435
|
// Note that, if there are no new messages to insert, but there was an L1 reorg that pruned out last messages,
|
|
415
436
|
// we'd notice by comparing our local state with the remote one again, and seeing they don't match even after
|
|
416
437
|
// our sync attempt. In this case, we also rollback our syncpoint, and trigger a retry.
|
|
417
|
-
const localLastMessageAfterSync = await this.
|
|
438
|
+
const localLastMessageAfterSync = await this.stores.messages.getLastMessage();
|
|
418
439
|
if (!(await this.localStateMatches(localLastMessageAfterSync, remoteMessagesState))) {
|
|
419
440
|
this.log.warn(
|
|
420
441
|
`Local L1 to L2 messages state does not match remote after sync attempt. Rolling back syncpoint to retry.`,
|
|
421
442
|
{ localLastMessageAfterSync, remoteMessagesState },
|
|
422
443
|
);
|
|
423
|
-
await this.rollbackL1ToL2Messages(remoteMessagesState
|
|
444
|
+
await this.rollbackL1ToL2Messages(remoteMessagesState);
|
|
424
445
|
return false;
|
|
425
446
|
}
|
|
426
447
|
|
|
427
448
|
// Advance the syncpoint after a successful sync
|
|
428
|
-
await this.
|
|
449
|
+
await this.stores.messages.setMessageSyncState(currentL1Block, remoteMessagesState.treeInProgress);
|
|
429
450
|
return true;
|
|
430
451
|
}
|
|
431
452
|
|
|
432
453
|
/** Checks if the local rolling hash and message count matches the remote state */
|
|
433
454
|
private async localStateMatches(localLastMessage: InboxMessage | undefined, remoteState: InboxContractState) {
|
|
434
|
-
const localMessageCount = await this.
|
|
455
|
+
const localMessageCount = await this.stores.messages.getTotalL1ToL2MessageCount();
|
|
435
456
|
this.log.trace(`Comparing local and remote inbox state`, { localMessageCount, localLastMessage, remoteState });
|
|
436
457
|
|
|
437
458
|
return (
|
|
@@ -453,7 +474,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
453
474
|
this.log.trace(`Retrieving L1 to L2 messages in L1 blocks ${searchStartBlock}-${searchEndBlock}`);
|
|
454
475
|
const messages = await retrieveL1ToL2Messages(this.inbox, searchStartBlock, searchEndBlock);
|
|
455
476
|
const timer = new Timer();
|
|
456
|
-
await this.
|
|
477
|
+
await this.stores.messages.addL1ToL2Messages(messages);
|
|
457
478
|
const perMsg = timer.ms() / messages.length;
|
|
458
479
|
this.instrumentation.processNewMessages(messages.length, perMsg);
|
|
459
480
|
for (const msg of messages) {
|
|
@@ -475,18 +496,39 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
475
496
|
* 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.
|
|
476
497
|
* If no common message is found, rolls back all messages and sets the syncpoint to the start block.
|
|
477
498
|
*/
|
|
478
|
-
private async rollbackL1ToL2Messages(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
//
|
|
499
|
+
private async rollbackL1ToL2Messages(remoteMessagesState: InboxContractState): Promise<L1BlockId> {
|
|
500
|
+
const { treeInProgress: remoteTreeInProgress, messagesRollingHash: remoteRollingHash } = remoteMessagesState;
|
|
501
|
+
|
|
502
|
+
// Slowly go back through our messages until we find the last common message. We could query the logs in
|
|
503
|
+
// batch as an optimization, but the depth of the reorg should not be deep, and this is a very rare case,
|
|
504
|
+
// so it's fine to query one log at a time.
|
|
482
505
|
let commonMsg: undefined | InboxMessage;
|
|
483
506
|
let messagesToDelete = 0;
|
|
484
507
|
this.log.verbose(`Searching most recent common L1 to L2 message`);
|
|
485
|
-
for await (const localMsg of this.
|
|
508
|
+
for await (const localMsg of this.stores.messages.iterateL1ToL2Messages({ reverse: true })) {
|
|
509
|
+
const logCtx = { remoteMsg: undefined as InboxMessage | undefined, localMsg, remoteMessagesState };
|
|
510
|
+
|
|
511
|
+
// First check if the local message rolling hash matches the current rolling hash of the inbox contract,
|
|
512
|
+
// which means we just need to rollback some local messages and we should be back in sync. This means there
|
|
513
|
+
// was an L1 reorg that removed some of the messages we had, but no new messages were added compared.
|
|
514
|
+
if (localMsg.rollingHash.equals(remoteRollingHash)) {
|
|
515
|
+
this.log.info(
|
|
516
|
+
`Found common L1 to L2 message at index ${localMsg.index} on L1 block ${localMsg.l1BlockNumber} matching current remote state`,
|
|
517
|
+
logCtx,
|
|
518
|
+
);
|
|
519
|
+
commonMsg = localMsg;
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// If there's no match with the current remote state, check if the message exists on the inbox contract at all
|
|
524
|
+
// by looking at the inbox events. If the L1 reorg *added* new messages in addition to deleting existing ones,
|
|
525
|
+
// then the current remote state's rolling hash will not match anything we have locally, so we need to check existence
|
|
526
|
+
// of individual messages via logs. Note we use logs and not historical queries so we don't have to depend on
|
|
527
|
+
// an archival rpc node, since the message could be from a long time ago if we're catching up with syncing.
|
|
486
528
|
const remoteMsg = await retrieveL1ToL2Message(this.inbox, localMsg);
|
|
487
|
-
|
|
529
|
+
logCtx.remoteMsg = remoteMsg;
|
|
488
530
|
if (remoteMsg && remoteMsg.rollingHash.equals(localMsg.rollingHash)) {
|
|
489
|
-
this.log.
|
|
531
|
+
this.log.info(
|
|
490
532
|
`Found most recent common L1 to L2 message at index ${localMsg.index} on L1 block ${localMsg.l1BlockNumber}`,
|
|
491
533
|
logCtx,
|
|
492
534
|
);
|
|
@@ -506,7 +548,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
506
548
|
if (messagesToDelete > 0) {
|
|
507
549
|
const lastGoodIndex = commonMsg?.index;
|
|
508
550
|
this.log.warn(`Rolling back all local L1 to L2 messages after index ${lastGoodIndex ?? 'initial'}`);
|
|
509
|
-
await this.
|
|
551
|
+
await this.stores.messages.removeL1ToL2Messages(lastGoodIndex !== undefined ? lastGoodIndex + 1n : 0n);
|
|
510
552
|
}
|
|
511
553
|
|
|
512
554
|
// Update the syncpoint so the loop below reprocesses the changed messages. We go to the block before
|
|
@@ -515,7 +557,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
515
557
|
const syncPointL1BlockNumber = commonMsg ? commonMsg.l1BlockNumber - 1n : this.l1Constants.l1StartBlock;
|
|
516
558
|
const syncPointL1BlockHash = await this.getL1BlockHash(syncPointL1BlockNumber);
|
|
517
559
|
const messagesSyncPoint = { l1BlockNumber: syncPointL1BlockNumber, l1BlockHash: syncPointL1BlockHash };
|
|
518
|
-
await this.
|
|
560
|
+
await this.stores.messages.setMessageSyncState(messagesSyncPoint, remoteTreeInProgress);
|
|
519
561
|
this.log.verbose(`Updated messages syncpoint to L1 block ${syncPointL1BlockNumber}`, {
|
|
520
562
|
...messagesSyncPoint,
|
|
521
563
|
remoteTreeInProgress,
|
|
@@ -537,9 +579,9 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
537
579
|
currentL1BlockNumber: bigint,
|
|
538
580
|
initialSyncComplete: boolean,
|
|
539
581
|
): Promise<RollupStatus> {
|
|
540
|
-
const localPendingCheckpointNumber = await this.
|
|
582
|
+
const localPendingCheckpointNumber = await this.stores.blocks.getLatestCheckpointNumber();
|
|
541
583
|
const initialValidationResult: ValidateCheckpointResult | undefined =
|
|
542
|
-
await this.
|
|
584
|
+
await this.stores.blocks.getPendingChainValidationStatus();
|
|
543
585
|
const {
|
|
544
586
|
provenCheckpointNumber,
|
|
545
587
|
provenArchive,
|
|
@@ -569,7 +611,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
569
611
|
// we need to set it to zero. This is an edge case because we dont have a checkpoint zero (initial checkpoint is one),
|
|
570
612
|
// so localCheckpointForDestinationProvenCheckpointNumber would not be found below.
|
|
571
613
|
if (provenCheckpointNumber === 0) {
|
|
572
|
-
const localProvenCheckpointNumber = await this.
|
|
614
|
+
const localProvenCheckpointNumber = await this.stores.blocks.getProvenCheckpointNumber();
|
|
573
615
|
if (localProvenCheckpointNumber !== provenCheckpointNumber) {
|
|
574
616
|
await this.updater.setProvenCheckpointNumber(provenCheckpointNumber);
|
|
575
617
|
this.log.info(`Rolled back proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
|
|
@@ -577,11 +619,11 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
577
619
|
}
|
|
578
620
|
|
|
579
621
|
const localCheckpointForDestinationProvenCheckpointNumber =
|
|
580
|
-
await this.
|
|
622
|
+
await this.stores.blocks.getCheckpointData(provenCheckpointNumber);
|
|
581
623
|
|
|
582
624
|
// Sanity check. I've hit what seems to be a state where the proven checkpoint is set to a value greater than the latest
|
|
583
625
|
// synched checkpoint when requesting L2Tips from the archiver. This is the only place where the proven checkpoint is set.
|
|
584
|
-
const synched = await this.
|
|
626
|
+
const synched = await this.stores.blocks.getLatestCheckpointNumber();
|
|
585
627
|
if (
|
|
586
628
|
localCheckpointForDestinationProvenCheckpointNumber &&
|
|
587
629
|
synched < localCheckpointForDestinationProvenCheckpointNumber.checkpointNumber
|
|
@@ -601,7 +643,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
601
643
|
localCheckpointForDestinationProvenCheckpointNumber &&
|
|
602
644
|
provenArchive.equals(localCheckpointForDestinationProvenCheckpointNumber.archive.root)
|
|
603
645
|
) {
|
|
604
|
-
const localProvenCheckpointNumber = await this.
|
|
646
|
+
const localProvenCheckpointNumber = await this.stores.blocks.getProvenCheckpointNumber();
|
|
605
647
|
if (localProvenCheckpointNumber !== provenCheckpointNumber) {
|
|
606
648
|
await this.updater.setProvenCheckpointNumber(provenCheckpointNumber);
|
|
607
649
|
this.log.info(`Updated proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
|
|
@@ -629,7 +671,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
629
671
|
// If we have 0 checkpoints locally and there are no checkpoints onchain there is nothing to do.
|
|
630
672
|
const noCheckpoints = localPendingCheckpointNumber === 0 && pendingCheckpointNumber === 0;
|
|
631
673
|
if (noCheckpoints) {
|
|
632
|
-
await this.
|
|
674
|
+
await this.stores.blocks.setSynchedL1BlockNumber(currentL1BlockNumber);
|
|
633
675
|
this.log.debug(
|
|
634
676
|
`No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no checkpoints on chain`,
|
|
635
677
|
);
|
|
@@ -641,7 +683,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
641
683
|
// Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
|
|
642
684
|
// are any state that could be impacted by it. If we have no checkpoints, there is no impact.
|
|
643
685
|
if (localPendingCheckpointNumber > 0) {
|
|
644
|
-
const localPendingCheckpoint = await this.
|
|
686
|
+
const localPendingCheckpoint = await this.stores.blocks.getCheckpointData(localPendingCheckpointNumber);
|
|
645
687
|
if (localPendingCheckpoint === undefined) {
|
|
646
688
|
throw new Error(`Missing checkpoint ${localPendingCheckpointNumber}`);
|
|
647
689
|
}
|
|
@@ -656,7 +698,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
656
698
|
// However, in the re-org scenario, our L1 node is temporarily lying to us and we end up potentially missing checkpoints.
|
|
657
699
|
// We must only set this block number based on actually retrieved logs.
|
|
658
700
|
// TODO(#8621): Tackle this properly when we handle L1 Re-orgs.
|
|
659
|
-
// await this.
|
|
701
|
+
// await this.stores.blocks.setSynchedL1BlockNumber(currentL1BlockNumber);
|
|
660
702
|
this.log.debug(`No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
661
703
|
return rollupStatus;
|
|
662
704
|
}
|
|
@@ -676,7 +718,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
676
718
|
|
|
677
719
|
let tipAfterUnwind = localPendingCheckpointNumber;
|
|
678
720
|
while (true) {
|
|
679
|
-
const candidateCheckpoint = await this.
|
|
721
|
+
const candidateCheckpoint = await this.stores.blocks.getCheckpointData(tipAfterUnwind);
|
|
680
722
|
if (candidateCheckpoint === undefined) {
|
|
681
723
|
break;
|
|
682
724
|
}
|
|
@@ -701,7 +743,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
701
743
|
this.log.warn(
|
|
702
744
|
`Removed ${count(checkpointsToRemove, 'checkpoint')} after checkpoint ${tipAfterUnwind} ` +
|
|
703
745
|
`due to mismatched checkpoint hashes at L1 block ${currentL1BlockNumber}. ` +
|
|
704
|
-
`Updated L2 latest checkpoint is ${await this.
|
|
746
|
+
`Updated L2 latest checkpoint is ${await this.stores.blocks.getLatestCheckpointNumber()}.`,
|
|
705
747
|
);
|
|
706
748
|
}
|
|
707
749
|
}
|
|
@@ -718,22 +760,20 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
718
760
|
|
|
719
761
|
this.log.trace(`Retrieving checkpoints from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
720
762
|
|
|
721
|
-
//
|
|
722
|
-
const
|
|
723
|
-
|
|
763
|
+
// First fetch calldata only, no blobs yet, since we may be able to just get that data out of the proposed chain
|
|
764
|
+
const calldataCheckpoints = await execInSpan(this.tracer, 'Archiver.retrieveCheckpointCalldataFromRollup', () =>
|
|
765
|
+
retrieveCheckpointCalldataFromRollup(
|
|
724
766
|
this.rollup,
|
|
725
767
|
this.publicClient,
|
|
726
768
|
this.debugClient,
|
|
727
|
-
this.blobClient,
|
|
728
769
|
searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
|
|
729
770
|
searchEndBlock,
|
|
730
771
|
this.instrumentation,
|
|
731
772
|
this.log,
|
|
732
|
-
!initialSyncComplete, // isHistoricalSync
|
|
733
773
|
),
|
|
734
774
|
);
|
|
735
775
|
|
|
736
|
-
if (
|
|
776
|
+
if (calldataCheckpoints.length === 0) {
|
|
737
777
|
// We are not calling `setBlockSynchedL1BlockNumber` because it may cause sync issues if based off infura.
|
|
738
778
|
// See further details in earlier comments.
|
|
739
779
|
this.log.trace(`Retrieved no new checkpoints from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
@@ -741,21 +781,56 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
741
781
|
}
|
|
742
782
|
|
|
743
783
|
this.log.debug(
|
|
744
|
-
`Retrieved ${
|
|
784
|
+
`Retrieved ${calldataCheckpoints.length} new checkpoint calldata between L1 blocks ${searchStartBlock} and ${searchEndBlock}`,
|
|
745
785
|
{
|
|
746
|
-
lastProcessedCheckpoint:
|
|
786
|
+
lastProcessedCheckpoint: calldataCheckpoints[calldataCheckpoints.length - 1].l1,
|
|
747
787
|
searchStartBlock,
|
|
748
788
|
searchEndBlock,
|
|
749
789
|
},
|
|
750
790
|
);
|
|
751
791
|
|
|
752
|
-
|
|
792
|
+
// Check if the last checkpoint matches a local pending entry (so we can skip blob fetch).
|
|
793
|
+
// We only check the last one; if it matches, the blob fetch is skipped for that entry.
|
|
794
|
+
// TODO(palla/pipelining): We may have more than a single checkpoint to promote
|
|
795
|
+
const lastCalldataCheckpoint = calldataCheckpoints[calldataCheckpoints.length - 1];
|
|
796
|
+
const promoteResult = await this.tryBuildPublishedCheckpointFromProposed(lastCalldataCheckpoint);
|
|
797
|
+
const checkpointToPromote = promoteResult && !('diverged' in promoteResult) ? promoteResult : undefined;
|
|
798
|
+
const evictProposedFrom =
|
|
799
|
+
promoteResult && 'diverged' in promoteResult ? promoteResult.fromCheckpointNumber : undefined;
|
|
800
|
+
|
|
801
|
+
// Then fetch blobs in parallel and build the full published checkpoints
|
|
802
|
+
const toFetchBlobs = checkpointToPromote ? calldataCheckpoints.slice(0, -1) : calldataCheckpoints;
|
|
803
|
+
const blobFetched = await asyncPool(10, toFetchBlobs, async checkpoint =>
|
|
804
|
+
retrievedToPublishedCheckpoint({
|
|
805
|
+
...checkpoint,
|
|
806
|
+
checkpointBlobData: await getCheckpointBlobDataFromBlobs(
|
|
807
|
+
this.blobClient,
|
|
808
|
+
checkpoint.l1.blockHash,
|
|
809
|
+
checkpoint.blobHashes,
|
|
810
|
+
checkpoint.checkpointNumber,
|
|
811
|
+
this.log,
|
|
812
|
+
!initialSyncComplete,
|
|
813
|
+
checkpoint.parentBeaconBlockRoot,
|
|
814
|
+
checkpoint.l1.timestamp,
|
|
815
|
+
),
|
|
816
|
+
}),
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
// And add the promoted checkpoint to the list of all checkpoints
|
|
820
|
+
const publishedCheckpoints = checkpointToPromote ? [...blobFetched, checkpointToPromote] : blobFetched;
|
|
753
821
|
const validCheckpoints: PublishedCheckpoint[] = [];
|
|
754
822
|
|
|
823
|
+
// Now loop through all checkpoints and validate their attestations
|
|
755
824
|
for (const published of publishedCheckpoints) {
|
|
756
825
|
const validationResult = this.config.skipValidateCheckpointAttestations
|
|
757
826
|
? { valid: true as const }
|
|
758
|
-
: await validateCheckpointAttestations(
|
|
827
|
+
: await validateCheckpointAttestations(
|
|
828
|
+
published,
|
|
829
|
+
this.epochCache,
|
|
830
|
+
this.l1Constants,
|
|
831
|
+
this.getSignatureContext(),
|
|
832
|
+
this.log,
|
|
833
|
+
);
|
|
759
834
|
|
|
760
835
|
// Only update the validation result if it has changed, so we can keep track of the first invalid checkpoint
|
|
761
836
|
// in case there is a sequence of more than one invalid checkpoint, as we need to invalidate the first one.
|
|
@@ -793,7 +868,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
793
868
|
// Check the inHash of the checkpoint against the l1->l2 messages.
|
|
794
869
|
// The messages should've been synced up to the currentL1BlockNumber and must be available for the published
|
|
795
870
|
// checkpoints we just retrieved.
|
|
796
|
-
const l1ToL2Messages = await this.
|
|
871
|
+
const l1ToL2Messages = await this.stores.messages.getL1ToL2Messages(published.checkpoint.number);
|
|
797
872
|
const computedInHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
798
873
|
const publishedInHash = published.checkpoint.header.inHash;
|
|
799
874
|
if (!computedInHash.equals(publishedInHash)) {
|
|
@@ -832,16 +907,33 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
832
907
|
try {
|
|
833
908
|
const updatedValidationResult =
|
|
834
909
|
rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
|
|
910
|
+
|
|
911
|
+
// Split valid checkpoints: the promoted one (if any) is persisted via the proposed-promotion path,
|
|
912
|
+
// the rest via addCheckpoints. Both paths run within the same store transaction for atomicity.
|
|
913
|
+
const [[maybeValidCheckpointToPromote], checkpointsToAdd] = partition(
|
|
914
|
+
validCheckpoints,
|
|
915
|
+
c => c.checkpoint.number === checkpointToPromote?.checkpoint.number,
|
|
916
|
+
);
|
|
917
|
+
|
|
835
918
|
const [processDuration, result] = await elapsed(() =>
|
|
836
919
|
execInSpan(this.tracer, 'Archiver.addCheckpoints', () =>
|
|
837
|
-
this.updater.addCheckpoints(
|
|
920
|
+
this.updater.addCheckpoints(
|
|
921
|
+
checkpointsToAdd,
|
|
922
|
+
updatedValidationResult,
|
|
923
|
+
maybeValidCheckpointToPromote && {
|
|
924
|
+
l1: lastCalldataCheckpoint.l1,
|
|
925
|
+
attestations: lastCalldataCheckpoint.attestations,
|
|
926
|
+
checkpoint: maybeValidCheckpointToPromote,
|
|
927
|
+
},
|
|
928
|
+
evictProposedFrom,
|
|
929
|
+
),
|
|
838
930
|
),
|
|
839
931
|
);
|
|
840
932
|
|
|
841
|
-
if (
|
|
933
|
+
if (checkpointsToAdd.length > 0) {
|
|
842
934
|
this.instrumentation.processNewCheckpointedBlocks(
|
|
843
|
-
processDuration /
|
|
844
|
-
|
|
935
|
+
processDuration / checkpointsToAdd.length,
|
|
936
|
+
checkpointsToAdd.flatMap(c => c.checkpoint.blocks),
|
|
845
937
|
);
|
|
846
938
|
}
|
|
847
939
|
|
|
@@ -868,10 +960,10 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
868
960
|
if (err instanceof InitialCheckpointNumberNotSequentialError) {
|
|
869
961
|
const { previousCheckpointNumber, newCheckpointNumber } = err;
|
|
870
962
|
const previousCheckpoint = previousCheckpointNumber
|
|
871
|
-
? await this.
|
|
963
|
+
? await this.stores.blocks.getCheckpointData(CheckpointNumber(previousCheckpointNumber))
|
|
872
964
|
: undefined;
|
|
873
965
|
const updatedL1SyncPoint = previousCheckpoint?.l1.blockNumber ?? this.l1Constants.l1StartBlock;
|
|
874
|
-
await this.
|
|
966
|
+
await this.stores.blocks.setSynchedL1BlockNumber(updatedL1SyncPoint);
|
|
875
967
|
this.log.warn(
|
|
876
968
|
`Attempting to insert checkpoint ${newCheckpointNumber} with previous block ${previousCheckpointNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
|
|
877
969
|
{
|
|
@@ -896,7 +988,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
896
988
|
});
|
|
897
989
|
}
|
|
898
990
|
lastRetrievedCheckpoint = validCheckpoints.at(-1) ?? lastRetrievedCheckpoint;
|
|
899
|
-
lastL1BlockWithCheckpoint =
|
|
991
|
+
lastL1BlockWithCheckpoint = calldataCheckpoints.at(-1)?.l1.blockNumber ?? lastL1BlockWithCheckpoint;
|
|
900
992
|
} while (searchEndBlock < currentL1BlockNumber);
|
|
901
993
|
|
|
902
994
|
// Important that we update AFTER inserting the blocks.
|
|
@@ -905,6 +997,82 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
905
997
|
return { ...rollupStatus, lastRetrievedCheckpoint, lastL1BlockWithCheckpoint };
|
|
906
998
|
}
|
|
907
999
|
|
|
1000
|
+
/**
|
|
1001
|
+
* Checks if a specific checkpoint matches a local pending entry, and if so, loads local data to build
|
|
1002
|
+
* a synthetic published checkpoint (skipping blob fetch).
|
|
1003
|
+
*
|
|
1004
|
+
* Returns { diverged: true, fromCheckpointNumber } when the L1 checkpoint does NOT match local pending
|
|
1005
|
+
* data for that number, so the caller can evict the entire pending suffix >= fromCheckpointNumber
|
|
1006
|
+
* (those entries chain off the now-invalid local state) within the same addCheckpoints transaction.
|
|
1007
|
+
*/
|
|
1008
|
+
private async tryBuildPublishedCheckpointFromProposed(
|
|
1009
|
+
calldataCheckpoint: RetrievedCheckpointFromCalldata | undefined,
|
|
1010
|
+
): Promise<PublishedCheckpoint | { diverged: true; fromCheckpointNumber: CheckpointNumber } | undefined> {
|
|
1011
|
+
if (this.config.skipPromoteProposedCheckpointDuringL1Sync || !calldataCheckpoint) {
|
|
1012
|
+
return undefined;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Look up the specific pending entry for the checkpoint being mined, not just the tip
|
|
1016
|
+
const proposed = await this.stores.blocks.getProposedCheckpointByNumber(calldataCheckpoint.checkpointNumber);
|
|
1017
|
+
if (!proposed) {
|
|
1018
|
+
return undefined;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (
|
|
1022
|
+
!proposed.header.equals(calldataCheckpoint.header) ||
|
|
1023
|
+
!proposed.archive.root.equals(calldataCheckpoint.archiveRoot)
|
|
1024
|
+
) {
|
|
1025
|
+
this.log.warn(
|
|
1026
|
+
`Local proposed checkpoint ${proposed.checkpointNumber} does not match checkpoint retrieved from L1, overriding with L1 data`,
|
|
1027
|
+
{
|
|
1028
|
+
proposedCheckpointNumber: proposed.checkpointNumber,
|
|
1029
|
+
proposedHeader: proposed.header.toInspect(),
|
|
1030
|
+
proposedArchiveRoot: proposed.archive.root.toString(),
|
|
1031
|
+
calldataCheckpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1032
|
+
calldataHeader: calldataCheckpoint.header.toInspect(),
|
|
1033
|
+
calldataArchiveRoot: calldataCheckpoint.archiveRoot.toString(),
|
|
1034
|
+
},
|
|
1035
|
+
);
|
|
1036
|
+
// Return a divergence signal so the caller can evict pending >= this number
|
|
1037
|
+
return { diverged: true, fromCheckpointNumber: proposed.checkpointNumber };
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
this.log.debug(
|
|
1041
|
+
`Building published checkpoint from proposed ${calldataCheckpoint.checkpointNumber} (skipping blob fetch)`,
|
|
1042
|
+
{ proposedHeader: proposed.header.toInspect(), proposedArchiveRoot: proposed.archive.root.toString() },
|
|
1043
|
+
);
|
|
1044
|
+
|
|
1045
|
+
const blocks = await this.stores.blocks.getBlocks(BlockNumber(proposed.startBlock), proposed.blockCount);
|
|
1046
|
+
if (blocks.length !== proposed.blockCount) {
|
|
1047
|
+
this.log.warn(
|
|
1048
|
+
`Local proposed checkpoint ${proposed.checkpointNumber} has wrong block count (expected ${proposed.blockCount} blocks starting at ${proposed.startBlock} but got ${blocks.length})`,
|
|
1049
|
+
{
|
|
1050
|
+
proposedCheckpointNumber: proposed.checkpointNumber,
|
|
1051
|
+
proposedStartBlock: proposed.startBlock,
|
|
1052
|
+
proposedBlockCount: proposed.blockCount,
|
|
1053
|
+
retrievedBlocks: blocks.map(b => b.number),
|
|
1054
|
+
},
|
|
1055
|
+
);
|
|
1056
|
+
return undefined;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const checkpoint = Checkpoint.from({
|
|
1060
|
+
archive: proposed.archive,
|
|
1061
|
+
header: proposed.header,
|
|
1062
|
+
blocks,
|
|
1063
|
+
number: proposed.checkpointNumber,
|
|
1064
|
+
feeAssetPriceModifier: proposed.feeAssetPriceModifier,
|
|
1065
|
+
});
|
|
1066
|
+
const promotedCheckpoint = PublishedCheckpoint.from({
|
|
1067
|
+
checkpoint,
|
|
1068
|
+
l1: calldataCheckpoint.l1,
|
|
1069
|
+
attestations: calldataCheckpoint.attestations,
|
|
1070
|
+
});
|
|
1071
|
+
this.instrumentation.processCheckpointPromoted();
|
|
1072
|
+
|
|
1073
|
+
return promotedCheckpoint;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
908
1076
|
private async checkForNewCheckpointsBeforeL1SyncPoint(
|
|
909
1077
|
status: RollupStatus,
|
|
910
1078
|
blocksSynchedTo: bigint,
|
|
@@ -914,7 +1082,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
914
1082
|
// Compare the last checkpoint we have (either retrieved in this round or loaded from store) with what the
|
|
915
1083
|
// rollup contract told us was the latest one (pinned at the currentL1BlockNumber).
|
|
916
1084
|
const latestLocalCheckpointNumber =
|
|
917
|
-
lastRetrievedCheckpoint?.checkpoint.number ?? (await this.
|
|
1085
|
+
lastRetrievedCheckpoint?.checkpoint.number ?? (await this.stores.blocks.getLatestCheckpointNumber());
|
|
918
1086
|
if (latestLocalCheckpointNumber < pendingCheckpointNumber) {
|
|
919
1087
|
// Here we have consumed all logs until the `currentL1Block` we pinned at the beginning of the archiver loop,
|
|
920
1088
|
// but still haven't reached the pending checkpoint according to the call to the rollup contract.
|
|
@@ -927,7 +1095,9 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
927
1095
|
latestLocalCheckpointArchive = lastRetrievedCheckpoint.checkpoint.archive.root.toString();
|
|
928
1096
|
targetL1BlockNumber = lastRetrievedCheckpoint.l1.blockNumber;
|
|
929
1097
|
} else if (latestLocalCheckpointNumber > 0) {
|
|
930
|
-
const checkpoint = await this.
|
|
1098
|
+
const checkpoint = await this.stores.blocks
|
|
1099
|
+
.getRangeOfCheckpoints(latestLocalCheckpointNumber, 1)
|
|
1100
|
+
.then(([c]) => c);
|
|
931
1101
|
latestLocalCheckpointArchive = checkpoint.archive.root.toString();
|
|
932
1102
|
targetL1BlockNumber = checkpoint.l1.blockNumber;
|
|
933
1103
|
}
|
|
@@ -942,7 +1112,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
942
1112
|
...status,
|
|
943
1113
|
},
|
|
944
1114
|
);
|
|
945
|
-
await this.
|
|
1115
|
+
await this.stores.blocks.setSynchedL1BlockNumber(targetL1BlockNumber);
|
|
946
1116
|
} else {
|
|
947
1117
|
this.log.trace(`No new checkpoints behind L1 sync point to retrieve.`, {
|
|
948
1118
|
latestLocalCheckpointNumber,
|
|
@@ -952,7 +1122,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
952
1122
|
}
|
|
953
1123
|
|
|
954
1124
|
private async getCheckpointHeader(number: CheckpointNumber) {
|
|
955
|
-
const checkpoint = await this.
|
|
1125
|
+
const checkpoint = await this.stores.blocks.getCheckpointData(number);
|
|
956
1126
|
if (!checkpoint) {
|
|
957
1127
|
return undefined;
|
|
958
1128
|
}
|