@aztec/archiver 0.0.1-commit.f1df4d2 → 0.0.1-commit.f224bb98b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/archiver.d.ts +7 -4
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +62 -110
- package/dest/errors.d.ts +7 -9
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +9 -14
- package/dest/factory.d.ts +3 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +15 -13
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/l1/bin/retrieve-calldata.js +36 -33
- package/dest/l1/calldata_retriever.d.ts +73 -50
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +190 -259
- package/dest/l1/data_retrieval.d.ts +9 -9
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +21 -19
- package/dest/l1/spire_proposer.d.ts +5 -5
- package/dest/l1/spire_proposer.d.ts.map +1 -1
- package/dest/l1/spire_proposer.js +9 -17
- package/dest/modules/data_source_base.d.ts +10 -5
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +28 -72
- package/dest/modules/data_store_updater.d.ts +22 -7
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +69 -29
- package/dest/modules/instrumentation.d.ts +15 -2
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +19 -2
- package/dest/modules/l1_synchronizer.d.ts +5 -8
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +41 -10
- package/dest/store/block_store.d.ts +27 -25
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +123 -74
- package/dest/store/kv_archiver_store.d.ts +33 -11
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +37 -7
- package/dest/store/l2_tips_cache.d.ts +19 -0
- package/dest/store/l2_tips_cache.d.ts.map +1 -0
- package/dest/store/l2_tips_cache.js +89 -0
- 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 +55 -35
- package/dest/store/message_store.js +1 -1
- package/dest/test/fake_l1_state.d.ts +13 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +84 -20
- package/dest/test/mock_archiver.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +3 -2
- package/dest/test/mock_l2_block_source.d.ts +21 -5
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +130 -84
- package/dest/test/mock_structs.d.ts +4 -1
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +13 -1
- package/dest/test/noop_l1_archiver.d.ts +4 -1
- package/dest/test/noop_l1_archiver.d.ts.map +1 -1
- package/dest/test/noop_l1_archiver.js +5 -1
- package/package.json +13 -13
- package/src/archiver.ts +74 -130
- package/src/errors.ts +10 -24
- package/src/factory.ts +29 -14
- package/src/index.ts +1 -0
- package/src/l1/README.md +25 -68
- package/src/l1/bin/retrieve-calldata.ts +46 -39
- package/src/l1/calldata_retriever.ts +249 -379
- package/src/l1/data_retrieval.ts +23 -25
- package/src/l1/spire_proposer.ts +7 -15
- package/src/modules/data_source_base.ts +55 -94
- package/src/modules/data_store_updater.ts +71 -30
- package/src/modules/instrumentation.ts +29 -2
- package/src/modules/l1_synchronizer.ts +46 -14
- package/src/store/block_store.ts +146 -103
- package/src/store/kv_archiver_store.ts +57 -11
- package/src/store/l2_tips_cache.ts +89 -0
- package/src/store/log_store.ts +93 -31
- package/src/store/message_store.ts +1 -1
- package/src/test/fake_l1_state.ts +110 -21
- package/src/test/mock_archiver.ts +3 -2
- package/src/test/mock_l2_block_source.ts +166 -80
- package/src/test/mock_structs.ts +20 -6
- package/src/test/noop_l1_archiver.ts +7 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
2
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
3
3
|
import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
4
|
-
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
5
4
|
import type { L1BlockId } from '@aztec/ethereum/l1-types';
|
|
6
5
|
import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
|
|
7
6
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
@@ -9,7 +8,6 @@ import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/br
|
|
|
9
8
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
10
9
|
import { pick } from '@aztec/foundation/collection';
|
|
11
10
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
12
|
-
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
13
11
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
14
12
|
import { count } from '@aztec/foundation/string';
|
|
15
13
|
import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
|
|
@@ -28,6 +26,7 @@ import {
|
|
|
28
26
|
retrievedToPublishedCheckpoint,
|
|
29
27
|
} from '../l1/data_retrieval.js';
|
|
30
28
|
import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
|
|
29
|
+
import type { L2TipsCache } from '../store/l2_tips_cache.js';
|
|
31
30
|
import type { InboxMessage } from '../structs/inbox_message.js';
|
|
32
31
|
import { ArchiverDataStoreUpdater } from './data_store_updater.js';
|
|
33
32
|
import type { ArchiverInstrumentation } from './instrumentation.js';
|
|
@@ -60,10 +59,6 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
60
59
|
private readonly debugClient: ViemPublicDebugClient,
|
|
61
60
|
private readonly rollup: RollupContract,
|
|
62
61
|
private readonly inbox: InboxContract,
|
|
63
|
-
private readonly l1Addresses: Pick<
|
|
64
|
-
L1ContractAddresses,
|
|
65
|
-
'registryAddress' | 'governanceProposerAddress' | 'slashFactoryAddress'
|
|
66
|
-
> & { slashingProposerAddress: EthAddress },
|
|
67
62
|
private readonly store: KVArchiverDataStore,
|
|
68
63
|
private config: {
|
|
69
64
|
batchSize: number;
|
|
@@ -74,12 +69,18 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
74
69
|
private readonly epochCache: EpochCache,
|
|
75
70
|
private readonly dateProvider: DateProvider,
|
|
76
71
|
private readonly instrumentation: ArchiverInstrumentation,
|
|
77
|
-
private readonly l1Constants: L1RollupConstants & {
|
|
72
|
+
private readonly l1Constants: L1RollupConstants & {
|
|
73
|
+
l1StartBlockHash: Buffer32;
|
|
74
|
+
genesisArchiveRoot: Fr;
|
|
75
|
+
},
|
|
78
76
|
private readonly events: ArchiverEmitter,
|
|
79
77
|
tracer: Tracer,
|
|
78
|
+
l2TipsCache?: L2TipsCache,
|
|
80
79
|
private readonly log: Logger = createLogger('archiver:l1-sync'),
|
|
81
80
|
) {
|
|
82
|
-
this.updater = new ArchiverDataStoreUpdater(this.store
|
|
81
|
+
this.updater = new ArchiverDataStoreUpdater(this.store, l2TipsCache, {
|
|
82
|
+
rollupManaLimit: l1Constants.rollupManaLimit,
|
|
83
|
+
});
|
|
83
84
|
this.tracer = tracer;
|
|
84
85
|
}
|
|
85
86
|
|
|
@@ -215,6 +216,9 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
215
216
|
this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
|
|
216
217
|
}
|
|
217
218
|
|
|
219
|
+
// Update the finalized L2 checkpoint based on L1 finality.
|
|
220
|
+
await this.updateFinalizedCheckpoint();
|
|
221
|
+
|
|
218
222
|
// After syncing has completed, update the current l1 block number and timestamp,
|
|
219
223
|
// otherwise we risk announcing to the world that we've synced to a given point,
|
|
220
224
|
// but the corresponding blocks have not been processed (see #12631).
|
|
@@ -230,6 +234,27 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
230
234
|
});
|
|
231
235
|
}
|
|
232
236
|
|
|
237
|
+
/** Query L1 for its finalized block and update the finalized checkpoint accordingly. */
|
|
238
|
+
private async updateFinalizedCheckpoint(): Promise<void> {
|
|
239
|
+
try {
|
|
240
|
+
const finalizedL1Block = await this.publicClient.getBlock({ blockTag: 'finalized', includeTransactions: false });
|
|
241
|
+
const finalizedL1BlockNumber = finalizedL1Block.number;
|
|
242
|
+
const finalizedCheckpointNumber = await this.rollup.getProvenCheckpointNumber({
|
|
243
|
+
blockNumber: finalizedL1BlockNumber,
|
|
244
|
+
});
|
|
245
|
+
const localFinalizedCheckpointNumber = await this.store.getFinalizedCheckpointNumber();
|
|
246
|
+
if (localFinalizedCheckpointNumber !== finalizedCheckpointNumber) {
|
|
247
|
+
await this.updater.setFinalizedCheckpointNumber(finalizedCheckpointNumber);
|
|
248
|
+
this.log.info(`Updated finalized chain to checkpoint ${finalizedCheckpointNumber}`, {
|
|
249
|
+
finalizedCheckpointNumber,
|
|
250
|
+
finalizedL1BlockNumber,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
this.log.warn(`Failed to update finalized checkpoint: ${err}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
233
258
|
/** Prune all proposed local blocks that should have been checkpointed by now. */
|
|
234
259
|
private async pruneUncheckpointedBlocks(currentL1Timestamp: bigint) {
|
|
235
260
|
const [lastCheckpointedBlockNumber, lastProposedBlockNumber] = await Promise.all([
|
|
@@ -550,7 +575,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
550
575
|
if (provenCheckpointNumber === 0) {
|
|
551
576
|
const localProvenCheckpointNumber = await this.store.getProvenCheckpointNumber();
|
|
552
577
|
if (localProvenCheckpointNumber !== provenCheckpointNumber) {
|
|
553
|
-
await this.
|
|
578
|
+
await this.updater.setProvenCheckpointNumber(provenCheckpointNumber);
|
|
554
579
|
this.log.info(`Rolled back proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
|
|
555
580
|
}
|
|
556
581
|
}
|
|
@@ -582,13 +607,13 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
582
607
|
) {
|
|
583
608
|
const localProvenCheckpointNumber = await this.store.getProvenCheckpointNumber();
|
|
584
609
|
if (localProvenCheckpointNumber !== provenCheckpointNumber) {
|
|
585
|
-
await this.
|
|
610
|
+
await this.updater.setProvenCheckpointNumber(provenCheckpointNumber);
|
|
586
611
|
this.log.info(`Updated proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
|
|
587
612
|
const provenSlotNumber = localCheckpointForDestinationProvenCheckpointNumber.header.slotNumber;
|
|
588
613
|
const provenEpochNumber: EpochNumber = getEpochAtSlot(provenSlotNumber, this.l1Constants);
|
|
589
614
|
const lastBlockNumberInCheckpoint =
|
|
590
615
|
localCheckpointForDestinationProvenCheckpointNumber.startBlock +
|
|
591
|
-
localCheckpointForDestinationProvenCheckpointNumber.
|
|
616
|
+
localCheckpointForDestinationProvenCheckpointNumber.blockCount -
|
|
592
617
|
1;
|
|
593
618
|
|
|
594
619
|
this.events.emit(L2BlockSourceEvents.L2BlockProven, {
|
|
@@ -597,7 +622,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
597
622
|
slotNumber: provenSlotNumber,
|
|
598
623
|
epochNumber: provenEpochNumber,
|
|
599
624
|
});
|
|
600
|
-
this.instrumentation.
|
|
625
|
+
this.instrumentation.updateLastProvenCheckpoint(localCheckpointForDestinationProvenCheckpointNumber);
|
|
601
626
|
} else {
|
|
602
627
|
this.log.trace(`Proven checkpoint ${provenCheckpointNumber} already stored.`);
|
|
603
628
|
}
|
|
@@ -706,7 +731,6 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
706
731
|
this.blobClient,
|
|
707
732
|
searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
|
|
708
733
|
searchEndBlock,
|
|
709
|
-
this.l1Addresses,
|
|
710
734
|
this.instrumentation,
|
|
711
735
|
this.log,
|
|
712
736
|
!initialSyncComplete, // isHistoricalSync
|
|
@@ -801,6 +825,14 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
801
825
|
);
|
|
802
826
|
}
|
|
803
827
|
|
|
828
|
+
for (const published of validCheckpoints) {
|
|
829
|
+
this.instrumentation.processCheckpointL1Timing({
|
|
830
|
+
slotNumber: published.checkpoint.header.slotNumber,
|
|
831
|
+
l1Timestamp: published.l1.timestamp,
|
|
832
|
+
l1Constants: this.l1Constants,
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
804
836
|
try {
|
|
805
837
|
const updatedValidationResult =
|
|
806
838
|
rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
|
|
@@ -819,7 +851,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
819
851
|
const prunedCheckpointNumber = result.prunedBlocks[0].checkpointNumber;
|
|
820
852
|
const prunedSlotNumber = result.prunedBlocks[0].header.globalVariables.slotNumber;
|
|
821
853
|
|
|
822
|
-
this.log.
|
|
854
|
+
this.log.info(
|
|
823
855
|
`Pruned ${result.prunedBlocks.length} mismatching blocks for checkpoint ${prunedCheckpointNumber}`,
|
|
824
856
|
{ prunedBlocks: result.prunedBlocks.map(b => b.toBlockInfo()), prunedSlotNumber, prunedCheckpointNumber },
|
|
825
857
|
);
|
package/src/store/block_store.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { isDefined } from '@aztec/foundation/types';
|
|
|
9
9
|
import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } from '@aztec/kv-store';
|
|
10
10
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
11
11
|
import {
|
|
12
|
+
type BlockData,
|
|
12
13
|
BlockHash,
|
|
13
14
|
Body,
|
|
14
15
|
CheckpointedL2Block,
|
|
@@ -18,8 +19,7 @@ import {
|
|
|
18
19
|
deserializeValidateCheckpointResult,
|
|
19
20
|
serializeValidateCheckpointResult,
|
|
20
21
|
} from '@aztec/stdlib/block';
|
|
21
|
-
import { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
22
|
-
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
22
|
+
import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
23
23
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
24
24
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
25
25
|
import {
|
|
@@ -34,15 +34,14 @@ import {
|
|
|
34
34
|
} from '@aztec/stdlib/tx';
|
|
35
35
|
|
|
36
36
|
import {
|
|
37
|
+
BlockAlreadyCheckpointedError,
|
|
37
38
|
BlockArchiveNotConsistentError,
|
|
38
39
|
BlockIndexNotSequentialError,
|
|
39
40
|
BlockNotFoundError,
|
|
40
41
|
BlockNumberNotSequentialError,
|
|
41
42
|
CannotOverwriteCheckpointedBlockError,
|
|
42
43
|
CheckpointNotFoundError,
|
|
43
|
-
CheckpointNumberNotConsistentError,
|
|
44
44
|
CheckpointNumberNotSequentialError,
|
|
45
|
-
InitialBlockNumberNotSequentialError,
|
|
46
45
|
InitialCheckpointNumberNotSequentialError,
|
|
47
46
|
} from '../errors.js';
|
|
48
47
|
|
|
@@ -61,23 +60,14 @@ type BlockStorage = {
|
|
|
61
60
|
type CheckpointStorage = {
|
|
62
61
|
header: Buffer;
|
|
63
62
|
archive: Buffer;
|
|
63
|
+
checkpointOutHash: Buffer;
|
|
64
64
|
checkpointNumber: number;
|
|
65
65
|
startBlock: number;
|
|
66
|
-
|
|
66
|
+
blockCount: number;
|
|
67
67
|
l1: Buffer;
|
|
68
68
|
attestations: Buffer[];
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
export type CheckpointData = {
|
|
72
|
-
checkpointNumber: CheckpointNumber;
|
|
73
|
-
header: CheckpointHeader;
|
|
74
|
-
archive: AppendOnlyTreeSnapshot;
|
|
75
|
-
startBlock: number;
|
|
76
|
-
numBlocks: number;
|
|
77
|
-
l1: L1PublishedData;
|
|
78
|
-
attestations: Buffer[];
|
|
79
|
-
};
|
|
80
|
-
|
|
81
71
|
export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
|
|
82
72
|
|
|
83
73
|
/**
|
|
@@ -90,6 +80,9 @@ export class BlockStore {
|
|
|
90
80
|
/** Map checkpoint number to checkpoint data */
|
|
91
81
|
#checkpoints: AztecAsyncMap<number, CheckpointStorage>;
|
|
92
82
|
|
|
83
|
+
/** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
|
|
84
|
+
#slotToCheckpoint: AztecAsyncMap<number, number>;
|
|
85
|
+
|
|
93
86
|
/** Map block hash to list of tx hashes */
|
|
94
87
|
#blockTxs: AztecAsyncMap<string, Buffer>;
|
|
95
88
|
|
|
@@ -102,6 +95,9 @@ export class BlockStore {
|
|
|
102
95
|
/** Stores last proven checkpoint */
|
|
103
96
|
#lastProvenCheckpoint: AztecAsyncSingleton<number>;
|
|
104
97
|
|
|
98
|
+
/** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
|
|
99
|
+
#lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
|
|
100
|
+
|
|
105
101
|
/** Stores the pending chain validation status */
|
|
106
102
|
#pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
|
|
107
103
|
|
|
@@ -116,10 +112,7 @@ export class BlockStore {
|
|
|
116
112
|
|
|
117
113
|
#log = createLogger('archiver:block_store');
|
|
118
114
|
|
|
119
|
-
constructor(
|
|
120
|
-
private db: AztecAsyncKVStore,
|
|
121
|
-
private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
122
|
-
) {
|
|
115
|
+
constructor(private db: AztecAsyncKVStore) {
|
|
123
116
|
this.#blocks = db.openMap('archiver_blocks');
|
|
124
117
|
this.#blockTxs = db.openMap('archiver_block_txs');
|
|
125
118
|
this.#txEffects = db.openMap('archiver_tx_effects');
|
|
@@ -128,40 +121,42 @@ export class BlockStore {
|
|
|
128
121
|
this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
|
|
129
122
|
this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
|
|
130
123
|
this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
|
|
124
|
+
this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
|
|
131
125
|
this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
|
|
132
126
|
this.#checkpoints = db.openMap('archiver_checkpoints');
|
|
127
|
+
this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
|
|
133
128
|
}
|
|
134
129
|
|
|
135
130
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* TODO(#13569): Compute proper finalized block number based on L1 finalized block.
|
|
139
|
-
* TODO(palla/mbps): Even the provisional computation is wrong, since it should subtract checkpoints, not blocks
|
|
131
|
+
* Returns the finalized L2 block number. An L2 block is finalized when it was proven
|
|
132
|
+
* in an L1 block that has itself been finalized on Ethereum.
|
|
140
133
|
* @returns The finalized block number.
|
|
141
134
|
*/
|
|
142
135
|
async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
|
|
143
|
-
const
|
|
144
|
-
|
|
136
|
+
const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
|
|
137
|
+
if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
138
|
+
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
139
|
+
}
|
|
140
|
+
const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
|
|
141
|
+
if (!checkpointStorage) {
|
|
142
|
+
throw new CheckpointNotFoundError(finalizedCheckpointNumber);
|
|
143
|
+
}
|
|
144
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
|
-
* Append new proposed
|
|
149
|
-
*
|
|
148
|
+
* Append a new proposed block to the store.
|
|
149
|
+
* This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
|
|
150
150
|
* For checkpointed blocks (already published to L1), use addCheckpoints() instead.
|
|
151
|
-
* @param
|
|
151
|
+
* @param block - The proposed L2 block to be added to the store.
|
|
152
152
|
* @returns True if the operation is successful.
|
|
153
153
|
*/
|
|
154
|
-
async
|
|
155
|
-
if (blocks.length === 0) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
|
|
154
|
+
async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
|
|
159
155
|
return await this.db.transactionAsync(async () => {
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const firstBlockLastArchive = blocks[0].header.lastArchive.root;
|
|
156
|
+
const blockNumber = block.number;
|
|
157
|
+
const blockCheckpointNumber = block.checkpointNumber;
|
|
158
|
+
const blockIndex = block.indexWithinCheckpoint;
|
|
159
|
+
const blockLastArchive = block.header.lastArchive.root;
|
|
165
160
|
|
|
166
161
|
// Extract the latest block and checkpoint numbers
|
|
167
162
|
const previousBlockNumber = await this.getLatestBlockNumber();
|
|
@@ -169,71 +164,52 @@ export class BlockStore {
|
|
|
169
164
|
|
|
170
165
|
// Verify we're not overwriting checkpointed blocks
|
|
171
166
|
const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
|
|
172
|
-
if (!opts.force &&
|
|
173
|
-
|
|
167
|
+
if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
|
|
168
|
+
// Check if the proposed block matches the already-checkpointed one
|
|
169
|
+
const existingBlock = await this.getBlock(BlockNumber(blockNumber));
|
|
170
|
+
if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
|
|
171
|
+
throw new BlockAlreadyCheckpointedError(blockNumber);
|
|
172
|
+
}
|
|
173
|
+
throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
// Check that the
|
|
177
|
-
if (!opts.force && previousBlockNumber !==
|
|
178
|
-
throw new
|
|
176
|
+
// Check that the block number is the expected one
|
|
177
|
+
if (!opts.force && previousBlockNumber !== blockNumber - 1) {
|
|
178
|
+
throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// The same check as above but for checkpoints
|
|
182
|
-
if (!opts.force && previousCheckpointNumber !==
|
|
183
|
-
throw new
|
|
182
|
+
if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
|
|
183
|
+
throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// Extract the previous block if there is one and see if it is for the same checkpoint or not
|
|
187
187
|
const previousBlockResult = await this.getBlock(previousBlockNumber);
|
|
188
188
|
|
|
189
|
-
let
|
|
189
|
+
let expectedBlockIndex = 0;
|
|
190
190
|
let previousBlockIndex: number | undefined = undefined;
|
|
191
191
|
if (previousBlockResult !== undefined) {
|
|
192
|
-
if (previousBlockResult.checkpointNumber ===
|
|
192
|
+
if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
|
|
193
193
|
// The previous block is for the same checkpoint, therefore our index should follow it
|
|
194
194
|
previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
|
|
195
|
-
|
|
195
|
+
expectedBlockIndex = previousBlockIndex + 1;
|
|
196
196
|
}
|
|
197
|
-
if (!previousBlockResult.archive.root.equals(
|
|
197
|
+
if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
|
|
198
198
|
throw new BlockArchiveNotConsistentError(
|
|
199
|
-
|
|
199
|
+
blockNumber,
|
|
200
200
|
previousBlockResult.number,
|
|
201
|
-
|
|
201
|
+
blockLastArchive,
|
|
202
202
|
previousBlockResult.archive.root,
|
|
203
203
|
);
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// Now check that the
|
|
208
|
-
if (!opts.force &&
|
|
209
|
-
throw new BlockIndexNotSequentialError(
|
|
207
|
+
// Now check that the block has the expected index value
|
|
208
|
+
if (!opts.force && expectedBlockIndex !== blockIndex) {
|
|
209
|
+
throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
|
|
213
|
-
let previousBlock: L2Block | undefined = undefined;
|
|
214
|
-
for (const block of blocks) {
|
|
215
|
-
if (!opts.force && previousBlock) {
|
|
216
|
-
if (previousBlock.number + 1 !== block.number) {
|
|
217
|
-
throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
|
|
218
|
-
}
|
|
219
|
-
if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
|
|
220
|
-
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
|
|
221
|
-
}
|
|
222
|
-
if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
|
|
223
|
-
throw new BlockArchiveNotConsistentError(
|
|
224
|
-
block.number,
|
|
225
|
-
previousBlock.number,
|
|
226
|
-
block.header.lastArchive.root,
|
|
227
|
-
previousBlock.archive.root,
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
|
|
232
|
-
throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
|
|
233
|
-
}
|
|
234
|
-
previousBlock = block;
|
|
235
|
-
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
236
|
-
}
|
|
212
|
+
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
237
213
|
|
|
238
214
|
return true;
|
|
239
215
|
});
|
|
@@ -273,7 +249,7 @@ export class BlockStore {
|
|
|
273
249
|
|
|
274
250
|
// If we have a previous checkpoint then we need to get the previous block number
|
|
275
251
|
if (previousCheckpointData !== undefined) {
|
|
276
|
-
previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.
|
|
252
|
+
previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
|
|
277
253
|
previousBlock = await this.getBlock(previousBlockNumber);
|
|
278
254
|
if (previousBlock === undefined) {
|
|
279
255
|
// We should be able to get the required previous block
|
|
@@ -337,12 +313,16 @@ export class BlockStore {
|
|
|
337
313
|
await this.#checkpoints.set(checkpoint.checkpoint.number, {
|
|
338
314
|
header: checkpoint.checkpoint.header.toBuffer(),
|
|
339
315
|
archive: checkpoint.checkpoint.archive.toBuffer(),
|
|
316
|
+
checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
340
317
|
l1: checkpoint.l1.toBuffer(),
|
|
341
318
|
attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
|
|
342
319
|
checkpointNumber: checkpoint.checkpoint.number,
|
|
343
320
|
startBlock: checkpoint.checkpoint.blocks[0].number,
|
|
344
|
-
|
|
321
|
+
blockCount: checkpoint.checkpoint.blocks.length,
|
|
345
322
|
});
|
|
323
|
+
|
|
324
|
+
// Update slot-to-checkpoint index
|
|
325
|
+
await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
|
|
346
326
|
}
|
|
347
327
|
|
|
348
328
|
await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
|
|
@@ -425,7 +405,7 @@ export class BlockStore {
|
|
|
425
405
|
if (!targetCheckpoint) {
|
|
426
406
|
throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
|
|
427
407
|
}
|
|
428
|
-
lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.
|
|
408
|
+
lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
|
|
429
409
|
}
|
|
430
410
|
|
|
431
411
|
// Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
|
|
@@ -433,6 +413,11 @@ export class BlockStore {
|
|
|
433
413
|
|
|
434
414
|
// Remove all checkpoints after the target
|
|
435
415
|
for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
|
|
416
|
+
const checkpointStorage = await this.#checkpoints.getAsync(c);
|
|
417
|
+
if (checkpointStorage) {
|
|
418
|
+
const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
|
|
419
|
+
await this.#slotToCheckpoint.delete(slotNumber);
|
|
420
|
+
}
|
|
436
421
|
await this.#checkpoints.delete(c);
|
|
437
422
|
this.#log.debug(`Removed checkpoint ${c}`);
|
|
438
423
|
}
|
|
@@ -461,17 +446,32 @@ export class BlockStore {
|
|
|
461
446
|
return checkpoints;
|
|
462
447
|
}
|
|
463
448
|
|
|
464
|
-
|
|
465
|
-
|
|
449
|
+
/** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
|
|
450
|
+
async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
|
|
451
|
+
const result: CheckpointData[] = [];
|
|
452
|
+
for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
|
|
453
|
+
start: startSlot,
|
|
454
|
+
end: endSlot + 1,
|
|
455
|
+
})) {
|
|
456
|
+
const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
|
|
457
|
+
if (checkpointStorage) {
|
|
458
|
+
result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
|
|
465
|
+
return {
|
|
466
466
|
header: CheckpointHeader.fromBuffer(checkpointStorage.header),
|
|
467
467
|
archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
|
|
468
|
+
checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
|
|
468
469
|
checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
|
|
469
|
-
startBlock: checkpointStorage.startBlock,
|
|
470
|
-
|
|
470
|
+
startBlock: BlockNumber(checkpointStorage.startBlock),
|
|
471
|
+
blockCount: checkpointStorage.blockCount,
|
|
471
472
|
l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
|
|
472
|
-
attestations: checkpointStorage.attestations,
|
|
473
|
+
attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
|
|
473
474
|
};
|
|
474
|
-
return data;
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
|
|
@@ -483,7 +483,7 @@ export class BlockStore {
|
|
|
483
483
|
const blocksForCheckpoint = await toArray(
|
|
484
484
|
this.#blocks.entriesAsync({
|
|
485
485
|
start: checkpoint.startBlock,
|
|
486
|
-
end: checkpoint.startBlock + checkpoint.
|
|
486
|
+
end: checkpoint.startBlock + checkpoint.blockCount,
|
|
487
487
|
}),
|
|
488
488
|
);
|
|
489
489
|
|
|
@@ -556,7 +556,7 @@ export class BlockStore {
|
|
|
556
556
|
if (!checkpointStorage) {
|
|
557
557
|
throw new CheckpointNotFoundError(provenCheckpointNumber);
|
|
558
558
|
} else {
|
|
559
|
-
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.
|
|
559
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
560
560
|
}
|
|
561
561
|
}
|
|
562
562
|
|
|
@@ -655,6 +655,32 @@ export class BlockStore {
|
|
|
655
655
|
}
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
+
/**
|
|
659
|
+
* Gets block metadata (without tx data) by block number.
|
|
660
|
+
* @param blockNumber - The number of the block to return.
|
|
661
|
+
* @returns The requested block data.
|
|
662
|
+
*/
|
|
663
|
+
async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
|
|
664
|
+
const blockStorage = await this.#blocks.getAsync(blockNumber);
|
|
665
|
+
if (!blockStorage || !blockStorage.header) {
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
668
|
+
return this.getBlockDataFromBlockStorage(blockStorage);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Gets block metadata (without tx data) by archive root.
|
|
673
|
+
* @param archive - The archive root of the block to return.
|
|
674
|
+
* @returns The requested block data.
|
|
675
|
+
*/
|
|
676
|
+
async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
|
|
677
|
+
const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
|
|
678
|
+
if (blockNumber === undefined) {
|
|
679
|
+
return undefined;
|
|
680
|
+
}
|
|
681
|
+
return this.getBlockData(BlockNumber(blockNumber));
|
|
682
|
+
}
|
|
683
|
+
|
|
658
684
|
/**
|
|
659
685
|
* Gets an L2 block.
|
|
660
686
|
* @param blockNumber - The number of the block to return.
|
|
@@ -759,15 +785,24 @@ export class BlockStore {
|
|
|
759
785
|
}
|
|
760
786
|
}
|
|
761
787
|
|
|
788
|
+
private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
|
|
789
|
+
return {
|
|
790
|
+
header: BlockHeader.fromBuffer(blockStorage.header),
|
|
791
|
+
archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
|
|
792
|
+
blockHash: Fr.fromBuffer(blockStorage.blockHash),
|
|
793
|
+
checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
|
|
794
|
+
indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
762
798
|
private async getBlockFromBlockStorage(
|
|
763
799
|
blockNumber: number,
|
|
764
800
|
blockStorage: BlockStorage,
|
|
765
801
|
): Promise<L2Block | undefined> {
|
|
766
|
-
const header =
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const blockHashString = bufferToHex(blockHash);
|
|
802
|
+
const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
|
|
803
|
+
this.getBlockDataFromBlockStorage(blockStorage);
|
|
804
|
+
header.setHash(blockHash);
|
|
805
|
+
const blockHashString = bufferToHex(blockStorage.blockHash);
|
|
771
806
|
const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
|
|
772
807
|
if (blockTxsBuffer === undefined) {
|
|
773
808
|
this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
|
|
@@ -786,13 +821,7 @@ export class BlockStore {
|
|
|
786
821
|
txEffects.push(deserializeIndexedTxEffect(txEffect).data);
|
|
787
822
|
}
|
|
788
823
|
const body = new Body(txEffects);
|
|
789
|
-
const block = new L2Block(
|
|
790
|
-
archive,
|
|
791
|
-
header,
|
|
792
|
-
body,
|
|
793
|
-
CheckpointNumber(blockStorage.checkpointNumber!),
|
|
794
|
-
IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
795
|
-
);
|
|
824
|
+
const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
|
|
796
825
|
|
|
797
826
|
if (block.number !== blockNumber) {
|
|
798
827
|
throw new Error(
|
|
@@ -892,7 +921,7 @@ export class BlockStore {
|
|
|
892
921
|
if (!checkpoint) {
|
|
893
922
|
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
894
923
|
}
|
|
895
|
-
return BlockNumber(checkpoint.startBlock + checkpoint.
|
|
924
|
+
return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
|
|
896
925
|
}
|
|
897
926
|
|
|
898
927
|
async getLatestL2BlockNumber(): Promise<BlockNumber> {
|
|
@@ -927,6 +956,20 @@ export class BlockStore {
|
|
|
927
956
|
return result;
|
|
928
957
|
}
|
|
929
958
|
|
|
959
|
+
async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
960
|
+
const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
|
|
961
|
+
this.getLatestCheckpointNumber(),
|
|
962
|
+
this.#lastFinalizedCheckpoint.getAsync(),
|
|
963
|
+
]);
|
|
964
|
+
return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
|
|
965
|
+
? latestCheckpointNumber
|
|
966
|
+
: CheckpointNumber(finalizedCheckpointNumber ?? 0);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
|
|
970
|
+
return this.#lastFinalizedCheckpoint.set(checkpointNumber);
|
|
971
|
+
}
|
|
972
|
+
|
|
930
973
|
#computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
|
|
931
974
|
if (limit < 1) {
|
|
932
975
|
throw new Error(`Invalid limit: ${limit}`);
|