@aztec/world-state 0.0.1-commit.b655e406 → 0.0.1-commit.b8a057fa
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/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/instrumentation/instrumentation.d.ts +4 -3
- package/dest/instrumentation/instrumentation.d.ts.map +1 -1
- package/dest/instrumentation/instrumentation.js +21 -50
- package/dest/native/bench_metrics.d.ts +1 -1
- package/dest/native/bench_metrics.d.ts.map +1 -1
- package/dest/native/fork_checkpoint.d.ts +7 -1
- package/dest/native/fork_checkpoint.d.ts.map +1 -1
- package/dest/native/fork_checkpoint.js +15 -3
- package/dest/native/index.d.ts +2 -1
- package/dest/native/index.d.ts.map +1 -1
- package/dest/native/index.js +1 -0
- package/dest/native/ipc_world_state_instance.d.ts +89 -0
- package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
- package/dest/native/ipc_world_state_instance.js +661 -0
- package/dest/native/merkle_trees_facade.d.ts +18 -8
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +90 -111
- package/dest/native/message.d.ts +22 -228
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +14 -55
- package/dest/native/native_world_state.d.ts +30 -15
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +133 -66
- package/dest/native/native_world_state_instance.d.ts +56 -33
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +1 -182
- package/dest/native/world_state_operation.d.ts +7 -0
- package/dest/native/world_state_operation.d.ts.map +1 -0
- package/dest/native/world_state_operation.js +5 -0
- package/dest/synchronizer/config.d.ts +3 -5
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +15 -18
- package/dest/synchronizer/errors.d.ts +8 -1
- package/dest/synchronizer/errors.d.ts.map +1 -1
- package/dest/synchronizer/errors.js +8 -1
- package/dest/synchronizer/factory.d.ts +6 -5
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +7 -6
- package/dest/synchronizer/index.d.ts +2 -1
- package/dest/synchronizer/index.d.ts.map +1 -1
- package/dest/synchronizer/index.js +1 -0
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +20 -32
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +181 -85
- package/dest/test/index.d.ts +1 -1
- package/dest/test/utils.d.ts +12 -5
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +59 -50
- package/dest/testing.d.ts +5 -4
- package/dest/testing.d.ts.map +1 -1
- package/dest/testing.js +15 -8
- package/dest/world-state-db/index.d.ts +1 -1
- package/dest/world-state-db/merkle_tree_db.d.ts +9 -19
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
- package/package.json +16 -13
- package/src/index.ts +1 -0
- package/src/instrumentation/instrumentation.ts +23 -59
- package/src/native/fork_checkpoint.ts +19 -3
- package/src/native/index.ts +1 -0
- package/src/native/ipc_world_state_instance.ts +834 -0
- package/src/native/merkle_trees_facade.ts +118 -104
- package/src/native/message.ts +33 -305
- package/src/native/native_world_state.ts +179 -108
- package/src/native/native_world_state_instance.ts +94 -251
- package/src/native/world_state_operation.ts +33 -0
- package/src/synchronizer/config.ts +16 -23
- package/src/synchronizer/errors.ts +8 -0
- package/src/synchronizer/factory.ts +18 -11
- package/src/synchronizer/index.ts +1 -0
- package/src/synchronizer/server_world_state_synchronizer.ts +219 -111
- package/src/test/utils.ts +96 -92
- package/src/testing.ts +13 -11
- package/src/world-state-db/merkle_tree_db.ts +12 -19
- package/dest/native/world_state_ops_queue.d.ts +0 -19
- package/dest/native/world_state_ops_queue.d.ts.map +0 -1
- package/dest/native/world_state_ops_queue.js +0 -146
- package/src/native/world_state_ops_queue.ts +0 -190
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
1
|
+
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
5
4
|
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
6
5
|
import { elapsed } from '@aztec/foundation/timer';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
L2BlockSource,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
L2Tips,
|
|
6
|
+
import {
|
|
7
|
+
type BlockHash,
|
|
8
|
+
EventDrivenL2BlockStream,
|
|
9
|
+
type L2Block,
|
|
10
|
+
type L2BlockSource,
|
|
11
|
+
type L2BlockStreamEvent,
|
|
12
|
+
type L2BlockStreamEventHandler,
|
|
13
|
+
type L2BlockStreamLocalDataProvider,
|
|
14
|
+
type LocalChainTips,
|
|
17
15
|
} from '@aztec/stdlib/block';
|
|
18
16
|
import {
|
|
19
17
|
WorldStateRunningState,
|
|
@@ -25,7 +23,7 @@ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
|
25
23
|
import type { SnapshotDataKeys } from '@aztec/stdlib/snapshots';
|
|
26
24
|
import type { L2BlockHandledStats } from '@aztec/stdlib/stats';
|
|
27
25
|
import { MerkleTreeId, type MerkleTreeReadOperations, type MerkleTreeWriteOperations } from '@aztec/stdlib/trees';
|
|
28
|
-
import {
|
|
26
|
+
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
29
27
|
|
|
30
28
|
import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
|
|
31
29
|
import type { WorldStateStatusFull } from '../native/message.js';
|
|
@@ -45,17 +43,16 @@ export class ServerWorldStateSynchronizer
|
|
|
45
43
|
{
|
|
46
44
|
private readonly merkleTreeCommitted: MerkleTreeReadOperations;
|
|
47
45
|
|
|
48
|
-
private latestBlockNumberAtStart =
|
|
46
|
+
private latestBlockNumberAtStart = BlockNumber.ZERO;
|
|
49
47
|
private historyToKeep: number | undefined;
|
|
50
48
|
private currentState: WorldStateRunningState = WorldStateRunningState.IDLE;
|
|
51
|
-
private latestBlockHashQuery: { blockNumber: number; hash: string | undefined } | undefined = undefined;
|
|
52
49
|
|
|
53
50
|
private syncPromise = promiseWithResolvers<void>();
|
|
54
|
-
protected blockStream:
|
|
51
|
+
protected blockStream: EventDrivenL2BlockStream | undefined;
|
|
55
52
|
|
|
56
53
|
// WorldState doesn't track the proven block number, it only tracks the latest tips of the pending chain and the finalized chain
|
|
57
54
|
// store the proven block number here, in the synchronizer, so that we don't end up spamming the logs with 'chain-proved' events
|
|
58
|
-
private provenBlockNumber:
|
|
55
|
+
private provenBlockNumber: BlockNumber | undefined;
|
|
59
56
|
|
|
60
57
|
constructor(
|
|
61
58
|
private readonly merkleTreeDb: MerkleTreeAdminDatabase,
|
|
@@ -65,7 +62,7 @@ export class ServerWorldStateSynchronizer
|
|
|
65
62
|
private readonly log: Logger = createLogger('world_state'),
|
|
66
63
|
) {
|
|
67
64
|
this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
|
|
68
|
-
this.historyToKeep = config.
|
|
65
|
+
this.historyToKeep = config.worldStateCheckpointHistory < 1 ? undefined : config.worldStateCheckpointHistory;
|
|
69
66
|
this.log.info(
|
|
70
67
|
`Created world state synchroniser with block history of ${
|
|
71
68
|
this.historyToKeep === undefined ? 'infinity' : this.historyToKeep
|
|
@@ -77,12 +74,52 @@ export class ServerWorldStateSynchronizer
|
|
|
77
74
|
return this.merkleTreeDb.getCommitted();
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
public getSnapshot(blockNumber:
|
|
77
|
+
public getSnapshot(blockNumber: BlockNumber): MerkleTreeReadOperations {
|
|
81
78
|
return this.merkleTreeDb.getSnapshot(blockNumber);
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
public
|
|
85
|
-
|
|
81
|
+
public async getVerifiedSnapshot(blockNumber: BlockNumber, blockHash: BlockHash): Promise<MerkleTreeReadOperations> {
|
|
82
|
+
const snapshot = this.merkleTreeDb.getSnapshot(blockNumber);
|
|
83
|
+
// Block 0's snapshot is the pre-genesis archive view (size 0), so archive leaf 0 is not visible from it;
|
|
84
|
+
// verify against the initial header hash instead. For later blocks, read archive leaf `blockNumber` from the
|
|
85
|
+
// snapshot's own view so the exact handle we return is validated against the requested fork.
|
|
86
|
+
const actualHash =
|
|
87
|
+
blockNumber === BlockNumber.ZERO
|
|
88
|
+
? (await this.merkleTreeCommitted.getInitialHeader().hash()).toString()
|
|
89
|
+
: (await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber)))?.toString();
|
|
90
|
+
|
|
91
|
+
if (actualHash === undefined) {
|
|
92
|
+
// A missing archive leaf means either the block's history has been pruned away (permanent: the block predates
|
|
93
|
+
// the oldest historical block kept by world state) or a reorg flipped the fork between the sync and this read
|
|
94
|
+
// (transient). Only the latter is worth retrying, so it alone surfaces as WorldStateSynchronizerError.
|
|
95
|
+
const { oldestHistoricalBlock } = await this.merkleTreeDb.getStatusSummary();
|
|
96
|
+
if (blockNumber < oldestHistoricalBlock) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Unable to find leaf for block ${blockNumber} in the archive tree: world state history has been pruned to block ${oldestHistoricalBlock}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
throw new WorldStateSynchronizerError(`Unable to read block hash at block ${blockNumber} to verify snapshot`, {
|
|
102
|
+
cause: { reason: 'block_not_available', targetBlockNumber: blockNumber },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (actualHash !== blockHash.toString()) {
|
|
106
|
+
throw new WorldStateSynchronizerError(
|
|
107
|
+
`Block hash mismatch at block ${blockNumber} (expected ${blockHash} but got ${actualHash})`,
|
|
108
|
+
{
|
|
109
|
+
cause: {
|
|
110
|
+
reason: 'block_hash_mismatch',
|
|
111
|
+
targetBlockNumber: blockNumber,
|
|
112
|
+
expectedHash: blockHash.toString(),
|
|
113
|
+
actualHash,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return snapshot;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public fork(blockNumber?: BlockNumber, opts?: { closeDelayMs?: number }): Promise<MerkleTreeWriteOperations> {
|
|
122
|
+
return this.merkleTreeDb.fork(blockNumber, opts);
|
|
86
123
|
}
|
|
87
124
|
|
|
88
125
|
public backupTo(dstPath: string, compact?: boolean): Promise<Record<Exclude<SnapshotDataKeys, 'archiver'>, string>> {
|
|
@@ -102,9 +139,7 @@ export class ServerWorldStateSynchronizer
|
|
|
102
139
|
}
|
|
103
140
|
|
|
104
141
|
// Get the current latest block number
|
|
105
|
-
this.latestBlockNumberAtStart = await
|
|
106
|
-
? this.l2BlockSource.getProvenBlockNumber()
|
|
107
|
-
: this.l2BlockSource.getBlockNumber());
|
|
142
|
+
this.latestBlockNumberAtStart = BlockNumber(await this.l2BlockSource.getBlockNumber());
|
|
108
143
|
|
|
109
144
|
const blockToDownloadFrom = (await this.getLatestBlockNumber()) + 1;
|
|
110
145
|
|
|
@@ -125,13 +160,12 @@ export class ServerWorldStateSynchronizer
|
|
|
125
160
|
return this.syncPromise.promise;
|
|
126
161
|
}
|
|
127
162
|
|
|
128
|
-
protected createBlockStream():
|
|
129
|
-
const tracer = this.instrumentation.telemetry.getTracer('WorldStateL2BlockStream');
|
|
163
|
+
protected createBlockStream(): EventDrivenL2BlockStream {
|
|
130
164
|
const logger = createLogger('world-state:block_stream');
|
|
131
|
-
return new
|
|
132
|
-
proven: this.config.worldStateProvenBlocksOnly,
|
|
165
|
+
return new EventDrivenL2BlockStream(this.l2BlockSource, this, this, logger, {
|
|
133
166
|
pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
|
|
134
167
|
batchSize: this.config.worldStateBlockRequestBatchSize,
|
|
168
|
+
ignoreCheckpoints: true,
|
|
135
169
|
});
|
|
136
170
|
}
|
|
137
171
|
|
|
@@ -147,10 +181,10 @@ export class ServerWorldStateSynchronizer
|
|
|
147
181
|
public async status(): Promise<WorldStateSynchronizerStatus> {
|
|
148
182
|
const summary = await this.merkleTreeDb.getStatusSummary();
|
|
149
183
|
const status: WorldStateSyncStatus = {
|
|
150
|
-
latestBlockNumber:
|
|
151
|
-
latestBlockHash: (await this.getL2BlockHash(
|
|
152
|
-
finalizedBlockNumber:
|
|
153
|
-
oldestHistoricBlockNumber:
|
|
184
|
+
latestBlockNumber: summary.unfinalizedBlockNumber,
|
|
185
|
+
latestBlockHash: (await this.getL2BlockHash(summary.unfinalizedBlockNumber)) ?? '',
|
|
186
|
+
finalizedBlockNumber: summary.finalizedBlockNumber,
|
|
187
|
+
oldestHistoricBlockNumber: summary.oldestHistoricalBlock,
|
|
154
188
|
treesAreSynched: summary.treesAreSynched,
|
|
155
189
|
};
|
|
156
190
|
return {
|
|
@@ -160,7 +194,7 @@ export class ServerWorldStateSynchronizer
|
|
|
160
194
|
}
|
|
161
195
|
|
|
162
196
|
public async getLatestBlockNumber() {
|
|
163
|
-
return (await this.getL2Tips()).
|
|
197
|
+
return (await this.getL2Tips()).proposed.number;
|
|
164
198
|
}
|
|
165
199
|
|
|
166
200
|
public async stopSync() {
|
|
@@ -181,10 +215,10 @@ export class ServerWorldStateSynchronizer
|
|
|
181
215
|
/**
|
|
182
216
|
* Forces an immediate sync.
|
|
183
217
|
* @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
|
|
184
|
-
* @param
|
|
218
|
+
* @param blockHash - If provided, verifies the block at targetBlockNumber matches this hash. On mismatch, triggers a resync (reorg detection).
|
|
185
219
|
* @returns A promise that resolves with the block number the world state was synced to
|
|
186
220
|
*/
|
|
187
|
-
public async syncImmediate(targetBlockNumber?:
|
|
221
|
+
public async syncImmediate(targetBlockNumber?: BlockNumber, blockHash?: BlockHash): Promise<BlockNumber> {
|
|
188
222
|
if (this.currentState !== WorldStateRunningState.RUNNING) {
|
|
189
223
|
throw new Error(`World State is not running. Unable to perform sync.`);
|
|
190
224
|
}
|
|
@@ -196,13 +230,25 @@ export class ServerWorldStateSynchronizer
|
|
|
196
230
|
// If we have been given a block number to sync to and we have reached that number then return
|
|
197
231
|
const currentBlockNumber = await this.getLatestBlockNumber();
|
|
198
232
|
if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
|
|
199
|
-
|
|
233
|
+
if (blockHash === undefined) {
|
|
234
|
+
return currentBlockNumber;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// If a block hash was provided, verify we're on the expected fork
|
|
238
|
+
const currentHash = await this.getL2BlockHash(targetBlockNumber);
|
|
239
|
+
if (currentHash === blockHash.toString()) {
|
|
240
|
+
return currentBlockNumber;
|
|
241
|
+
}
|
|
242
|
+
// Hash mismatch: a reorg may have occurred, fall through to trigger sync
|
|
243
|
+
this.log.debug(
|
|
244
|
+
`World state block hash mismatch at ${targetBlockNumber} (expected ${blockHash}, got ${currentHash}). Triggering resync.`,
|
|
245
|
+
);
|
|
200
246
|
}
|
|
201
247
|
this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
|
|
202
248
|
|
|
203
249
|
// If the archiver is behind the target block, force an archiver sync
|
|
204
250
|
if (targetBlockNumber) {
|
|
205
|
-
const archiverLatestBlock = await this.l2BlockSource.getBlockNumber();
|
|
251
|
+
const archiverLatestBlock = BlockNumber(await this.l2BlockSource.getBlockNumber());
|
|
206
252
|
if (archiverLatestBlock < targetBlockNumber) {
|
|
207
253
|
this.log.debug(`Archiver is at ${archiverLatestBlock} behind target block ${targetBlockNumber}.`);
|
|
208
254
|
await this.l2BlockSource.syncImmediate();
|
|
@@ -214,7 +260,7 @@ export class ServerWorldStateSynchronizer
|
|
|
214
260
|
|
|
215
261
|
// If we have been given a block number to sync to and we have not reached that number then fail
|
|
216
262
|
const updatedBlockNumber = await this.getLatestBlockNumber();
|
|
217
|
-
if (
|
|
263
|
+
if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
|
|
218
264
|
throw new WorldStateSynchronizerError(
|
|
219
265
|
`Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
|
|
220
266
|
{
|
|
@@ -228,35 +274,62 @@ export class ServerWorldStateSynchronizer
|
|
|
228
274
|
);
|
|
229
275
|
}
|
|
230
276
|
|
|
277
|
+
// If a block hash was provided, verify we're on the expected fork after syncing, throw otherwise
|
|
278
|
+
if (blockHash !== undefined && targetBlockNumber !== undefined) {
|
|
279
|
+
const updatedHash = await this.getL2BlockHash(targetBlockNumber);
|
|
280
|
+
if (updatedHash !== blockHash.toString()) {
|
|
281
|
+
throw new WorldStateSynchronizerError(
|
|
282
|
+
`Block hash mismatch at block ${targetBlockNumber} (expected ${blockHash} but got ${updatedHash})`,
|
|
283
|
+
{
|
|
284
|
+
cause: {
|
|
285
|
+
reason: 'block_hash_mismatch',
|
|
286
|
+
targetBlockNumber,
|
|
287
|
+
expectedHash: blockHash.toString(),
|
|
288
|
+
actualHash: updatedHash,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
231
295
|
return updatedBlockNumber;
|
|
232
296
|
}
|
|
233
297
|
|
|
234
298
|
/** Returns the L2 block hash for a given number. Used by the L2BlockStream for detecting reorgs. */
|
|
235
|
-
public async getL2BlockHash(number:
|
|
236
|
-
if (number ===
|
|
299
|
+
public async getL2BlockHash(number: BlockNumber): Promise<string | undefined> {
|
|
300
|
+
if (number === BlockNumber.ZERO) {
|
|
237
301
|
return (await this.merkleTreeCommitted.getInitialHeader().hash()).toString();
|
|
238
302
|
}
|
|
239
|
-
|
|
240
|
-
this.latestBlockHashQuery = {
|
|
241
|
-
hash: await this.merkleTreeCommitted
|
|
242
|
-
.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number))
|
|
243
|
-
.then(leaf => leaf?.toString()),
|
|
244
|
-
blockNumber: number,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
return this.latestBlockHashQuery.hash;
|
|
303
|
+
return this.merkleTreeCommitted.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number)).then(leaf => leaf?.toString());
|
|
248
304
|
}
|
|
249
305
|
|
|
250
|
-
/**
|
|
251
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Returns the proposed, proven, and finalized block tips of the chain. World state drives its block stream with
|
|
308
|
+
* `ignoreCheckpoints`, so it does not track checkpointed blocks or checkpoints and omits `checkpointed` from the tips
|
|
309
|
+
* it reports.
|
|
310
|
+
*/
|
|
311
|
+
public async getL2Tips(): Promise<LocalChainTips> {
|
|
252
312
|
const status = await this.merkleTreeDb.getStatusSummary();
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
|
|
313
|
+
const unfinalizedBlockHashPromise = this.getL2BlockHash(status.unfinalizedBlockNumber);
|
|
314
|
+
const finalizedBlockHashPromise = this.getL2BlockHash(status.finalizedBlockNumber);
|
|
315
|
+
|
|
316
|
+
const provenBlockNumber = this.provenBlockNumber ?? status.finalizedBlockNumber;
|
|
317
|
+
const provenBlockHashPromise =
|
|
318
|
+
this.provenBlockNumber === undefined ? finalizedBlockHashPromise : this.getL2BlockHash(this.provenBlockNumber);
|
|
319
|
+
|
|
320
|
+
const [unfinalizedBlockHash, finalizedBlockHash, provenBlockHash] = await Promise.all([
|
|
321
|
+
unfinalizedBlockHashPromise,
|
|
322
|
+
finalizedBlockHashPromise,
|
|
323
|
+
provenBlockHashPromise,
|
|
324
|
+
]);
|
|
256
325
|
return {
|
|
257
|
-
|
|
258
|
-
finalized: {
|
|
259
|
-
|
|
326
|
+
proposed: { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash },
|
|
327
|
+
finalized: {
|
|
328
|
+
block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash },
|
|
329
|
+
},
|
|
330
|
+
proven: {
|
|
331
|
+
block: { number: provenBlockNumber, hash: provenBlockHash },
|
|
332
|
+
},
|
|
260
333
|
};
|
|
261
334
|
}
|
|
262
335
|
|
|
@@ -264,7 +337,7 @@ export class ServerWorldStateSynchronizer
|
|
|
264
337
|
public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
|
|
265
338
|
switch (event.type) {
|
|
266
339
|
case 'blocks-added':
|
|
267
|
-
await this.handleL2Blocks(event.blocks
|
|
340
|
+
await this.handleL2Blocks(event.blocks);
|
|
268
341
|
break;
|
|
269
342
|
case 'chain-pruned':
|
|
270
343
|
await this.handleChainPruned(event.block.number);
|
|
@@ -275,6 +348,15 @@ export class ServerWorldStateSynchronizer
|
|
|
275
348
|
case 'chain-finalized':
|
|
276
349
|
await this.handleChainFinalized(event.block.number);
|
|
277
350
|
break;
|
|
351
|
+
// World state runs in block mode with ignoreCheckpoints: it tracks tips via blocks-added/pruned/proven/finalized
|
|
352
|
+
// and ignores the thin tip events (it never anchors on them).
|
|
353
|
+
case 'chain-proposed':
|
|
354
|
+
case 'chain-checkpointed':
|
|
355
|
+
break;
|
|
356
|
+
default: {
|
|
357
|
+
const _: never = event;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
278
360
|
}
|
|
279
361
|
}
|
|
280
362
|
|
|
@@ -284,21 +366,31 @@ export class ServerWorldStateSynchronizer
|
|
|
284
366
|
* @returns Whether the block handled was produced by this same node.
|
|
285
367
|
*/
|
|
286
368
|
private async handleL2Blocks(l2Blocks: L2Block[]) {
|
|
287
|
-
this.log.
|
|
369
|
+
this.log.debug(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
|
|
370
|
+
|
|
371
|
+
// Fetch the L1->L2 messages for the first block in a checkpoint.
|
|
372
|
+
const messagesForBlocks = new Map<BlockNumber, Fr[]>();
|
|
373
|
+
await Promise.all(
|
|
374
|
+
l2Blocks
|
|
375
|
+
.filter(b => b.indexWithinCheckpoint === 0)
|
|
376
|
+
.map(async block => {
|
|
377
|
+
const l1ToL2Messages = await this.l2BlockSource.getL1ToL2Messages(block.checkpointNumber);
|
|
378
|
+
messagesForBlocks.set(block.number, l1ToL2Messages);
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
288
381
|
|
|
289
|
-
const messagePromises = l2Blocks.map(block => this.l2BlockSource.getL1ToL2Messages(block.number));
|
|
290
|
-
const l1ToL2Messages: Fr[][] = await Promise.all(messagePromises);
|
|
291
382
|
let updateStatus: WorldStateStatusFull | undefined = undefined;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
383
|
+
for (const block of l2Blocks) {
|
|
384
|
+
const [duration, result] = await elapsed(() =>
|
|
385
|
+
this.handleL2Block(block, messagesForBlocks.get(block.number) ?? []),
|
|
386
|
+
);
|
|
387
|
+
this.log.info(`World state updated with L2 block ${block.number}`, {
|
|
296
388
|
eventName: 'l2-block-handled',
|
|
297
389
|
duration,
|
|
298
|
-
unfinalizedBlockNumber: result.summary.unfinalizedBlockNumber,
|
|
299
|
-
finalizedBlockNumber: result.summary.finalizedBlockNumber,
|
|
300
|
-
oldestHistoricBlock: result.summary.oldestHistoricalBlock,
|
|
301
|
-
...
|
|
390
|
+
unfinalizedBlockNumber: BigInt(result.summary.unfinalizedBlockNumber),
|
|
391
|
+
finalizedBlockNumber: BigInt(result.summary.finalizedBlockNumber),
|
|
392
|
+
oldestHistoricBlock: BigInt(result.summary.oldestHistoricalBlock),
|
|
393
|
+
...block.getStats(),
|
|
302
394
|
} satisfies L2BlockHandledStats);
|
|
303
395
|
updateStatus = result;
|
|
304
396
|
}
|
|
@@ -315,17 +407,12 @@ export class ServerWorldStateSynchronizer
|
|
|
315
407
|
* @returns Whether the block handled was produced by this same node.
|
|
316
408
|
*/
|
|
317
409
|
private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
|
|
318
|
-
|
|
319
|
-
// Note that we cannot optimize this check by checking the root of the subtree after inserting the messages
|
|
320
|
-
// to the real L1_TO_L2_MESSAGE_TREE (like we do in merkleTreeDb.handleL2BlockAndMessages(...)) because that
|
|
321
|
-
// tree uses pedersen and we don't have access to the converted root.
|
|
322
|
-
await this.verifyMessagesHashToInHash(l1ToL2Messages, l2Block.header.contentCommitment.inHash);
|
|
323
|
-
|
|
324
|
-
// If the above check succeeds, we can proceed to handle the block.
|
|
325
|
-
this.log.trace(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
|
|
410
|
+
this.log.debug(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
|
|
326
411
|
blockNumber: l2Block.number,
|
|
327
412
|
blockHash: await l2Block.hash().then(h => h.toString()),
|
|
328
413
|
l1ToL2Messages: l1ToL2Messages.map(msg => msg.toString()),
|
|
414
|
+
blockHeader: l2Block.header.toInspect(),
|
|
415
|
+
blockStats: l2Block.getStats(),
|
|
329
416
|
});
|
|
330
417
|
const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages);
|
|
331
418
|
|
|
@@ -337,32 +424,73 @@ export class ServerWorldStateSynchronizer
|
|
|
337
424
|
return result;
|
|
338
425
|
}
|
|
339
426
|
|
|
340
|
-
private async handleChainFinalized(blockNumber:
|
|
427
|
+
private async handleChainFinalized(blockNumber: BlockNumber) {
|
|
341
428
|
this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
|
|
342
|
-
|
|
429
|
+
// If the finalized block number is older than the oldest available block in world state,
|
|
430
|
+
// skip entirely. The finalized block number can jump backwards (e.g. when the finalization
|
|
431
|
+
// heuristic changes) and try to read block data that has already been pruned. When this
|
|
432
|
+
// happens, there is nothing useful to do — the native world state is already finalized
|
|
433
|
+
// past this point and pruning has already happened.
|
|
434
|
+
const currentSummary = await this.merkleTreeDb.getStatusSummary();
|
|
435
|
+
if (blockNumber < currentSummary.oldestHistoricalBlock || blockNumber < 1) {
|
|
436
|
+
this.log.trace(
|
|
437
|
+
`Finalized block ${blockNumber} is older than the oldest available block ${currentSummary.oldestHistoricalBlock}. Skipping.`,
|
|
438
|
+
);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const summary = await this.merkleTreeDb.setFinalized(blockNumber);
|
|
343
442
|
if (this.historyToKeep === undefined) {
|
|
344
443
|
return;
|
|
345
444
|
}
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
445
|
+
const finalisedBlockData = await this.l2BlockSource.getBlockData({ number: summary.finalizedBlockNumber });
|
|
446
|
+
if (finalisedBlockData === undefined) {
|
|
447
|
+
this.log.warn(
|
|
448
|
+
`Failed to retrieve checkpointed block for finalized block number: ${summary.finalizedBlockNumber}`,
|
|
449
|
+
);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
// Compute the required historic checkpoint number
|
|
453
|
+
const newHistoricCheckpointNumber = finalisedBlockData.checkpointNumber - this.historyToKeep + 1;
|
|
454
|
+
if (newHistoricCheckpointNumber <= 1) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
// Retrieve the historic checkpoint
|
|
458
|
+
const historicCheckpoint = await this.l2BlockSource.getCheckpoint({
|
|
459
|
+
number: CheckpointNumber(newHistoricCheckpointNumber),
|
|
460
|
+
});
|
|
461
|
+
if (!historicCheckpoint) {
|
|
462
|
+
this.log.warn(`Failed to retrieve checkpoint number ${newHistoricCheckpointNumber} from Archiver`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (historicCheckpoint.checkpoint.blocks.length === 0 || historicCheckpoint.checkpoint.blocks[0] === undefined) {
|
|
466
|
+
this.log.warn(`Retrieved checkpoint number ${newHistoricCheckpointNumber} has no blocks!`);
|
|
348
467
|
return;
|
|
349
468
|
}
|
|
350
|
-
|
|
351
|
-
const
|
|
469
|
+
// Find the block at the start of the checkpoint and remove blocks up to this one
|
|
470
|
+
const newHistoricBlock = historicCheckpoint.checkpoint.blocks[0];
|
|
471
|
+
if (newHistoricBlock.number <= currentSummary.oldestHistoricalBlock) {
|
|
472
|
+
this.log.debug(
|
|
473
|
+
`Historic block ${newHistoricBlock.number} is not newer than oldest available ${currentSummary.oldestHistoricalBlock}. Skipping prune.`,
|
|
474
|
+
);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
this.log.verbose(`Pruning historic blocks to ${newHistoricBlock.number}`);
|
|
478
|
+
const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock.number));
|
|
352
479
|
this.log.debug(`World state summary `, status.summary);
|
|
353
480
|
}
|
|
354
481
|
|
|
355
|
-
private handleChainProven(blockNumber:
|
|
356
|
-
this.provenBlockNumber =
|
|
482
|
+
private handleChainProven(blockNumber: BlockNumber) {
|
|
483
|
+
this.provenBlockNumber = blockNumber;
|
|
357
484
|
this.log.debug(`Proven chain is now at block ${blockNumber}`);
|
|
358
485
|
return Promise.resolve();
|
|
359
486
|
}
|
|
360
487
|
|
|
361
|
-
private async handleChainPruned(blockNumber:
|
|
362
|
-
this.log.
|
|
363
|
-
const status = await this.merkleTreeDb.unwindBlocks(
|
|
364
|
-
this.
|
|
365
|
-
|
|
488
|
+
private async handleChainPruned(blockNumber: BlockNumber) {
|
|
489
|
+
this.log.info(`Chain pruned to block ${blockNumber}`);
|
|
490
|
+
const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
|
|
491
|
+
if (this.provenBlockNumber !== undefined && this.provenBlockNumber > blockNumber) {
|
|
492
|
+
this.provenBlockNumber = undefined;
|
|
493
|
+
}
|
|
366
494
|
this.instrumentation.updateWorldStateMetrics(status);
|
|
367
495
|
}
|
|
368
496
|
|
|
@@ -374,24 +502,4 @@ export class ServerWorldStateSynchronizer
|
|
|
374
502
|
this.currentState = newState;
|
|
375
503
|
this.log.debug(`Moved to state ${WorldStateRunningState[this.currentState]}`);
|
|
376
504
|
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Verifies that the L1 to L2 messages hash to the block inHash.
|
|
380
|
-
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
381
|
-
* @param inHash - The inHash of the block.
|
|
382
|
-
* @throws If the L1 to L2 messages do not hash to the block inHash.
|
|
383
|
-
*/
|
|
384
|
-
protected async verifyMessagesHashToInHash(l1ToL2Messages: Fr[], inHash: Fr) {
|
|
385
|
-
const treeCalculator = await MerkleTreeCalculator.create(
|
|
386
|
-
L1_TO_L2_MSG_SUBTREE_HEIGHT,
|
|
387
|
-
Buffer.alloc(32),
|
|
388
|
-
(lhs, rhs) => Promise.resolve(new SHA256Trunc().hash(lhs, rhs)),
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
const root = await treeCalculator.computeTreeRoot(l1ToL2Messages.map(msg => msg.toBuffer()));
|
|
392
|
-
|
|
393
|
-
if (!root.equals(inHash.toBuffer())) {
|
|
394
|
-
throw new Error('Obtained L1 to L2 messages failed to be hashed to the block inHash');
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
505
|
}
|