@aztec/archiver 0.86.0-starknet.1 → 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 +20 -8
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +259 -65
- package/dest/archiver/archiver_store.d.ts +30 -15
- 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 +357 -55
- package/dest/archiver/data_retrieval.d.ts +5 -3
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +41 -23
- 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 +34 -5
- 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 +19 -11
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +30 -10
- 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 +297 -79
- package/src/archiver/archiver_store.ts +34 -15
- package/src/archiver/archiver_store_test_suite.ts +300 -45
- package/src/archiver/data_retrieval.ts +47 -30
- package/src/archiver/errors.ts +21 -0
- package/src/archiver/instrumentation.ts +4 -1
- package/src/archiver/kv_archiver_store/block_store.ts +37 -8
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +20 -7
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +47 -15
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/archiver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -62,17 +62,17 @@
|
|
|
62
62
|
]
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@aztec/blob-lib": "0.
|
|
66
|
-
"@aztec/blob-sink": "0.
|
|
67
|
-
"@aztec/constants": "0.
|
|
68
|
-
"@aztec/ethereum": "0.
|
|
69
|
-
"@aztec/foundation": "0.
|
|
70
|
-
"@aztec/kv-store": "0.
|
|
71
|
-
"@aztec/l1-artifacts": "0.
|
|
72
|
-
"@aztec/noir-protocol-circuits-types": "0.
|
|
73
|
-
"@aztec/protocol-contracts": "0.
|
|
74
|
-
"@aztec/stdlib": "0.
|
|
75
|
-
"@aztec/telemetry-client": "0.
|
|
65
|
+
"@aztec/blob-lib": "0.87.0",
|
|
66
|
+
"@aztec/blob-sink": "0.87.0",
|
|
67
|
+
"@aztec/constants": "0.87.0",
|
|
68
|
+
"@aztec/ethereum": "0.87.0",
|
|
69
|
+
"@aztec/foundation": "0.87.0",
|
|
70
|
+
"@aztec/kv-store": "0.87.0",
|
|
71
|
+
"@aztec/l1-artifacts": "0.87.0",
|
|
72
|
+
"@aztec/noir-protocol-circuits-types": "0.87.0",
|
|
73
|
+
"@aztec/protocol-contracts": "0.87.0",
|
|
74
|
+
"@aztec/stdlib": "0.87.0",
|
|
75
|
+
"@aztec/telemetry-client": "0.87.0",
|
|
76
76
|
"debug": "^4.3.4",
|
|
77
77
|
"lodash.groupby": "^4.6.0",
|
|
78
78
|
"lodash.omit": "^4.5.0",
|
|
@@ -86,12 +86,12 @@
|
|
|
86
86
|
"@types/jest": "^29.5.0",
|
|
87
87
|
"@types/lodash.groupby": "^4.6.9",
|
|
88
88
|
"@types/lodash.omit": "^4.5.7",
|
|
89
|
-
"@types/node": "^
|
|
89
|
+
"@types/node": "^22.15.17",
|
|
90
90
|
"concurrently": "^8.0.1",
|
|
91
91
|
"jest": "^29.5.0",
|
|
92
92
|
"jest-mock-extended": "^3.0.4",
|
|
93
93
|
"ts-node": "^10.9.1",
|
|
94
|
-
"typescript": "^5.
|
|
94
|
+
"typescript": "^5.3.3"
|
|
95
95
|
},
|
|
96
96
|
"files": [
|
|
97
97
|
"dest",
|
|
@@ -100,6 +100,6 @@
|
|
|
100
100
|
],
|
|
101
101
|
"types": "./dest/index.d.ts",
|
|
102
102
|
"engines": {
|
|
103
|
-
"node": ">=
|
|
103
|
+
"node": ">=20.10"
|
|
104
104
|
}
|
|
105
105
|
}
|
package/src/archiver/archiver.ts
CHANGED
|
@@ -1,6 +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';
|
|
3
10
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
11
|
+
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
4
12
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
13
|
import { Fr } from '@aztec/foundation/fields';
|
|
6
14
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
@@ -8,7 +16,8 @@ import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/runni
|
|
|
8
16
|
import { sleep } from '@aztec/foundation/sleep';
|
|
9
17
|
import { count } from '@aztec/foundation/string';
|
|
10
18
|
import { elapsed } from '@aztec/foundation/timer';
|
|
11
|
-
import {
|
|
19
|
+
import type { CustomRange } from '@aztec/kv-store';
|
|
20
|
+
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
12
21
|
import {
|
|
13
22
|
ContractClassRegisteredEvent,
|
|
14
23
|
PrivateFunctionBroadcastedEvent,
|
|
@@ -48,24 +57,25 @@ import {
|
|
|
48
57
|
import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
|
|
49
58
|
import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
|
|
50
59
|
import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
51
|
-
import type {
|
|
60
|
+
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
52
61
|
import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
|
|
53
62
|
import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
54
63
|
|
|
55
64
|
import { EventEmitter } from 'events';
|
|
56
65
|
import groupBy from 'lodash.groupby';
|
|
57
|
-
import { type GetContractReturnType, createPublicClient, fallback,
|
|
66
|
+
import { type GetContractReturnType, createPublicClient, fallback, http } from 'viem';
|
|
58
67
|
|
|
59
68
|
import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
|
|
60
69
|
import type { ArchiverConfig } from './config.js';
|
|
61
70
|
import {
|
|
62
71
|
retrieveBlocksFromRollup,
|
|
72
|
+
retrieveL1ToL2Message,
|
|
63
73
|
retrieveL1ToL2Messages,
|
|
64
74
|
retrievedBlockToPublishedL2Block,
|
|
65
75
|
} from './data_retrieval.js';
|
|
66
|
-
import { NoBlobBodiesFoundError } from './errors.js';
|
|
76
|
+
import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
|
|
67
77
|
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
68
|
-
import type {
|
|
78
|
+
import type { InboxMessage } from './structs/inbox_message.js';
|
|
69
79
|
import type { PublishedL2Block } from './structs/published.js';
|
|
70
80
|
|
|
71
81
|
/**
|
|
@@ -85,7 +95,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
85
95
|
private runningPromise?: RunningPromise;
|
|
86
96
|
|
|
87
97
|
private rollup: RollupContract;
|
|
88
|
-
private inbox:
|
|
98
|
+
private inbox: InboxContract;
|
|
89
99
|
|
|
90
100
|
private store: ArchiverStoreHelper;
|
|
91
101
|
|
|
@@ -112,7 +122,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
112
122
|
private readonly config: { pollingIntervalMs: number; batchSize: number },
|
|
113
123
|
private readonly blobSinkClient: BlobSinkClientInterface,
|
|
114
124
|
private readonly instrumentation: ArchiverInstrumentation,
|
|
115
|
-
private readonly l1constants: L1RollupConstants,
|
|
125
|
+
private readonly l1constants: L1RollupConstants & { l1StartBlockHash: Buffer32 },
|
|
116
126
|
private readonly log: Logger = createLogger('archiver'),
|
|
117
127
|
) {
|
|
118
128
|
super();
|
|
@@ -121,12 +131,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
121
131
|
this.store = new ArchiverStoreHelper(dataStore);
|
|
122
132
|
|
|
123
133
|
this.rollup = new RollupContract(publicClient, l1Addresses.rollupAddress);
|
|
124
|
-
|
|
125
|
-
this.inbox = getContract({
|
|
126
|
-
address: l1Addresses.inboxAddress.toString(),
|
|
127
|
-
abi: InboxAbi,
|
|
128
|
-
client: publicClient,
|
|
129
|
-
});
|
|
134
|
+
this.inbox = new InboxContract(publicClient, l1Addresses.inboxAddress);
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
/**
|
|
@@ -156,6 +161,10 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
156
161
|
rollup.getL1GenesisTime(),
|
|
157
162
|
] as const);
|
|
158
163
|
|
|
164
|
+
const l1StartBlockHash = await publicClient
|
|
165
|
+
.getBlock({ blockNumber: l1StartBlock, includeTransactions: false })
|
|
166
|
+
.then(block => Buffer32.fromString(block.hash));
|
|
167
|
+
|
|
159
168
|
const {
|
|
160
169
|
aztecEpochDuration: epochDuration,
|
|
161
170
|
aztecSlotDuration: slotDuration,
|
|
@@ -163,17 +172,29 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
163
172
|
aztecProofSubmissionWindow: proofSubmissionWindow,
|
|
164
173
|
} = config;
|
|
165
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
|
+
|
|
166
190
|
const archiver = new Archiver(
|
|
167
191
|
publicClient,
|
|
168
192
|
config.l1Contracts,
|
|
169
193
|
archiverStore,
|
|
170
|
-
|
|
171
|
-
pollingIntervalMs: config.archiverPollingIntervalMS ?? 10_000,
|
|
172
|
-
batchSize: config.archiverBatchSize ?? 100,
|
|
173
|
-
},
|
|
194
|
+
opts,
|
|
174
195
|
deps.blobSinkClient,
|
|
175
196
|
await ArchiverInstrumentation.new(deps.telemetry, () => archiverStore.estimateSize()),
|
|
176
|
-
|
|
197
|
+
l1Constants,
|
|
177
198
|
);
|
|
178
199
|
await archiver.start(blockUntilSynced);
|
|
179
200
|
return archiver;
|
|
@@ -252,16 +273,21 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
252
273
|
*
|
|
253
274
|
* This code does not handle reorgs.
|
|
254
275
|
*/
|
|
255
|
-
const { l1StartBlock } = this.l1constants;
|
|
256
|
-
const {
|
|
257
|
-
|
|
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);
|
|
258
285
|
|
|
259
286
|
if (initialRun) {
|
|
260
287
|
this.log.info(
|
|
261
|
-
`Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
)} 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 },
|
|
265
291
|
);
|
|
266
292
|
}
|
|
267
293
|
|
|
@@ -285,7 +311,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
285
311
|
*/
|
|
286
312
|
|
|
287
313
|
// ********** Events that are processed per L1 block **********
|
|
288
|
-
await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber);
|
|
314
|
+
await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber, currentL1BlockHash);
|
|
289
315
|
|
|
290
316
|
// Get L1 timestamp for the current block
|
|
291
317
|
const currentL1Timestamp =
|
|
@@ -401,38 +427,149 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
401
427
|
return [nextStart, nextEnd];
|
|
402
428
|
}
|
|
403
429
|
|
|
404
|
-
private async handleL1ToL2Messages(
|
|
405
|
-
|
|
406
|
-
|
|
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) {
|
|
407
437
|
return;
|
|
408
438
|
}
|
|
409
439
|
|
|
410
|
-
|
|
411
|
-
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 });
|
|
412
444
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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}.`,
|
|
417
458
|
);
|
|
459
|
+
await this.store.setMessageSynchedL1Block({
|
|
460
|
+
l1BlockHash: currentL1BlockHash,
|
|
461
|
+
l1BlockNumber: currentL1BlockNumber,
|
|
462
|
+
});
|
|
418
463
|
return;
|
|
419
464
|
}
|
|
420
465
|
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
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
|
+
|
|
424
493
|
do {
|
|
425
494
|
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
426
495
|
this.log.trace(`Retrieving L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
|
|
427
|
-
const
|
|
496
|
+
const messages = await retrieveL1ToL2Messages(this.inbox.getContract(), searchStartBlock, searchEndBlock);
|
|
428
497
|
this.log.verbose(
|
|
429
|
-
`Retrieved ${
|
|
498
|
+
`Retrieved ${messages.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`,
|
|
430
499
|
);
|
|
431
|
-
await this.store.addL1ToL2Messages(
|
|
432
|
-
for (const msg of
|
|
433
|
-
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++;
|
|
434
505
|
}
|
|
435
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
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
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);
|
|
436
573
|
}
|
|
437
574
|
|
|
438
575
|
private async handleL2blocks(blocksSynchedTo: bigint, currentL1BlockNumber: bigint) {
|
|
@@ -607,11 +744,32 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
607
744
|
});
|
|
608
745
|
}
|
|
609
746
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
+
}
|
|
615
773
|
|
|
616
774
|
for (const block of publishedBlocks) {
|
|
617
775
|
this.log.info(`Downloaded L2 block ${block.block.number}`, {
|
|
@@ -619,6 +777,8 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
619
777
|
blockNumber: block.block.number,
|
|
620
778
|
txCount: block.block.body.txEffects.length,
|
|
621
779
|
globalVariables: block.block.header.globalVariables.toInspect(),
|
|
780
|
+
archiveRoot: block.block.archive.root.toString(),
|
|
781
|
+
archiveNextLeafIndex: block.block.archive.nextAvailableLeafIndex,
|
|
622
782
|
});
|
|
623
783
|
}
|
|
624
784
|
lastRetrievedBlock = publishedBlocks.at(-1) ?? lastRetrievedBlock;
|
|
@@ -837,11 +997,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
837
997
|
if (number < 0) {
|
|
838
998
|
number = await this.store.getSynchedL2BlockNumber();
|
|
839
999
|
}
|
|
840
|
-
if (number
|
|
1000
|
+
if (number === 0) {
|
|
841
1001
|
return undefined;
|
|
842
1002
|
}
|
|
843
|
-
const
|
|
844
|
-
return
|
|
1003
|
+
const publishedBlock = await this.store.getPublishedBlock(number);
|
|
1004
|
+
return publishedBlock?.block;
|
|
845
1005
|
}
|
|
846
1006
|
|
|
847
1007
|
public async getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
|
|
@@ -1014,6 +1174,40 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
1014
1174
|
} as L2BlockId,
|
|
1015
1175
|
};
|
|
1016
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
|
+
}
|
|
1017
1211
|
}
|
|
1018
1212
|
|
|
1019
1213
|
enum Operation {
|
|
@@ -1042,6 +1236,7 @@ export class ArchiverStoreHelper
|
|
|
1042
1236
|
| 'addFunctions'
|
|
1043
1237
|
| 'backupTo'
|
|
1044
1238
|
| 'close'
|
|
1239
|
+
| 'transactionAsync'
|
|
1045
1240
|
>
|
|
1046
1241
|
{
|
|
1047
1242
|
#log = createLogger('archiver:block-helper');
|
|
@@ -1182,34 +1377,42 @@ export class ArchiverStoreHelper
|
|
|
1182
1377
|
return true;
|
|
1183
1378
|
}
|
|
1184
1379
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
+
});
|
|
1207
1407
|
}
|
|
1208
1408
|
|
|
1209
|
-
async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
1409
|
+
public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
1210
1410
|
const last = await this.getSynchedL2BlockNumber();
|
|
1211
1411
|
if (from != last) {
|
|
1212
|
-
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`);
|
|
1213
1416
|
}
|
|
1214
1417
|
|
|
1215
1418
|
// from - blocksToUnwind = the new head, so + 1 for what we need to remove
|
|
@@ -1242,6 +1445,9 @@ export class ArchiverStoreHelper
|
|
|
1242
1445
|
getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
|
|
1243
1446
|
return this.store.getPublishedBlocks(from, limit);
|
|
1244
1447
|
}
|
|
1448
|
+
getPublishedBlock(number: number): Promise<PublishedL2Block | undefined> {
|
|
1449
|
+
return this.store.getPublishedBlock(number);
|
|
1450
|
+
}
|
|
1245
1451
|
getBlockHeaders(from: number, limit: number): Promise<BlockHeader[]> {
|
|
1246
1452
|
return this.store.getBlockHeaders(from, limit);
|
|
1247
1453
|
}
|
|
@@ -1251,7 +1457,7 @@ export class ArchiverStoreHelper
|
|
|
1251
1457
|
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
|
|
1252
1458
|
return this.store.getSettledTxReceipt(txHash);
|
|
1253
1459
|
}
|
|
1254
|
-
addL1ToL2Messages(messages:
|
|
1460
|
+
addL1ToL2Messages(messages: InboxMessage[]): Promise<void> {
|
|
1255
1461
|
return this.store.addL1ToL2Messages(messages);
|
|
1256
1462
|
}
|
|
1257
1463
|
getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
|
|
@@ -1284,8 +1490,8 @@ export class ArchiverStoreHelper
|
|
|
1284
1490
|
setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
|
|
1285
1491
|
return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
|
|
1286
1492
|
}
|
|
1287
|
-
|
|
1288
|
-
return this.store.
|
|
1493
|
+
setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void> {
|
|
1494
|
+
return this.store.setMessageSynchedL1Block(l1Block);
|
|
1289
1495
|
}
|
|
1290
1496
|
getSynchPoint(): Promise<ArchiverL1SynchPoint> {
|
|
1291
1497
|
return this.store.getSynchPoint();
|
|
@@ -1314,4 +1520,16 @@ export class ArchiverStoreHelper
|
|
|
1314
1520
|
estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }> {
|
|
1315
1521
|
return this.store.estimateSize();
|
|
1316
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
|
+
}
|
|
1317
1535
|
}
|