@aztec/archiver 0.86.0 → 0.87.0
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/archiver.d.ts +62 -5
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +362 -91
- package/dest/archiver/archiver_store.d.ts +33 -17
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +364 -62
- package/dest/archiver/data_retrieval.d.ts +23 -14
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +86 -38
- package/dest/archiver/errors.d.ts +8 -0
- package/dest/archiver/errors.d.ts.map +1 -1
- package/dest/archiver/errors.js +12 -0
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.d.ts +4 -1
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +43 -6
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +3 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +17 -3
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +24 -14
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +37 -11
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +1 -1
- package/dest/archiver/kv_archiver_store/message_store.d.ts +21 -15
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +150 -48
- package/dest/archiver/structs/inbox_message.d.ts +14 -0
- package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
- package/dest/archiver/structs/inbox_message.js +38 -0
- package/dest/rpc/index.d.ts +1 -1
- package/dest/test/mock_l2_block_source.d.ts +2 -0
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +8 -1
- package/dest/test/mock_structs.d.ts +9 -0
- package/dest/test/mock_structs.d.ts.map +1 -0
- package/dest/test/mock_structs.js +37 -0
- package/package.json +15 -15
- package/src/archiver/archiver.ts +431 -108
- package/src/archiver/archiver_store.ts +37 -18
- package/src/archiver/archiver_store_test_suite.ts +307 -52
- package/src/archiver/data_retrieval.ts +130 -53
- package/src/archiver/errors.ts +21 -0
- package/src/archiver/instrumentation.ts +4 -1
- package/src/archiver/kv_archiver_store/block_store.ts +46 -8
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +20 -7
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +61 -17
- package/src/archiver/kv_archiver_store/log_store.ts +6 -2
- package/src/archiver/kv_archiver_store/message_store.ts +209 -53
- package/src/archiver/structs/inbox_message.ts +40 -0
- package/src/test/mock_l2_block_source.ts +9 -1
- package/src/test/mock_structs.ts +49 -0
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
- package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
package/src/archiver/archiver.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BlockTagTooOldError,
|
|
4
|
+
InboxContract,
|
|
5
|
+
type L1BlockId,
|
|
6
|
+
RollupContract,
|
|
7
|
+
type ViemPublicClient,
|
|
8
|
+
createEthereumChain,
|
|
9
|
+
} from '@aztec/ethereum';
|
|
10
|
+
import { maxBigint } from '@aztec/foundation/bigint';
|
|
11
|
+
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
3
12
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
13
|
import { Fr } from '@aztec/foundation/fields';
|
|
5
14
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
@@ -7,7 +16,8 @@ import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/runni
|
|
|
7
16
|
import { sleep } from '@aztec/foundation/sleep';
|
|
8
17
|
import { count } from '@aztec/foundation/string';
|
|
9
18
|
import { elapsed } from '@aztec/foundation/timer';
|
|
10
|
-
import {
|
|
19
|
+
import type { CustomRange } from '@aztec/kv-store';
|
|
20
|
+
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
11
21
|
import {
|
|
12
22
|
ContractClassRegisteredEvent,
|
|
13
23
|
PrivateFunctionBroadcastedEvent,
|
|
@@ -47,20 +57,25 @@ import {
|
|
|
47
57
|
import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
|
|
48
58
|
import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
|
|
49
59
|
import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
50
|
-
import type {
|
|
60
|
+
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
51
61
|
import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
|
|
52
62
|
import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
53
63
|
|
|
54
64
|
import { EventEmitter } from 'events';
|
|
55
65
|
import groupBy from 'lodash.groupby';
|
|
56
|
-
import { type GetContractReturnType, createPublicClient, fallback,
|
|
66
|
+
import { type GetContractReturnType, createPublicClient, fallback, http } from 'viem';
|
|
57
67
|
|
|
58
68
|
import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
|
|
59
69
|
import type { ArchiverConfig } from './config.js';
|
|
60
|
-
import {
|
|
61
|
-
|
|
70
|
+
import {
|
|
71
|
+
retrieveBlocksFromRollup,
|
|
72
|
+
retrieveL1ToL2Message,
|
|
73
|
+
retrieveL1ToL2Messages,
|
|
74
|
+
retrievedBlockToPublishedL2Block,
|
|
75
|
+
} from './data_retrieval.js';
|
|
76
|
+
import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
|
|
62
77
|
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
63
|
-
import type {
|
|
78
|
+
import type { InboxMessage } from './structs/inbox_message.js';
|
|
64
79
|
import type { PublishedL2Block } from './structs/published.js';
|
|
65
80
|
|
|
66
81
|
/**
|
|
@@ -80,7 +95,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
80
95
|
private runningPromise?: RunningPromise;
|
|
81
96
|
|
|
82
97
|
private rollup: RollupContract;
|
|
83
|
-
private inbox:
|
|
98
|
+
private inbox: InboxContract;
|
|
84
99
|
|
|
85
100
|
private store: ArchiverStoreHelper;
|
|
86
101
|
|
|
@@ -107,7 +122,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
107
122
|
private readonly config: { pollingIntervalMs: number; batchSize: number },
|
|
108
123
|
private readonly blobSinkClient: BlobSinkClientInterface,
|
|
109
124
|
private readonly instrumentation: ArchiverInstrumentation,
|
|
110
|
-
private readonly l1constants: L1RollupConstants,
|
|
125
|
+
private readonly l1constants: L1RollupConstants & { l1StartBlockHash: Buffer32 },
|
|
111
126
|
private readonly log: Logger = createLogger('archiver'),
|
|
112
127
|
) {
|
|
113
128
|
super();
|
|
@@ -116,12 +131,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
116
131
|
this.store = new ArchiverStoreHelper(dataStore);
|
|
117
132
|
|
|
118
133
|
this.rollup = new RollupContract(publicClient, l1Addresses.rollupAddress);
|
|
119
|
-
|
|
120
|
-
this.inbox = getContract({
|
|
121
|
-
address: l1Addresses.inboxAddress.toString(),
|
|
122
|
-
abi: InboxAbi,
|
|
123
|
-
client: publicClient,
|
|
124
|
-
});
|
|
134
|
+
this.inbox = new InboxContract(publicClient, l1Addresses.inboxAddress);
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
/**
|
|
@@ -151,6 +161,10 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
151
161
|
rollup.getL1GenesisTime(),
|
|
152
162
|
] as const);
|
|
153
163
|
|
|
164
|
+
const l1StartBlockHash = await publicClient
|
|
165
|
+
.getBlock({ blockNumber: l1StartBlock, includeTransactions: false })
|
|
166
|
+
.then(block => Buffer32.fromString(block.hash));
|
|
167
|
+
|
|
154
168
|
const {
|
|
155
169
|
aztecEpochDuration: epochDuration,
|
|
156
170
|
aztecSlotDuration: slotDuration,
|
|
@@ -158,17 +172,29 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
158
172
|
aztecProofSubmissionWindow: proofSubmissionWindow,
|
|
159
173
|
} = config;
|
|
160
174
|
|
|
175
|
+
const l1Constants = {
|
|
176
|
+
l1StartBlockHash,
|
|
177
|
+
l1StartBlock,
|
|
178
|
+
l1GenesisTime,
|
|
179
|
+
epochDuration,
|
|
180
|
+
slotDuration,
|
|
181
|
+
ethereumSlotDuration,
|
|
182
|
+
proofSubmissionWindow,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const opts = {
|
|
186
|
+
pollingIntervalMs: config.archiverPollingIntervalMS ?? 10_000,
|
|
187
|
+
batchSize: config.archiverBatchSize ?? 100,
|
|
188
|
+
};
|
|
189
|
+
|
|
161
190
|
const archiver = new Archiver(
|
|
162
191
|
publicClient,
|
|
163
192
|
config.l1Contracts,
|
|
164
193
|
archiverStore,
|
|
165
|
-
|
|
166
|
-
pollingIntervalMs: config.archiverPollingIntervalMS ?? 10_000,
|
|
167
|
-
batchSize: config.archiverBatchSize ?? 100,
|
|
168
|
-
},
|
|
194
|
+
opts,
|
|
169
195
|
deps.blobSinkClient,
|
|
170
196
|
await ArchiverInstrumentation.new(deps.telemetry, () => archiverStore.estimateSize()),
|
|
171
|
-
|
|
197
|
+
l1Constants,
|
|
172
198
|
);
|
|
173
199
|
await archiver.start(blockUntilSynced);
|
|
174
200
|
return archiver;
|
|
@@ -221,6 +247,8 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
221
247
|
} catch (error) {
|
|
222
248
|
if (error instanceof NoBlobBodiesFoundError) {
|
|
223
249
|
this.log.error(`Error syncing archiver: ${error.message}`);
|
|
250
|
+
} else if (error instanceof BlockTagTooOldError) {
|
|
251
|
+
this.log.warn(`Re-running archiver sync: ${error.message}`);
|
|
224
252
|
} else {
|
|
225
253
|
this.log.error('Error during archiver sync', error);
|
|
226
254
|
}
|
|
@@ -245,16 +273,21 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
245
273
|
*
|
|
246
274
|
* This code does not handle reorgs.
|
|
247
275
|
*/
|
|
248
|
-
const { l1StartBlock } = this.l1constants;
|
|
249
|
-
const {
|
|
250
|
-
|
|
276
|
+
const { l1StartBlock, l1StartBlockHash } = this.l1constants;
|
|
277
|
+
const {
|
|
278
|
+
blocksSynchedTo = l1StartBlock,
|
|
279
|
+
messagesSynchedTo = { l1BlockNumber: l1StartBlock, l1BlockHash: l1StartBlockHash },
|
|
280
|
+
} = await this.store.getSynchPoint();
|
|
281
|
+
|
|
282
|
+
const currentL1Block = await this.publicClient.getBlock({ includeTransactions: false });
|
|
283
|
+
const currentL1BlockNumber = currentL1Block.number;
|
|
284
|
+
const currentL1BlockHash = Buffer32.fromString(currentL1Block.hash);
|
|
251
285
|
|
|
252
286
|
if (initialRun) {
|
|
253
287
|
this.log.info(
|
|
254
|
-
`Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
)} to current L1 block ${currentL1BlockNumber}`,
|
|
288
|
+
`Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo}` +
|
|
289
|
+
` to current L1 block ${currentL1BlockNumber} with hash ${currentL1BlockHash.toString()}`,
|
|
290
|
+
{ blocksSynchedTo, messagesSynchedTo },
|
|
258
291
|
);
|
|
259
292
|
}
|
|
260
293
|
|
|
@@ -278,7 +311,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
278
311
|
*/
|
|
279
312
|
|
|
280
313
|
// ********** Events that are processed per L1 block **********
|
|
281
|
-
await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber);
|
|
314
|
+
await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber, currentL1BlockHash);
|
|
282
315
|
|
|
283
316
|
// Get L1 timestamp for the current block
|
|
284
317
|
const currentL1Timestamp =
|
|
@@ -289,13 +322,24 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
289
322
|
// ********** Events that are processed per L2 block **********
|
|
290
323
|
if (currentL1BlockNumber > blocksSynchedTo) {
|
|
291
324
|
// First we retrieve new L2 blocks
|
|
292
|
-
const
|
|
293
|
-
//
|
|
325
|
+
const rollupStatus = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
|
|
326
|
+
// Then we prune the current epoch if it'd reorg on next submission.
|
|
294
327
|
// Note that we don't do this before retrieving L2 blocks because we may need to retrieve
|
|
295
328
|
// blocks from more than 2 epochs ago, so we want to make sure we have the latest view of
|
|
296
329
|
// the chain locally before we start unwinding stuff. This can be optimized by figuring out
|
|
297
330
|
// up to which point we're pruning, and then requesting L2 blocks up to that point only.
|
|
298
|
-
await this.handleEpochPrune(
|
|
331
|
+
const { rollupCanPrune } = await this.handleEpochPrune(
|
|
332
|
+
rollupStatus.provenBlockNumber,
|
|
333
|
+
currentL1BlockNumber,
|
|
334
|
+
currentL1Timestamp,
|
|
335
|
+
);
|
|
336
|
+
// And lastly we check if we are missing any L2 blocks behind us due to a possible L1 reorg.
|
|
337
|
+
// We only do this if rollup cant prune on the next submission. Otherwise we will end up
|
|
338
|
+
// re-syncing the blocks we have just unwound above.
|
|
339
|
+
if (!rollupCanPrune) {
|
|
340
|
+
await this.checkForNewBlocksBeforeL1SyncPoint(rollupStatus, blocksSynchedTo, currentL1BlockNumber);
|
|
341
|
+
}
|
|
342
|
+
|
|
299
343
|
this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
|
|
300
344
|
}
|
|
301
345
|
|
|
@@ -315,7 +359,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
315
359
|
}
|
|
316
360
|
}
|
|
317
361
|
|
|
318
|
-
/** Queries the rollup contract on whether a prune can be executed on the
|
|
362
|
+
/** Queries the rollup contract on whether a prune can be executed on the immediate next L1 block. */
|
|
319
363
|
private async canPrune(currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
|
|
320
364
|
const time = (currentL1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
|
|
321
365
|
const result = await this.rollup.canPruneAtTime(time, { blockNumber: currentL1BlockNumber });
|
|
@@ -331,9 +375,9 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
331
375
|
|
|
332
376
|
/** Checks if there'd be a reorg for the next block submission and start pruning now. */
|
|
333
377
|
private async handleEpochPrune(provenBlockNumber: bigint, currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
|
|
378
|
+
const rollupCanPrune = await this.canPrune(currentL1BlockNumber, currentL1Timestamp);
|
|
334
379
|
const localPendingBlockNumber = BigInt(await this.getBlockNumber());
|
|
335
|
-
const canPrune =
|
|
336
|
-
localPendingBlockNumber > provenBlockNumber && (await this.canPrune(currentL1BlockNumber, currentL1Timestamp));
|
|
380
|
+
const canPrune = localPendingBlockNumber > provenBlockNumber && rollupCanPrune;
|
|
337
381
|
|
|
338
382
|
if (canPrune) {
|
|
339
383
|
const pruneFrom = provenBlockNumber + 1n;
|
|
@@ -369,6 +413,8 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
369
413
|
// Seems like the next iteration should handle this.
|
|
370
414
|
// await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
371
415
|
}
|
|
416
|
+
|
|
417
|
+
return { rollupCanPrune };
|
|
372
418
|
}
|
|
373
419
|
|
|
374
420
|
private nextRange(end: bigint, limit: bigint): [bigint, bigint] {
|
|
@@ -381,49 +427,176 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
381
427
|
return [nextStart, nextEnd];
|
|
382
428
|
}
|
|
383
429
|
|
|
384
|
-
private async handleL1ToL2Messages(
|
|
385
|
-
|
|
386
|
-
|
|
430
|
+
private async handleL1ToL2Messages(
|
|
431
|
+
messagesSyncPoint: L1BlockId,
|
|
432
|
+
currentL1BlockNumber: bigint,
|
|
433
|
+
currentL1BlockHash: Buffer32,
|
|
434
|
+
) {
|
|
435
|
+
this.log.trace(`Handling L1 to L2 messages from ${messagesSyncPoint.l1BlockNumber} to ${currentL1BlockNumber}.`);
|
|
436
|
+
if (currentL1BlockNumber <= messagesSyncPoint.l1BlockNumber) {
|
|
387
437
|
return;
|
|
388
438
|
}
|
|
389
439
|
|
|
390
|
-
|
|
391
|
-
const
|
|
440
|
+
// Load remote and local inbox states.
|
|
441
|
+
const localMessagesInserted = await this.store.getTotalL1ToL2MessageCount();
|
|
442
|
+
const localLastMessage = await this.store.getLastL1ToL2Message();
|
|
443
|
+
const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
|
|
392
444
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
445
|
+
this.log.trace(`Retrieved remote inbox state at L1 block ${currentL1BlockNumber}.`, {
|
|
446
|
+
localMessagesInserted,
|
|
447
|
+
localLastMessage,
|
|
448
|
+
remoteMessagesState,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Compare message count and rolling hash. If they match, no need to retrieve anything.
|
|
452
|
+
if (
|
|
453
|
+
remoteMessagesState.totalMessagesInserted === localMessagesInserted &&
|
|
454
|
+
remoteMessagesState.messagesRollingHash.equals(localLastMessage?.rollingHash ?? Buffer16.ZERO)
|
|
455
|
+
) {
|
|
456
|
+
this.log.debug(
|
|
457
|
+
`No L1 to L2 messages to query between L1 blocks ${messagesSyncPoint.l1BlockNumber} and ${currentL1BlockNumber}.`,
|
|
397
458
|
);
|
|
459
|
+
await this.store.setMessageSynchedL1Block({
|
|
460
|
+
l1BlockHash: currentL1BlockHash,
|
|
461
|
+
l1BlockNumber: currentL1BlockNumber,
|
|
462
|
+
});
|
|
398
463
|
return;
|
|
399
464
|
}
|
|
400
465
|
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
|
|
466
|
+
// Check if our syncpoint is still valid. If not, there was an L1 reorg and we need to re-retrieve messages.
|
|
467
|
+
// Note that we need to fetch it from logs and not from inbox state at the syncpoint l1 block number, since it
|
|
468
|
+
// could be older than 128 blocks and non-archive nodes cannot resolve it.
|
|
469
|
+
if (localLastMessage) {
|
|
470
|
+
const remoteLastMessage = await this.retrieveL1ToL2Message(localLastMessage.leaf);
|
|
471
|
+
this.log.trace(`Retrieved remote message for local last`, { remoteLastMessage, localLastMessage });
|
|
472
|
+
if (!remoteLastMessage || !remoteLastMessage.rollingHash.equals(localLastMessage.rollingHash)) {
|
|
473
|
+
this.log.warn(`Rolling back L1 to L2 messages due to hash mismatch or msg not found.`, {
|
|
474
|
+
remoteLastMessage,
|
|
475
|
+
messagesSyncPoint,
|
|
476
|
+
localLastMessage,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
messagesSyncPoint = await this.rollbackL1ToL2Messages(localLastMessage, messagesSyncPoint);
|
|
480
|
+
this.log.debug(`Rolled back L1 to L2 messages to L1 block ${messagesSyncPoint.l1BlockNumber}.`, {
|
|
481
|
+
messagesSyncPoint,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Retrieve and save messages in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
|
|
487
|
+
let searchStartBlock: bigint = 0n;
|
|
488
|
+
let searchEndBlock: bigint = messagesSyncPoint.l1BlockNumber;
|
|
489
|
+
|
|
490
|
+
let lastMessage: InboxMessage | undefined;
|
|
491
|
+
let messageCount = 0;
|
|
492
|
+
|
|
404
493
|
do {
|
|
405
494
|
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
406
495
|
this.log.trace(`Retrieving L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
|
|
407
|
-
const
|
|
496
|
+
const messages = await retrieveL1ToL2Messages(this.inbox.getContract(), searchStartBlock, searchEndBlock);
|
|
408
497
|
this.log.verbose(
|
|
409
|
-
`Retrieved ${
|
|
498
|
+
`Retrieved ${messages.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`,
|
|
410
499
|
);
|
|
411
|
-
await this.store.addL1ToL2Messages(
|
|
412
|
-
for (const msg of
|
|
413
|
-
this.log.debug(`Downloaded L1 to L2 message`, { leaf: msg.leaf.toString()
|
|
500
|
+
await this.store.addL1ToL2Messages(messages);
|
|
501
|
+
for (const msg of messages) {
|
|
502
|
+
this.log.debug(`Downloaded L1 to L2 message`, { ...msg, leaf: msg.leaf.toString() });
|
|
503
|
+
lastMessage = msg;
|
|
504
|
+
messageCount++;
|
|
414
505
|
}
|
|
415
506
|
} while (searchEndBlock < currentL1BlockNumber);
|
|
507
|
+
|
|
508
|
+
// Log stats for messages retrieved (if any).
|
|
509
|
+
if (messageCount > 0) {
|
|
510
|
+
this.log.info(
|
|
511
|
+
`Retrieved ${messageCount} new L1 to L2 messages up to message with index ${lastMessage?.index} for L2 block ${lastMessage?.l2BlockNumber}`,
|
|
512
|
+
{ lastMessage, messageCount },
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Warn if the resulting rolling hash does not match the remote state we had retrieved.
|
|
517
|
+
if (lastMessage && !lastMessage.rollingHash.equals(remoteMessagesState.messagesRollingHash)) {
|
|
518
|
+
this.log.warn(`Last message retrieved rolling hash does not match remote state.`, {
|
|
519
|
+
lastMessage,
|
|
520
|
+
remoteMessagesState,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
416
523
|
}
|
|
417
524
|
|
|
418
|
-
private
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
525
|
+
private retrieveL1ToL2Message(leaf: Fr): Promise<InboxMessage | undefined> {
|
|
526
|
+
return retrieveL1ToL2Message(this.inbox.getContract(), leaf, this.l1constants.l1StartBlock);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private async rollbackL1ToL2Messages(localLastMessage: InboxMessage, messagesSyncPoint: L1BlockId) {
|
|
530
|
+
// Slowly go back through our messages until we find the last common message.
|
|
531
|
+
// We could query the logs in batch as an optimization, but the depth of the reorg should not be deep, and this
|
|
532
|
+
// is a very rare case, so it's fine to query one log at a time.
|
|
533
|
+
let commonMsg: undefined | InboxMessage;
|
|
534
|
+
this.log.verbose(`Searching most recent common L1 to L2 message at or before index ${localLastMessage.index}`);
|
|
535
|
+
for await (const msg of this.store.iterateL1ToL2Messages({ reverse: true, end: localLastMessage.index })) {
|
|
536
|
+
const remoteMsg = await this.retrieveL1ToL2Message(msg.leaf);
|
|
537
|
+
const logCtx = { remoteMsg, localMsg: msg };
|
|
538
|
+
if (remoteMsg && remoteMsg.rollingHash.equals(msg.rollingHash)) {
|
|
539
|
+
this.log.verbose(
|
|
540
|
+
`Found most recent common L1 to L2 message at index ${msg.index} on L1 block ${msg.l1BlockNumber}`,
|
|
541
|
+
logCtx,
|
|
542
|
+
);
|
|
543
|
+
commonMsg = remoteMsg;
|
|
544
|
+
break;
|
|
545
|
+
} else if (remoteMsg) {
|
|
546
|
+
this.log.debug(`Local L1 to L2 message with index ${msg.index} has different rolling hash`, logCtx);
|
|
547
|
+
} else {
|
|
548
|
+
this.log.debug(`Local L1 to L2 message with index ${msg.index} not found on L1`, logCtx);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Delete everything after the common message we found.
|
|
553
|
+
const lastGoodIndex = commonMsg?.index;
|
|
554
|
+
this.log.warn(`Deleting all local L1 to L2 messages after index ${lastGoodIndex ?? 'undefined'}`);
|
|
555
|
+
await this.store.removeL1ToL2Messages(lastGoodIndex !== undefined ? lastGoodIndex + 1n : 0n);
|
|
556
|
+
|
|
557
|
+
// Update the syncpoint so the loop below reprocesses the changed messages. We go to the block before
|
|
558
|
+
// the last common one, so we force reprocessing it, in case new messages were added on that same L1 block
|
|
559
|
+
// after the last common message.
|
|
560
|
+
const syncPointL1BlockNumber = commonMsg ? commonMsg.l1BlockNumber - 1n : this.l1constants.l1StartBlock;
|
|
561
|
+
const syncPointL1BlockHash = await this.getL1BlockHash(syncPointL1BlockNumber);
|
|
562
|
+
messagesSyncPoint = { l1BlockNumber: syncPointL1BlockNumber, l1BlockHash: syncPointL1BlockHash };
|
|
563
|
+
await this.store.setMessageSynchedL1Block(messagesSyncPoint);
|
|
564
|
+
return messagesSyncPoint;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private async getL1BlockHash(l1BlockNumber: bigint): Promise<Buffer32> {
|
|
568
|
+
const block = await this.publicClient.getBlock({ blockNumber: l1BlockNumber, includeTransactions: false });
|
|
569
|
+
if (!block) {
|
|
570
|
+
throw new Error(`Missing L1 block ${l1BlockNumber}`);
|
|
571
|
+
}
|
|
572
|
+
return Buffer32.fromString(block.hash);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private async handleL2blocks(blocksSynchedTo: bigint, currentL1BlockNumber: bigint) {
|
|
422
576
|
const localPendingBlockNumber = BigInt(await this.getBlockNumber());
|
|
423
577
|
const [provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive, archiveForLocalPendingBlockNumber] =
|
|
424
578
|
await this.rollup.status(localPendingBlockNumber, { blockNumber: currentL1BlockNumber });
|
|
579
|
+
const rollupStatus = { provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive };
|
|
580
|
+
this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
|
|
581
|
+
localPendingBlockNumber,
|
|
582
|
+
blocksSynchedTo,
|
|
583
|
+
currentL1BlockNumber,
|
|
584
|
+
archiveForLocalPendingBlockNumber,
|
|
585
|
+
...rollupStatus,
|
|
586
|
+
});
|
|
425
587
|
|
|
426
588
|
const updateProvenBlock = async () => {
|
|
589
|
+
// Annoying edge case: if proven block is moved back to 0 due to a reorg at the beginning of the chain,
|
|
590
|
+
// we need to set it to zero. This is an edge case because we dont have a block zero (initial block is one),
|
|
591
|
+
// so localBlockForDestinationProvenBlockNumber would not be found below.
|
|
592
|
+
if (provenBlockNumber === 0n) {
|
|
593
|
+
const localProvenBlockNumber = await this.store.getProvenL2BlockNumber();
|
|
594
|
+
if (localProvenBlockNumber !== Number(provenBlockNumber)) {
|
|
595
|
+
await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
|
|
596
|
+
this.log.info(`Rolled back proven chain to block ${provenBlockNumber}`, { provenBlockNumber });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
427
600
|
const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
|
|
428
601
|
|
|
429
602
|
// Sanity check. I've hit what seems to be a state where the proven block is set to a value greater than the latest
|
|
@@ -435,6 +608,12 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
435
608
|
);
|
|
436
609
|
}
|
|
437
610
|
|
|
611
|
+
this.log.trace(
|
|
612
|
+
`Local block for remote proven block ${provenBlockNumber} is ${
|
|
613
|
+
localBlockForDestinationProvenBlockNumber?.archive.root.toString() ?? 'undefined'
|
|
614
|
+
}`,
|
|
615
|
+
);
|
|
616
|
+
|
|
438
617
|
if (
|
|
439
618
|
localBlockForDestinationProvenBlockNumber &&
|
|
440
619
|
provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()
|
|
@@ -445,6 +624,8 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
445
624
|
this.log.info(`Updated proven chain to block ${provenBlockNumber}`, {
|
|
446
625
|
provenBlockNumber,
|
|
447
626
|
});
|
|
627
|
+
} else {
|
|
628
|
+
this.log.trace(`Proven block ${provenBlockNumber} already stored.`);
|
|
448
629
|
}
|
|
449
630
|
}
|
|
450
631
|
this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
|
|
@@ -458,7 +639,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
458
639
|
this.log.debug(
|
|
459
640
|
`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no blocks on chain`,
|
|
460
641
|
);
|
|
461
|
-
return
|
|
642
|
+
return rollupStatus;
|
|
462
643
|
}
|
|
463
644
|
|
|
464
645
|
await updateProvenBlock();
|
|
@@ -479,10 +660,10 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
479
660
|
// this block again (or any blocks before).
|
|
480
661
|
// However, in the re-org scenario, our L1 node is temporarily lying to us and we end up potentially missing blocks
|
|
481
662
|
// We must only set this block number based on actually retrieved logs.
|
|
482
|
-
// TODO(
|
|
483
|
-
//await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
663
|
+
// TODO(#8621): Tackle this properly when we handle L1 Re-orgs.
|
|
664
|
+
// await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
484
665
|
this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
485
|
-
return
|
|
666
|
+
return rollupStatus;
|
|
486
667
|
}
|
|
487
668
|
|
|
488
669
|
const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingBlock.archive.root.toString();
|
|
@@ -523,6 +704,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
523
704
|
// computed using the L2 block time vs the L1 block time.
|
|
524
705
|
let searchStartBlock: bigint = blocksSynchedTo;
|
|
525
706
|
let searchEndBlock: bigint = blocksSynchedTo;
|
|
707
|
+
let lastRetrievedBlock: PublishedL2Block | undefined;
|
|
526
708
|
|
|
527
709
|
do {
|
|
528
710
|
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
@@ -551,7 +733,9 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
551
733
|
`Retrieved ${retrievedBlocks.length} new L2 blocks between L1 blocks ${searchStartBlock} and ${searchEndBlock} with last processed L1 block ${lastProcessedL1BlockNumber}.`,
|
|
552
734
|
);
|
|
553
735
|
|
|
554
|
-
|
|
736
|
+
const publishedBlocks = retrievedBlocks.map(b => retrievedBlockToPublishedL2Block(b));
|
|
737
|
+
|
|
738
|
+
for (const block of publishedBlocks) {
|
|
555
739
|
this.log.debug(`Ingesting new L2 block ${block.block.number} with ${block.block.body.txEffects.length} txs`, {
|
|
556
740
|
blockHash: block.block.hash(),
|
|
557
741
|
l1BlockNumber: block.l1.blockNumber,
|
|
@@ -560,26 +744,95 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
560
744
|
});
|
|
561
745
|
}
|
|
562
746
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
747
|
+
try {
|
|
748
|
+
const [processDuration] = await elapsed(() => this.store.addBlocks(publishedBlocks));
|
|
749
|
+
this.instrumentation.processNewBlocks(
|
|
750
|
+
processDuration / publishedBlocks.length,
|
|
751
|
+
publishedBlocks.map(b => b.block),
|
|
752
|
+
);
|
|
753
|
+
} catch (err) {
|
|
754
|
+
if (err instanceof InitialBlockNumberNotSequentialError) {
|
|
755
|
+
const { previousBlockNumber, newBlockNumber } = err;
|
|
756
|
+
const previousBlock = previousBlockNumber
|
|
757
|
+
? await this.store.getPublishedBlock(previousBlockNumber)
|
|
758
|
+
: undefined;
|
|
759
|
+
const updatedL1SyncPoint = previousBlock?.l1.blockNumber ?? this.l1constants.l1StartBlock;
|
|
760
|
+
await this.store.setBlockSynchedL1BlockNumber(updatedL1SyncPoint);
|
|
761
|
+
this.log.warn(
|
|
762
|
+
`Attempting to insert block ${newBlockNumber} with previous block ${previousBlockNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
|
|
763
|
+
{
|
|
764
|
+
previousBlockNumber,
|
|
765
|
+
previousBlockHash: await previousBlock?.block.hash(),
|
|
766
|
+
newBlockNumber,
|
|
767
|
+
updatedL1SyncPoint,
|
|
768
|
+
},
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
throw err;
|
|
772
|
+
}
|
|
568
773
|
|
|
569
|
-
for (const block of
|
|
774
|
+
for (const block of publishedBlocks) {
|
|
570
775
|
this.log.info(`Downloaded L2 block ${block.block.number}`, {
|
|
571
|
-
blockHash: block.block.hash(),
|
|
776
|
+
blockHash: await block.block.hash(),
|
|
572
777
|
blockNumber: block.block.number,
|
|
573
778
|
txCount: block.block.body.txEffects.length,
|
|
574
779
|
globalVariables: block.block.header.globalVariables.toInspect(),
|
|
780
|
+
archiveRoot: block.block.archive.root.toString(),
|
|
781
|
+
archiveNextLeafIndex: block.block.archive.nextAvailableLeafIndex,
|
|
575
782
|
});
|
|
576
783
|
}
|
|
784
|
+
lastRetrievedBlock = publishedBlocks.at(-1) ?? lastRetrievedBlock;
|
|
577
785
|
} while (searchEndBlock < currentL1BlockNumber);
|
|
578
786
|
|
|
579
787
|
// Important that we update AFTER inserting the blocks.
|
|
580
788
|
await updateProvenBlock();
|
|
581
789
|
|
|
582
|
-
return {
|
|
790
|
+
return { ...rollupStatus, lastRetrievedBlock };
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
private async checkForNewBlocksBeforeL1SyncPoint(
|
|
794
|
+
status: {
|
|
795
|
+
lastRetrievedBlock?: PublishedL2Block;
|
|
796
|
+
pendingBlockNumber: bigint;
|
|
797
|
+
},
|
|
798
|
+
blocksSynchedTo: bigint,
|
|
799
|
+
currentL1BlockNumber: bigint,
|
|
800
|
+
) {
|
|
801
|
+
const { lastRetrievedBlock, pendingBlockNumber } = status;
|
|
802
|
+
// Compare the last L2 block we have (either retrieved in this round or loaded from store) with what the
|
|
803
|
+
// rollup contract told us was the latest one (pinned at the currentL1BlockNumber).
|
|
804
|
+
const latestLocalL2BlockNumber = lastRetrievedBlock?.block.number ?? (await this.store.getSynchedL2BlockNumber());
|
|
805
|
+
if (latestLocalL2BlockNumber < pendingBlockNumber) {
|
|
806
|
+
// Here we have consumed all logs until the `currentL1Block` we pinned at the beginning of the archiver loop,
|
|
807
|
+
// but still havent reached the pending block according to the call to the rollup contract.
|
|
808
|
+
// We suspect an L1 reorg that added blocks *behind* us. If that is the case, it must have happened between the
|
|
809
|
+
// last L2 block we saw and the current one, so we reset the last synched L1 block number. In the edge case we
|
|
810
|
+
// don't have one, we go back 2 L1 epochs, which is the deepest possible reorg (assuming Casper is working).
|
|
811
|
+
const latestLocalL2Block =
|
|
812
|
+
lastRetrievedBlock ??
|
|
813
|
+
(latestLocalL2BlockNumber > 0
|
|
814
|
+
? await this.store.getPublishedBlocks(latestLocalL2BlockNumber, 1).then(([b]) => b)
|
|
815
|
+
: undefined);
|
|
816
|
+
const targetL1BlockNumber = latestLocalL2Block?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
|
|
817
|
+
const latestLocalL2BlockArchive = latestLocalL2Block?.block.archive.root.toString();
|
|
818
|
+
this.log.warn(
|
|
819
|
+
`Failed to reach L2 block ${pendingBlockNumber} at ${currentL1BlockNumber} (latest is ${latestLocalL2BlockNumber}). ` +
|
|
820
|
+
`Rolling back last synched L1 block number to ${targetL1BlockNumber}.`,
|
|
821
|
+
{
|
|
822
|
+
latestLocalL2BlockNumber,
|
|
823
|
+
latestLocalL2BlockArchive,
|
|
824
|
+
blocksSynchedTo,
|
|
825
|
+
currentL1BlockNumber,
|
|
826
|
+
...status,
|
|
827
|
+
},
|
|
828
|
+
);
|
|
829
|
+
await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
|
|
830
|
+
} else {
|
|
831
|
+
this.log.trace(`No new blocks behind L1 sync point to retrieve.`, {
|
|
832
|
+
latestLocalL2BlockNumber,
|
|
833
|
+
pendingBlockNumber,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
583
836
|
}
|
|
584
837
|
|
|
585
838
|
/** Resumes the archiver after a stop. */
|
|
@@ -731,7 +984,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
731
984
|
const limitWithProven = proven
|
|
732
985
|
? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
|
|
733
986
|
: limit;
|
|
734
|
-
return limitWithProven === 0 ? [] : await this.store.
|
|
987
|
+
return limitWithProven === 0 ? [] : await this.store.getPublishedBlocks(from, limitWithProven);
|
|
735
988
|
}
|
|
736
989
|
|
|
737
990
|
/**
|
|
@@ -744,11 +997,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
744
997
|
if (number < 0) {
|
|
745
998
|
number = await this.store.getSynchedL2BlockNumber();
|
|
746
999
|
}
|
|
747
|
-
if (number
|
|
1000
|
+
if (number === 0) {
|
|
748
1001
|
return undefined;
|
|
749
1002
|
}
|
|
750
|
-
const
|
|
751
|
-
return
|
|
1003
|
+
const publishedBlock = await this.store.getPublishedBlock(number);
|
|
1004
|
+
return publishedBlock?.block;
|
|
752
1005
|
}
|
|
753
1006
|
|
|
754
1007
|
public async getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
|
|
@@ -876,9 +1129,14 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
876
1129
|
this.getProvenBlockNumber(),
|
|
877
1130
|
] as const);
|
|
878
1131
|
|
|
879
|
-
|
|
1132
|
+
// TODO(#13569): Compute proper finalized block number based on L1 finalized block.
|
|
1133
|
+
// We just force it 2 epochs worth of proven data for now.
|
|
1134
|
+
const finalizedBlockNumber = Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0);
|
|
1135
|
+
|
|
1136
|
+
const [latestBlockHeader, provenBlockHeader, finalizedBlockHeader] = await Promise.all([
|
|
880
1137
|
latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
|
|
881
1138
|
provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined,
|
|
1139
|
+
finalizedBlockNumber > 0 ? this.getBlockHeader(finalizedBlockNumber) : undefined,
|
|
882
1140
|
] as const);
|
|
883
1141
|
|
|
884
1142
|
if (latestBlockNumber > 0 && !latestBlockHeader) {
|
|
@@ -891,9 +1149,16 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
891
1149
|
);
|
|
892
1150
|
}
|
|
893
1151
|
|
|
1152
|
+
if (finalizedBlockNumber > 0 && !finalizedBlockHeader) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
`Failed to retrieve finalized block header for block ${finalizedBlockNumber} (latest block is ${latestBlockNumber})`,
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
894
1158
|
const latestBlockHeaderHash = await latestBlockHeader?.hash();
|
|
895
1159
|
const provenBlockHeaderHash = await provenBlockHeader?.hash();
|
|
896
|
-
const finalizedBlockHeaderHash = await
|
|
1160
|
+
const finalizedBlockHeaderHash = await finalizedBlockHeader?.hash();
|
|
1161
|
+
|
|
897
1162
|
return {
|
|
898
1163
|
latest: {
|
|
899
1164
|
number: latestBlockNumber,
|
|
@@ -904,11 +1169,45 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
904
1169
|
hash: provenBlockHeaderHash?.toString(),
|
|
905
1170
|
} as L2BlockId,
|
|
906
1171
|
finalized: {
|
|
907
|
-
number:
|
|
1172
|
+
number: finalizedBlockNumber,
|
|
908
1173
|
hash: finalizedBlockHeaderHash?.toString(),
|
|
909
1174
|
} as L2BlockId,
|
|
910
1175
|
};
|
|
911
1176
|
}
|
|
1177
|
+
|
|
1178
|
+
public async rollbackTo(targetL2BlockNumber: number): Promise<void> {
|
|
1179
|
+
const currentBlocks = await this.getL2Tips();
|
|
1180
|
+
const currentL2Block = currentBlocks.latest.number;
|
|
1181
|
+
const currentProvenBlock = currentBlocks.proven.number;
|
|
1182
|
+
// const currentFinalizedBlock = currentBlocks.finalized.number;
|
|
1183
|
+
|
|
1184
|
+
if (targetL2BlockNumber >= currentL2Block) {
|
|
1185
|
+
throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
|
|
1186
|
+
}
|
|
1187
|
+
const blocksToUnwind = currentL2Block - targetL2BlockNumber;
|
|
1188
|
+
const targetL2Block = await this.store.getPublishedBlock(targetL2BlockNumber);
|
|
1189
|
+
if (!targetL2Block) {
|
|
1190
|
+
throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
|
|
1191
|
+
}
|
|
1192
|
+
const targetL1BlockNumber = targetL2Block.l1.blockNumber;
|
|
1193
|
+
const targetL1BlockHash = await this.getL1BlockHash(targetL1BlockNumber);
|
|
1194
|
+
this.log.info(`Unwinding ${blocksToUnwind} blocks from L2 block ${currentL2Block}`);
|
|
1195
|
+
await this.store.unwindBlocks(currentL2Block, blocksToUnwind);
|
|
1196
|
+
this.log.info(`Unwinding L1 to L2 messages to ${targetL2BlockNumber}`);
|
|
1197
|
+
await this.store.rollbackL1ToL2MessagesToL2Block(targetL2BlockNumber);
|
|
1198
|
+
this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
|
|
1199
|
+
await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
|
|
1200
|
+
await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
|
|
1201
|
+
if (targetL2BlockNumber < currentProvenBlock) {
|
|
1202
|
+
this.log.info(`Clearing proven L2 block number`);
|
|
1203
|
+
await this.store.setProvenL2BlockNumber(0);
|
|
1204
|
+
}
|
|
1205
|
+
// TODO(palla/reorg): Set the finalized block when we add support for it.
|
|
1206
|
+
// if (targetL2BlockNumber < currentFinalizedBlock) {
|
|
1207
|
+
// this.log.info(`Clearing finalized L2 block number`);
|
|
1208
|
+
// await this.store.setFinalizedL2BlockNumber(0);
|
|
1209
|
+
// }
|
|
1210
|
+
}
|
|
912
1211
|
}
|
|
913
1212
|
|
|
914
1213
|
enum Operation {
|
|
@@ -922,7 +1221,7 @@ enum Operation {
|
|
|
922
1221
|
* I would have preferred to not have this type. But it is useful for handling the logic that any
|
|
923
1222
|
* store would need to include otherwise while exposing fewer functions and logic directly to the archiver.
|
|
924
1223
|
*/
|
|
925
|
-
class ArchiverStoreHelper
|
|
1224
|
+
export class ArchiverStoreHelper
|
|
926
1225
|
implements
|
|
927
1226
|
Omit<
|
|
928
1227
|
ArchiverDataStore,
|
|
@@ -937,11 +1236,12 @@ class ArchiverStoreHelper
|
|
|
937
1236
|
| 'addFunctions'
|
|
938
1237
|
| 'backupTo'
|
|
939
1238
|
| 'close'
|
|
1239
|
+
| 'transactionAsync'
|
|
940
1240
|
>
|
|
941
1241
|
{
|
|
942
1242
|
#log = createLogger('archiver:block-helper');
|
|
943
1243
|
|
|
944
|
-
constructor(
|
|
1244
|
+
constructor(protected readonly store: ArchiverDataStore) {}
|
|
945
1245
|
|
|
946
1246
|
/**
|
|
947
1247
|
* Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
|
|
@@ -1077,38 +1377,46 @@ class ArchiverStoreHelper
|
|
|
1077
1377
|
return true;
|
|
1078
1378
|
}
|
|
1079
1379
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1380
|
+
public addBlocks(blocks: PublishedL2Block[]): Promise<boolean> {
|
|
1381
|
+
// Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
|
|
1382
|
+
// or if the previous block is not in the store.
|
|
1383
|
+
return this.store.transactionAsync(async () => {
|
|
1384
|
+
await this.store.addBlocks(blocks);
|
|
1385
|
+
|
|
1386
|
+
const opResults = await Promise.all([
|
|
1387
|
+
this.store.addLogs(blocks.map(block => block.block)),
|
|
1388
|
+
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
1389
|
+
...blocks.map(async block => {
|
|
1390
|
+
const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
|
|
1391
|
+
// ContractInstanceDeployed event logs are broadcast in privateLogs.
|
|
1392
|
+
const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
|
|
1393
|
+
const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
|
|
1394
|
+
return (
|
|
1395
|
+
await Promise.all([
|
|
1396
|
+
this.#updateRegisteredContractClasses(contractClassLogs, block.block.number, Operation.Store),
|
|
1397
|
+
this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
|
|
1398
|
+
this.#updateUpdatedContractInstances(publicLogs, block.block.number, Operation.Store),
|
|
1399
|
+
this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.block.number),
|
|
1400
|
+
])
|
|
1401
|
+
).every(Boolean);
|
|
1402
|
+
}),
|
|
1403
|
+
]);
|
|
1404
|
+
|
|
1405
|
+
return opResults.every(Boolean);
|
|
1406
|
+
});
|
|
1102
1407
|
}
|
|
1103
1408
|
|
|
1104
|
-
async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
1409
|
+
public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
1105
1410
|
const last = await this.getSynchedL2BlockNumber();
|
|
1106
1411
|
if (from != last) {
|
|
1107
|
-
throw new Error(`
|
|
1412
|
+
throw new Error(`Cannot unwind blocks from block ${from} when the last block is ${last}`);
|
|
1413
|
+
}
|
|
1414
|
+
if (blocksToUnwind <= 0) {
|
|
1415
|
+
throw new Error(`Cannot unwind ${blocksToUnwind} blocks`);
|
|
1108
1416
|
}
|
|
1109
1417
|
|
|
1110
1418
|
// from - blocksToUnwind = the new head, so + 1 for what we need to remove
|
|
1111
|
-
const blocks = await this.
|
|
1419
|
+
const blocks = await this.getPublishedBlocks(from - blocksToUnwind + 1, blocksToUnwind);
|
|
1112
1420
|
|
|
1113
1421
|
const opResults = await Promise.all([
|
|
1114
1422
|
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
@@ -1134,8 +1442,11 @@ class ArchiverStoreHelper
|
|
|
1134
1442
|
return opResults.every(Boolean);
|
|
1135
1443
|
}
|
|
1136
1444
|
|
|
1137
|
-
|
|
1138
|
-
return this.store.
|
|
1445
|
+
getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
|
|
1446
|
+
return this.store.getPublishedBlocks(from, limit);
|
|
1447
|
+
}
|
|
1448
|
+
getPublishedBlock(number: number): Promise<PublishedL2Block | undefined> {
|
|
1449
|
+
return this.store.getPublishedBlock(number);
|
|
1139
1450
|
}
|
|
1140
1451
|
getBlockHeaders(from: number, limit: number): Promise<BlockHeader[]> {
|
|
1141
1452
|
return this.store.getBlockHeaders(from, limit);
|
|
@@ -1146,7 +1457,7 @@ class ArchiverStoreHelper
|
|
|
1146
1457
|
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
|
|
1147
1458
|
return this.store.getSettledTxReceipt(txHash);
|
|
1148
1459
|
}
|
|
1149
|
-
addL1ToL2Messages(messages:
|
|
1460
|
+
addL1ToL2Messages(messages: InboxMessage[]): Promise<void> {
|
|
1150
1461
|
return this.store.addL1ToL2Messages(messages);
|
|
1151
1462
|
}
|
|
1152
1463
|
getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
|
|
@@ -1179,8 +1490,8 @@ class ArchiverStoreHelper
|
|
|
1179
1490
|
setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
|
|
1180
1491
|
return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
|
|
1181
1492
|
}
|
|
1182
|
-
|
|
1183
|
-
return this.store.
|
|
1493
|
+
setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void> {
|
|
1494
|
+
return this.store.setMessageSynchedL1Block(l1Block);
|
|
1184
1495
|
}
|
|
1185
1496
|
getSynchPoint(): Promise<ArchiverL1SynchPoint> {
|
|
1186
1497
|
return this.store.getSynchPoint();
|
|
@@ -1206,7 +1517,19 @@ class ArchiverStoreHelper
|
|
|
1206
1517
|
getTotalL1ToL2MessageCount(): Promise<bigint> {
|
|
1207
1518
|
return this.store.getTotalL1ToL2MessageCount();
|
|
1208
1519
|
}
|
|
1209
|
-
estimateSize(): Promise<{ mappingSize: number; actualSize: number; numItems: number }> {
|
|
1520
|
+
estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }> {
|
|
1210
1521
|
return this.store.estimateSize();
|
|
1211
1522
|
}
|
|
1523
|
+
rollbackL1ToL2MessagesToL2Block(targetBlockNumber: number | bigint): Promise<void> {
|
|
1524
|
+
return this.store.rollbackL1ToL2MessagesToL2Block(targetBlockNumber);
|
|
1525
|
+
}
|
|
1526
|
+
iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
|
|
1527
|
+
return this.store.iterateL1ToL2Messages(range);
|
|
1528
|
+
}
|
|
1529
|
+
removeL1ToL2Messages(startIndex: bigint): Promise<void> {
|
|
1530
|
+
return this.store.removeL1ToL2Messages(startIndex);
|
|
1531
|
+
}
|
|
1532
|
+
getLastL1ToL2Message(): Promise<InboxMessage | undefined> {
|
|
1533
|
+
return this.store.getLastL1ToL2Message();
|
|
1534
|
+
}
|
|
1212
1535
|
}
|