@aztec/archiver 0.0.1-commit.f5d02921e → 0.0.1-commit.f7ea82942
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/README.md +12 -6
- package/dest/archiver.d.ts +5 -3
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +15 -4
- package/dest/config.d.ts +3 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +13 -2
- package/dest/errors.d.ts +17 -1
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +22 -0
- package/dest/factory.d.ts +1 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +2 -1
- package/dest/index.d.ts +3 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -1
- package/dest/l1/data_retrieval.d.ts +19 -10
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +25 -32
- 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/data_store_updater.d.ts +12 -5
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +13 -3
- package/dest/modules/instrumentation.d.ts +7 -2
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +22 -6
- package/dest/modules/l1_synchronizer.d.ts +6 -2
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +213 -124
- package/dest/store/block_store.d.ts +11 -3
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +88 -6
- package/dest/store/kv_archiver_store.d.ts +11 -9
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +9 -7
- package/dest/store/l2_tips_cache.d.ts +1 -1
- package/dest/store/l2_tips_cache.d.ts.map +1 -1
- package/dest/store/l2_tips_cache.js +2 -2
- package/dest/store/log_store.d.ts +1 -1
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +2 -4
- package/dest/store/message_store.d.ts +3 -3
- package/dest/store/message_store.d.ts.map +1 -1
- package/dest/store/message_store.js +9 -10
- package/dest/test/fake_l1_state.d.ts +14 -3
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +55 -10
- package/dest/test/noop_l1_archiver.d.ts +1 -1
- package/dest/test/noop_l1_archiver.d.ts.map +1 -1
- package/dest/test/noop_l1_archiver.js +4 -2
- package/package.json +13 -13
- package/src/archiver.ts +28 -6
- package/src/config.ts +14 -1
- package/src/errors.ts +34 -0
- package/src/factory.ts +1 -0
- package/src/index.ts +2 -1
- package/src/l1/data_retrieval.ts +36 -45
- package/src/l1/validate_historical_logs.ts +140 -0
- package/src/modules/data_store_updater.ts +27 -3
- package/src/modules/instrumentation.ts +27 -7
- package/src/modules/l1_synchronizer.ts +274 -148
- package/src/store/block_store.ts +112 -4
- package/src/store/kv_archiver_store.ts +18 -10
- package/src/store/l2_tips_cache.ts +8 -2
- package/src/store/log_store.ts +2 -5
- package/src/store/message_store.ts +10 -12
- package/src/structs/inbox_message.ts +1 -1
- package/src/test/fake_l1_state.ts +75 -13
- package/src/test/noop_l1_archiver.ts +3 -1
package/src/store/block_store.ts
CHANGED
|
@@ -52,7 +52,10 @@ import {
|
|
|
52
52
|
CheckpointNotFoundError,
|
|
53
53
|
CheckpointNumberNotSequentialError,
|
|
54
54
|
InitialCheckpointNumberNotSequentialError,
|
|
55
|
+
NoProposedCheckpointToPromoteError,
|
|
56
|
+
ProposedCheckpointArchiveRootMismatchError,
|
|
55
57
|
ProposedCheckpointNotSequentialError,
|
|
58
|
+
ProposedCheckpointPromotionNotSequentialError,
|
|
56
59
|
ProposedCheckpointStaleError,
|
|
57
60
|
} from '../errors.js';
|
|
58
61
|
|
|
@@ -262,16 +265,28 @@ export class BlockStore {
|
|
|
262
265
|
}
|
|
263
266
|
|
|
264
267
|
return await this.db.transactionAsync(async () => {
|
|
265
|
-
// Check that the checkpoint immediately before the first block to be added is present in the store.
|
|
266
268
|
const firstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
267
269
|
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
268
270
|
|
|
269
|
-
|
|
271
|
+
// Handle already-stored checkpoints at the start of the batch.
|
|
272
|
+
// This can happen after an L1 reorg re-includes a checkpoint in a different L1 block.
|
|
273
|
+
// We accept them if archives match (same content) and update their L1 metadata.
|
|
274
|
+
if (!opts.force && firstCheckpointNumber <= previousCheckpointNumber) {
|
|
275
|
+
checkpoints = await this.skipOrUpdateAlreadyStoredCheckpoints(checkpoints, previousCheckpointNumber);
|
|
276
|
+
if (checkpoints.length === 0) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
// Re-check sequentiality after skipping
|
|
280
|
+
const newFirstNumber = checkpoints[0].checkpoint.number;
|
|
281
|
+
if (previousCheckpointNumber !== newFirstNumber - 1) {
|
|
282
|
+
throw new InitialCheckpointNumberNotSequentialError(newFirstNumber, previousCheckpointNumber);
|
|
283
|
+
}
|
|
284
|
+
} else if (previousCheckpointNumber !== firstCheckpointNumber - 1 && !opts.force) {
|
|
270
285
|
throw new InitialCheckpointNumberNotSequentialError(firstCheckpointNumber, previousCheckpointNumber);
|
|
271
286
|
}
|
|
272
287
|
|
|
273
288
|
// Get the last block of the previous checkpoint for archive chaining
|
|
274
|
-
let previousBlock = await this.getPreviousCheckpointBlock(
|
|
289
|
+
let previousBlock = await this.getPreviousCheckpointBlock(checkpoints[0].checkpoint.number);
|
|
275
290
|
|
|
276
291
|
// Iterate over checkpoints array and insert them, checking that the block numbers are sequential.
|
|
277
292
|
let previousCheckpoint: PublishedCheckpoint | undefined = undefined;
|
|
@@ -322,6 +337,50 @@ export class BlockStore {
|
|
|
322
337
|
});
|
|
323
338
|
}
|
|
324
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Handles checkpoints at the start of a batch that are already stored (e.g. due to L1 reorg).
|
|
342
|
+
* Verifies the archive root matches, updates L1 metadata, and returns only the new checkpoints.
|
|
343
|
+
*/
|
|
344
|
+
private async skipOrUpdateAlreadyStoredCheckpoints(
|
|
345
|
+
checkpoints: PublishedCheckpoint[],
|
|
346
|
+
latestStored: CheckpointNumber,
|
|
347
|
+
): Promise<PublishedCheckpoint[]> {
|
|
348
|
+
let i = 0;
|
|
349
|
+
for (; i < checkpoints.length && checkpoints[i].checkpoint.number <= latestStored; i++) {
|
|
350
|
+
const incoming = checkpoints[i];
|
|
351
|
+
const stored = await this.getCheckpointData(incoming.checkpoint.number);
|
|
352
|
+
if (!stored) {
|
|
353
|
+
// Should not happen if latestStored is correct, but be safe
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
// Verify the checkpoint content matches (archive root)
|
|
357
|
+
if (!stored.archive.root.equals(incoming.checkpoint.archive.root)) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Checkpoint ${incoming.checkpoint.number} already exists in store but with a different archive root. ` +
|
|
360
|
+
`Stored: ${stored.archive.root}, incoming: ${incoming.checkpoint.archive.root}`,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
// Update L1 metadata and attestations for the already-stored checkpoint
|
|
364
|
+
this.#log.warn(
|
|
365
|
+
`Checkpoint ${incoming.checkpoint.number} already stored, updating L1 info ` +
|
|
366
|
+
`(L1 block ${stored.l1.blockNumber} -> ${incoming.l1.blockNumber})`,
|
|
367
|
+
);
|
|
368
|
+
await this.#checkpoints.set(incoming.checkpoint.number, {
|
|
369
|
+
header: incoming.checkpoint.header.toBuffer(),
|
|
370
|
+
archive: incoming.checkpoint.archive.toBuffer(),
|
|
371
|
+
checkpointOutHash: incoming.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
372
|
+
l1: incoming.l1.toBuffer(),
|
|
373
|
+
attestations: incoming.attestations.map(a => a.toBuffer()),
|
|
374
|
+
checkpointNumber: incoming.checkpoint.number,
|
|
375
|
+
startBlock: incoming.checkpoint.blocks[0].number,
|
|
376
|
+
blockCount: incoming.checkpoint.blocks.length,
|
|
377
|
+
});
|
|
378
|
+
// Update the sync point to reflect the new L1 block
|
|
379
|
+
await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
|
|
380
|
+
}
|
|
381
|
+
return checkpoints.slice(i);
|
|
382
|
+
}
|
|
383
|
+
|
|
325
384
|
/**
|
|
326
385
|
* Gets the last block of the checkpoint before the given one.
|
|
327
386
|
* Returns undefined if there is no previous checkpoint (i.e. genesis).
|
|
@@ -638,6 +697,55 @@ export class BlockStore {
|
|
|
638
697
|
await this.#proposedCheckpoint.delete();
|
|
639
698
|
}
|
|
640
699
|
|
|
700
|
+
/**
|
|
701
|
+
* Promotes the proposed checkpoint singleton to a confirmed checkpoint entry.
|
|
702
|
+
* This persists the checkpoint to the store, clears the proposed singleton, and updates the L1 sync point.
|
|
703
|
+
* Should only be called after the checkpoint has been validated.
|
|
704
|
+
* @param expectedArchiveRoot - The archive root to match against the proposed checkpoint, to guard against races.
|
|
705
|
+
*/
|
|
706
|
+
async promoteProposedToCheckpointed(
|
|
707
|
+
l1: L1PublishedData,
|
|
708
|
+
attestations: CommitteeAttestation[],
|
|
709
|
+
expectedArchiveRoot: Fr,
|
|
710
|
+
): Promise<void> {
|
|
711
|
+
return await this.db.transactionAsync(async () => {
|
|
712
|
+
const proposed = await this.getProposedCheckpointOnly();
|
|
713
|
+
if (!proposed) {
|
|
714
|
+
throw new NoProposedCheckpointToPromoteError();
|
|
715
|
+
}
|
|
716
|
+
if (!proposed.archive.root.equals(expectedArchiveRoot)) {
|
|
717
|
+
throw new ProposedCheckpointArchiveRootMismatchError(expectedArchiveRoot, proposed.archive.root);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Verify sequentiality: promoted checkpoint must follow the latest confirmed one
|
|
721
|
+
const latestCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
722
|
+
if (latestCheckpointNumber !== proposed.checkpointNumber - 1) {
|
|
723
|
+
throw new ProposedCheckpointPromotionNotSequentialError(proposed.checkpointNumber, latestCheckpointNumber);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Write the checkpoint entry
|
|
727
|
+
await this.#checkpoints.set(proposed.checkpointNumber, {
|
|
728
|
+
header: proposed.header.toBuffer(),
|
|
729
|
+
archive: proposed.archive.toBuffer(),
|
|
730
|
+
checkpointOutHash: proposed.checkpointOutHash.toBuffer(),
|
|
731
|
+
l1: l1.toBuffer(),
|
|
732
|
+
attestations: attestations.map(attestation => attestation.toBuffer()),
|
|
733
|
+
checkpointNumber: proposed.checkpointNumber,
|
|
734
|
+
startBlock: proposed.startBlock,
|
|
735
|
+
blockCount: proposed.blockCount,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// Update the slot-to-checkpoint index
|
|
739
|
+
await this.#slotToCheckpoint.set(proposed.header.slotNumber, proposed.checkpointNumber);
|
|
740
|
+
|
|
741
|
+
// Clear the proposed checkpoint singleton
|
|
742
|
+
await this.#proposedCheckpoint.delete();
|
|
743
|
+
|
|
744
|
+
// Update the last synced L1 block
|
|
745
|
+
await this.#lastSynchedL1Block.set(l1.blockNumber);
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
|
|
641
749
|
/** Clears the proposed checkpoint if the given confirmed checkpoint number supersedes it. */
|
|
642
750
|
async clearProposedCheckpointIfSuperseded(confirmedCheckpointNumber: CheckpointNumber): Promise<void> {
|
|
643
751
|
const proposedCheckpointNumber = await this.getProposedCheckpointNumber();
|
|
@@ -920,7 +1028,7 @@ export class BlockStore {
|
|
|
920
1028
|
return {
|
|
921
1029
|
header: BlockHeader.fromBuffer(blockStorage.header),
|
|
922
1030
|
archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
|
|
923
|
-
blockHash:
|
|
1031
|
+
blockHash: BlockHash.fromBuffer(blockStorage.blockHash),
|
|
924
1032
|
checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
|
|
925
1033
|
indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
926
1034
|
};
|
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
type BlockData,
|
|
11
11
|
BlockHash,
|
|
12
12
|
CheckpointedL2Block,
|
|
13
|
+
type CommitteeAttestation,
|
|
13
14
|
L2Block,
|
|
14
15
|
type ValidateCheckpointResult,
|
|
15
16
|
} from '@aztec/stdlib/block';
|
|
16
17
|
import type {
|
|
17
18
|
CheckpointData,
|
|
18
19
|
CommonCheckpointData,
|
|
20
|
+
L1PublishedData,
|
|
19
21
|
ProposedCheckpointData,
|
|
20
22
|
ProposedCheckpointInput,
|
|
21
23
|
PublishedCheckpoint,
|
|
@@ -558,13 +560,6 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
558
560
|
await this.#blockStore.setSynchedL1BlockNumber(l1BlockNumber);
|
|
559
561
|
}
|
|
560
562
|
|
|
561
|
-
/**
|
|
562
|
-
* Stores the l1 block that messages have been synched until
|
|
563
|
-
*/
|
|
564
|
-
async setMessageSynchedL1Block(l1Block: L1BlockId) {
|
|
565
|
-
await this.#messageStore.setSynchedL1Block(l1Block);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
563
|
/**
|
|
569
564
|
* Returns the number of the most recent proven block
|
|
570
565
|
* @returns The number of the most recent proven block
|
|
@@ -597,9 +592,9 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
597
592
|
return this.#messageStore.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
|
|
598
593
|
}
|
|
599
594
|
|
|
600
|
-
/**
|
|
601
|
-
public
|
|
602
|
-
return this.#messageStore.
|
|
595
|
+
/** Atomically updates the message sync state: the L1 sync point and the inbox tree-in-progress marker. */
|
|
596
|
+
public setMessageSyncState(l1Block: L1BlockId, treeInProgress: bigint | undefined): Promise<void> {
|
|
597
|
+
return this.#messageStore.setMessageSyncState(l1Block, treeInProgress);
|
|
603
598
|
}
|
|
604
599
|
|
|
605
600
|
/** Returns an async iterator to all L1 to L2 messages on the range. */
|
|
@@ -654,6 +649,19 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
654
649
|
return this.#blockStore.deleteProposedCheckpoint();
|
|
655
650
|
}
|
|
656
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Promotes the proposed checkpoint to a confirmed checkpoint entry.
|
|
654
|
+
* Should only be called after the checkpoint has been validated.
|
|
655
|
+
* @param expectedArchiveRoot - The archive root to match against the proposed checkpoint, to guard against races.
|
|
656
|
+
*/
|
|
657
|
+
public promoteProposedToCheckpointed(
|
|
658
|
+
l1: L1PublishedData,
|
|
659
|
+
attestations: CommitteeAttestation[],
|
|
660
|
+
expectedArchiveRoot: Fr,
|
|
661
|
+
): Promise<void> {
|
|
662
|
+
return this.#blockStore.promoteProposedToCheckpointed(l1, attestations, expectedArchiveRoot);
|
|
663
|
+
}
|
|
664
|
+
|
|
657
665
|
/**
|
|
658
666
|
* Gets the number of the latest L2 block processed.
|
|
659
667
|
* @returns The number of the latest L2 block processed.
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
2
2
|
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type BlockData,
|
|
5
|
+
type CheckpointId,
|
|
6
|
+
GENESIS_BLOCK_HEADER_HASH,
|
|
7
|
+
GENESIS_CHECKPOINT_HEADER_HASH,
|
|
8
|
+
type L2Tips,
|
|
9
|
+
} from '@aztec/stdlib/block';
|
|
4
10
|
|
|
5
11
|
import type { BlockStore } from './block_store.js';
|
|
6
12
|
|
package/src/store/log_store.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
2
2
|
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import { compactArray, filterAsync } from '@aztec/foundation/collection';
|
|
4
|
-
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
6
5
|
import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
|
|
7
6
|
import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
|
|
@@ -302,13 +301,11 @@ export class LogStore {
|
|
|
302
301
|
}
|
|
303
302
|
|
|
304
303
|
#unpackBlockHash(reader: BufferReader): BlockHash {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (!blockHash) {
|
|
304
|
+
if (reader.remainingBytes() === 0) {
|
|
308
305
|
throw new Error('Failed to read block hash from log entry buffer');
|
|
309
306
|
}
|
|
310
307
|
|
|
311
|
-
return
|
|
308
|
+
return BlockHash.fromBuffer(reader);
|
|
312
309
|
}
|
|
313
310
|
|
|
314
311
|
deleteLogs(blocks: L2Block[]): Promise<boolean> {
|
|
@@ -161,15 +161,6 @@ export class MessageStore {
|
|
|
161
161
|
lastMessage = message;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// Update the L1 sync point to that of the last message added.
|
|
165
|
-
const currentSyncPoint = await this.getSynchedL1Block();
|
|
166
|
-
if (!currentSyncPoint || currentSyncPoint.l1BlockNumber < lastMessage!.l1BlockNumber) {
|
|
167
|
-
await this.setSynchedL1Block({
|
|
168
|
-
l1BlockNumber: lastMessage!.l1BlockNumber,
|
|
169
|
-
l1BlockHash: lastMessage!.l1BlockHash,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
164
|
// Update total message count with the number of inserted messages.
|
|
174
165
|
await this.increaseTotalMessageCount(messageCount);
|
|
175
166
|
});
|
|
@@ -194,9 +185,16 @@ export class MessageStore {
|
|
|
194
185
|
return this.#inboxTreeInProgress.getAsync();
|
|
195
186
|
}
|
|
196
187
|
|
|
197
|
-
/**
|
|
198
|
-
public
|
|
199
|
-
|
|
188
|
+
/** Atomically updates the message sync state: the L1 sync point and the inbox tree-in-progress marker. */
|
|
189
|
+
public setMessageSyncState(l1Block: L1BlockId, treeInProgress: bigint | undefined): Promise<void> {
|
|
190
|
+
return this.db.transactionAsync(async () => {
|
|
191
|
+
await this.setSynchedL1Block(l1Block);
|
|
192
|
+
if (treeInProgress !== undefined) {
|
|
193
|
+
await this.#inboxTreeInProgress.set(treeInProgress);
|
|
194
|
+
} else {
|
|
195
|
+
await this.#inboxTreeInProgress.delete();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
200
198
|
}
|
|
201
199
|
|
|
202
200
|
public async getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
|
|
@@ -151,8 +151,10 @@ export class FakeL1State {
|
|
|
151
151
|
// Computed from checkpoints based on L1 block visibility
|
|
152
152
|
private pendingCheckpointNumber: CheckpointNumber = CheckpointNumber(0);
|
|
153
153
|
|
|
154
|
-
// The L1 block number reported as "finalized" (defaults to the start block)
|
|
155
|
-
|
|
154
|
+
// The L1 block number reported as "finalized" (defaults to the start block).
|
|
155
|
+
// `undefined` simulates the startup window on a fresh devnet where
|
|
156
|
+
// `getBlock({ blockTag: 'finalized' })` fails with "finalized block not found".
|
|
157
|
+
private finalizedL1BlockNumber: bigint | undefined;
|
|
156
158
|
|
|
157
159
|
constructor(private readonly config: FakeL1StateConfig) {
|
|
158
160
|
this.l1BlockNumber = config.l1StartBlock;
|
|
@@ -288,8 +290,11 @@ export class FakeL1State {
|
|
|
288
290
|
this.updatePendingCheckpointNumber();
|
|
289
291
|
}
|
|
290
292
|
|
|
291
|
-
/**
|
|
292
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Sets the L1 block number that will be reported as "finalized". Pass `undefined` to
|
|
295
|
+
* simulate a chain that does not yet have a finalized block (devnet startup).
|
|
296
|
+
*/
|
|
297
|
+
setFinalizedL1BlockNumber(blockNumber: bigint | undefined): void {
|
|
293
298
|
this.finalizedL1BlockNumber = blockNumber;
|
|
294
299
|
}
|
|
295
300
|
|
|
@@ -332,6 +337,21 @@ export class FakeL1State {
|
|
|
332
337
|
this.updatePendingCheckpointNumber();
|
|
333
338
|
}
|
|
334
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Moves a checkpoint to a different L1 block number (simulates L1 reorg that
|
|
342
|
+
* re-includes the same checkpoint transaction in a different block).
|
|
343
|
+
* The checkpoint content stays the same — only the L1 metadata changes.
|
|
344
|
+
* Auto-updates pending status.
|
|
345
|
+
*/
|
|
346
|
+
moveCheckpointToL1Block(checkpointNumber: CheckpointNumber, newL1BlockNumber: bigint): void {
|
|
347
|
+
for (const cpData of this.checkpoints) {
|
|
348
|
+
if (cpData.checkpointNumber === checkpointNumber) {
|
|
349
|
+
cpData.l1BlockNumber = newL1BlockNumber;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
this.updatePendingCheckpointNumber();
|
|
353
|
+
}
|
|
354
|
+
|
|
335
355
|
/**
|
|
336
356
|
* Removes messages after a given total index (simulates L1 reorg).
|
|
337
357
|
* Auto-updates rolling hash.
|
|
@@ -451,19 +471,33 @@ export class FakeL1State {
|
|
|
451
471
|
createMockInboxContract(_publicClient: MockProxy<ViemPublicClient>): MockProxy<InboxContract> {
|
|
452
472
|
const mockInbox = mock<InboxContract>();
|
|
453
473
|
|
|
454
|
-
mockInbox.getState.mockImplementation(() => {
|
|
474
|
+
mockInbox.getState.mockImplementation((opts: { blockTag?: string; blockNumber?: bigint } = {}) => {
|
|
475
|
+
// Filter messages visible at the given block number (or all if not specified)
|
|
476
|
+
const blockNumber = opts.blockNumber ?? this.l1BlockNumber;
|
|
477
|
+
const visibleMessages = this.messages.filter(m => m.l1BlockNumber <= blockNumber);
|
|
478
|
+
|
|
455
479
|
// treeInProgress must be > any sealed checkpoint. On L1, a checkpoint can only be proposed
|
|
456
480
|
// after its messages are sealed, so treeInProgress > checkpointNumber for all published checkpoints.
|
|
457
481
|
const maxFromMessages =
|
|
458
|
-
|
|
482
|
+
visibleMessages.length > 0 ? Math.max(...visibleMessages.map(m => Number(m.checkpointNumber))) + 1 : 0;
|
|
459
483
|
const maxFromCheckpoints =
|
|
460
484
|
this.checkpoints.length > 0
|
|
461
|
-
? Math.max(
|
|
485
|
+
? Math.max(
|
|
486
|
+
...this.checkpoints
|
|
487
|
+
.filter(cp => !cp.pruned && cp.l1BlockNumber <= blockNumber)
|
|
488
|
+
.map(cp => Number(cp.checkpointNumber)),
|
|
489
|
+
0,
|
|
490
|
+
) + 1
|
|
462
491
|
: 0;
|
|
463
492
|
const treeInProgress = Math.max(maxFromMessages, maxFromCheckpoints, INITIAL_CHECKPOINT_NUMBER);
|
|
493
|
+
|
|
494
|
+
// Compute rolling hash only for visible messages
|
|
495
|
+
const rollingHash =
|
|
496
|
+
visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].rollingHash : Buffer16.ZERO;
|
|
497
|
+
|
|
464
498
|
return Promise.resolve({
|
|
465
|
-
messagesRollingHash:
|
|
466
|
-
totalMessagesInserted: BigInt(
|
|
499
|
+
messagesRollingHash: rollingHash,
|
|
500
|
+
totalMessagesInserted: BigInt(visibleMessages.length),
|
|
467
501
|
treeInProgress: BigInt(treeInProgress),
|
|
468
502
|
});
|
|
469
503
|
});
|
|
@@ -473,8 +507,8 @@ export class FakeL1State {
|
|
|
473
507
|
Promise.resolve(this.getMessageSentLogs(fromBlock, toBlock)),
|
|
474
508
|
);
|
|
475
509
|
|
|
476
|
-
mockInbox.getMessageSentEventByHash.mockImplementation((
|
|
477
|
-
Promise.resolve(this.
|
|
510
|
+
mockInbox.getMessageSentEventByHash.mockImplementation((msgHash: string, aroundL1BlockNumber: bigint) =>
|
|
511
|
+
Promise.resolve(this.getMessageSentLogByHash(msgHash, aroundL1BlockNumber) as MessageSentLog),
|
|
478
512
|
);
|
|
479
513
|
|
|
480
514
|
return mockInbox;
|
|
@@ -490,6 +524,11 @@ export class FakeL1State {
|
|
|
490
524
|
publicClient.getBlock.mockImplementation((async (args: { blockNumber?: bigint; blockTag?: string } = {}) => {
|
|
491
525
|
let blockNum: bigint;
|
|
492
526
|
if (args.blockTag === 'finalized') {
|
|
527
|
+
if (this.finalizedL1BlockNumber === undefined) {
|
|
528
|
+
throw Object.assign(new Error('finalized block not found'), {
|
|
529
|
+
details: 'finalized block not found',
|
|
530
|
+
});
|
|
531
|
+
}
|
|
493
532
|
blockNum = this.finalizedL1BlockNumber;
|
|
494
533
|
} else {
|
|
495
534
|
blockNum = args.blockNumber ?? (await publicClient.getBlockNumber());
|
|
@@ -515,10 +554,10 @@ export class FakeL1State {
|
|
|
515
554
|
createMockBlobClient(): MockProxy<BlobClientInterface> {
|
|
516
555
|
const blobClient = mock<BlobClientInterface>();
|
|
517
556
|
|
|
518
|
-
// The blockId is the
|
|
557
|
+
// The blockId is the L1 block hash, which we derive from the L1 block number
|
|
519
558
|
blobClient.getBlobSidecar.mockImplementation((blockId: `0x${string}`) =>
|
|
520
559
|
Promise.resolve(
|
|
521
|
-
this.checkpoints.find(cpData => cpData.
|
|
560
|
+
this.checkpoints.find(cpData => Buffer32.fromBigInt(cpData.l1BlockNumber).toString() === blockId)?.blobs ?? [],
|
|
522
561
|
),
|
|
523
562
|
);
|
|
524
563
|
|
|
@@ -594,6 +633,29 @@ export class FakeL1State {
|
|
|
594
633
|
}));
|
|
595
634
|
}
|
|
596
635
|
|
|
636
|
+
private getMessageSentLogByHash(msgHash: string, aroundL1BlockNumber: bigint): MessageSentLog | undefined {
|
|
637
|
+
const msg = this.messages.find(
|
|
638
|
+
msg =>
|
|
639
|
+
msg.leaf.toString() === msgHash &&
|
|
640
|
+
msg.l1BlockNumber >= aroundL1BlockNumber - 5n &&
|
|
641
|
+
msg.l1BlockNumber <= aroundL1BlockNumber + 5n,
|
|
642
|
+
);
|
|
643
|
+
if (!msg) {
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
l1BlockNumber: msg.l1BlockNumber,
|
|
648
|
+
l1BlockHash: Buffer32.fromBigInt(msg.l1BlockNumber),
|
|
649
|
+
l1TransactionHash: `0x${msg.l1BlockNumber.toString(16)}` as `0x${string}`,
|
|
650
|
+
args: {
|
|
651
|
+
checkpointNumber: msg.checkpointNumber,
|
|
652
|
+
index: msg.index,
|
|
653
|
+
leaf: msg.leaf,
|
|
654
|
+
rollingHash: msg.rollingHash,
|
|
655
|
+
},
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
597
659
|
private async makeRollupTx(
|
|
598
660
|
checkpoint: Checkpoint,
|
|
599
661
|
signers: Secp256k1Signer[],
|
|
@@ -70,9 +70,10 @@ export class NoopL1Archiver extends Archiver {
|
|
|
70
70
|
debugClient,
|
|
71
71
|
rollup,
|
|
72
72
|
{
|
|
73
|
+
rollupAddress: EthAddress.ZERO,
|
|
73
74
|
registryAddress: EthAddress.ZERO,
|
|
75
|
+
inboxAddress: EthAddress.ZERO,
|
|
74
76
|
governanceProposerAddress: EthAddress.ZERO,
|
|
75
|
-
slashFactoryAddress: EthAddress.ZERO,
|
|
76
77
|
slashingProposerAddress: EthAddress.ZERO,
|
|
77
78
|
},
|
|
78
79
|
dataStore,
|
|
@@ -82,6 +83,7 @@ export class NoopL1Archiver extends Archiver {
|
|
|
82
83
|
skipValidateCheckpointAttestations: true,
|
|
83
84
|
maxAllowedEthClientDriftSeconds: 300,
|
|
84
85
|
ethereumAllowNoDebugHosts: true, // Skip trace validation
|
|
86
|
+
skipHistoricalLogsCheck: true, // Skip historical logs validation
|
|
85
87
|
},
|
|
86
88
|
blobClient,
|
|
87
89
|
instrumentation,
|