@aztec/world-state 0.0.1-commit.fffb133c → 0.0.1-dev
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/instrumentation/instrumentation.d.ts +1 -1
- package/dest/instrumentation/instrumentation.d.ts.map +1 -1
- package/dest/instrumentation/instrumentation.js +9 -2
- 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/merkle_trees_facade.d.ts +5 -5
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +9 -6
- package/dest/native/message.d.ts +17 -5
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/native_world_state.d.ts +5 -5
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +13 -9
- package/dest/native/native_world_state_instance.d.ts +3 -3
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +4 -4
- package/dest/synchronizer/config.d.ts +3 -3
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +6 -3
- package/dest/synchronizer/factory.d.ts +4 -3
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +5 -5
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +4 -4
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +83 -19
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +6 -1
- package/package.json +10 -10
- package/src/instrumentation/instrumentation.ts +9 -1
- package/src/native/fork_checkpoint.ts +19 -3
- package/src/native/merkle_trees_facade.ts +14 -7
- package/src/native/message.ts +18 -4
- package/src/native/native_world_state.ts +24 -11
- package/src/native/native_world_state_instance.ts +6 -4
- package/src/synchronizer/config.ts +7 -5
- package/src/synchronizer/factory.ts +7 -1
- package/src/synchronizer/server_world_state_synchronizer.ts +99 -21
- package/src/test/utils.ts +10 -1
|
@@ -4,9 +4,9 @@ import { fromEntries, padArrayEnd } from '@aztec/foundation/collection';
|
|
|
4
4
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
6
|
import { tryRmDir } from '@aztec/foundation/fs';
|
|
7
|
-
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
7
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
8
8
|
import type { L2Block } from '@aztec/stdlib/block';
|
|
9
|
-
import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
|
|
9
|
+
import { DatabaseVersionManager } from '@aztec/stdlib/database-version/manager';
|
|
10
10
|
import type {
|
|
11
11
|
IndexedTreeId,
|
|
12
12
|
MerkleTreeReadOperations,
|
|
@@ -52,7 +52,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
52
52
|
protected constructor(
|
|
53
53
|
protected instance: NativeWorldState,
|
|
54
54
|
protected readonly worldStateInstrumentation: WorldStateInstrumentation,
|
|
55
|
-
protected readonly log: Logger
|
|
55
|
+
protected readonly log: Logger,
|
|
56
56
|
private readonly cleanup = () => Promise.resolve(),
|
|
57
57
|
) {}
|
|
58
58
|
|
|
@@ -62,9 +62,10 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
62
62
|
wsTreeMapSizes: WorldStateTreeMapSizes,
|
|
63
63
|
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
64
64
|
instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
|
|
65
|
-
|
|
65
|
+
bindings?: LoggerBindings,
|
|
66
66
|
cleanup = () => Promise.resolve(),
|
|
67
67
|
): Promise<NativeWorldStateService> {
|
|
68
|
+
const log = createLogger('world-state:database', bindings);
|
|
68
69
|
const worldStateDirectory = join(dataDir, WORLD_STATE_DIR);
|
|
69
70
|
// Create a version manager to handle versioning
|
|
70
71
|
const versionManager = new DatabaseVersionManager({
|
|
@@ -72,7 +73,9 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
72
73
|
rollupAddress,
|
|
73
74
|
dataDirectory: worldStateDirectory,
|
|
74
75
|
onOpen: (dir: string) => {
|
|
75
|
-
return Promise.resolve(
|
|
76
|
+
return Promise.resolve(
|
|
77
|
+
new NativeWorldState(dir, wsTreeMapSizes, prefilledPublicData, instrumentation, bindings),
|
|
78
|
+
);
|
|
76
79
|
},
|
|
77
80
|
});
|
|
78
81
|
|
|
@@ -93,8 +96,9 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
93
96
|
cleanupTmpDir = true,
|
|
94
97
|
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
95
98
|
instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
|
|
99
|
+
bindings?: LoggerBindings,
|
|
96
100
|
): Promise<NativeWorldStateService> {
|
|
97
|
-
const log = createLogger('world-state:database');
|
|
101
|
+
const log = createLogger('world-state:database', bindings);
|
|
98
102
|
const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
|
|
99
103
|
const dbMapSizeKb = 10 * 1024 * 1024;
|
|
100
104
|
const worldStateTreeMapSizes: WorldStateTreeMapSizes = {
|
|
@@ -116,7 +120,15 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
116
120
|
}
|
|
117
121
|
};
|
|
118
122
|
|
|
119
|
-
return this.new(
|
|
123
|
+
return this.new(
|
|
124
|
+
rollupAddress,
|
|
125
|
+
dataDir,
|
|
126
|
+
worldStateTreeMapSizes,
|
|
127
|
+
prefilledPublicData,
|
|
128
|
+
instrumentation,
|
|
129
|
+
bindings,
|
|
130
|
+
cleanup,
|
|
131
|
+
);
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
protected async init() {
|
|
@@ -135,9 +147,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
135
147
|
|
|
136
148
|
// the initial header _must_ be the first element in the archive tree
|
|
137
149
|
// if this assertion fails, check that the hashing done in Header in yarn-project matches the initial header hash done in world_state.cpp
|
|
138
|
-
const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [
|
|
139
|
-
(await this.initialHeader.hash()).toField(),
|
|
140
|
-
]);
|
|
150
|
+
const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [(await this.initialHeader.hash()).toFr()]);
|
|
141
151
|
const initialHeaderIndex = indices[0];
|
|
142
152
|
assert.strictEqual(initialHeaderIndex, 0n, 'Invalid initial archive state');
|
|
143
153
|
}
|
|
@@ -222,7 +232,10 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
222
232
|
WorldStateMessageType.SYNC_BLOCK,
|
|
223
233
|
{
|
|
224
234
|
blockNumber: l2Block.number,
|
|
225
|
-
blockHeaderHash: await l2Block.hash(),
|
|
235
|
+
blockHeaderHash: (await l2Block.hash()).toBuffer(),
|
|
236
|
+
// Forwarded so the native sync verifies the archive root against canonical and rejects a divergent tree.
|
|
237
|
+
expectedArchiveRoot: l2Block.archive.root.toBuffer(),
|
|
238
|
+
expectedPreviousArchiveRoot: l2Block.header.lastArchive.root.toBuffer(),
|
|
226
239
|
paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
|
|
227
240
|
paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
|
|
228
241
|
paddedNullifiers: paddedNullifiers.map(serializeLeaf),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ARCHIVE_HEIGHT,
|
|
3
|
-
|
|
3
|
+
DomainSeparator,
|
|
4
4
|
L1_TO_L2_MSG_TREE_HEIGHT,
|
|
5
5
|
MAX_NULLIFIERS_PER_TX,
|
|
6
6
|
MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
NULLIFIER_TREE_HEIGHT,
|
|
9
9
|
PUBLIC_DATA_TREE_HEIGHT,
|
|
10
10
|
} from '@aztec/constants';
|
|
11
|
-
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
11
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
12
12
|
import { NativeWorldState as BaseNativeWorldState, MsgpackChannel } from '@aztec/native';
|
|
13
13
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
14
14
|
import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
@@ -57,7 +57,8 @@ export class NativeWorldState implements NativeWorldStateInstance {
|
|
|
57
57
|
private readonly wsTreeMapSizes: WorldStateTreeMapSizes,
|
|
58
58
|
private readonly prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
59
59
|
private readonly instrumentation: WorldStateInstrumentation,
|
|
60
|
-
|
|
60
|
+
bindings?: LoggerBindings,
|
|
61
|
+
private readonly log: Logger = createLogger('world-state:database', bindings),
|
|
61
62
|
) {
|
|
62
63
|
const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
|
|
63
64
|
log.info(
|
|
@@ -80,7 +81,7 @@ export class NativeWorldState implements NativeWorldStateInstance {
|
|
|
80
81
|
[MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
81
82
|
},
|
|
82
83
|
prefilledPublicDataBufferArray,
|
|
83
|
-
|
|
84
|
+
DomainSeparator.BLOCK_HEADER_HASH,
|
|
84
85
|
{
|
|
85
86
|
[MerkleTreeId.NULLIFIER_TREE]: wsTreeMapSizes.nullifierTreeMapSizeKb,
|
|
86
87
|
[MerkleTreeId.NOTE_HASH_TREE]: wsTreeMapSizes.noteHashTreeMapSizeKb,
|
|
@@ -105,6 +106,7 @@ export class NativeWorldState implements NativeWorldStateInstance {
|
|
|
105
106
|
this.wsTreeMapSizes,
|
|
106
107
|
this.prefilledPublicData,
|
|
107
108
|
this.instrumentation,
|
|
109
|
+
this.log.getBindings(),
|
|
108
110
|
this.log,
|
|
109
111
|
);
|
|
110
112
|
}
|
|
@@ -29,8 +29,8 @@ export interface WorldStateConfig {
|
|
|
29
29
|
/** Optional directory for the world state DB, if unspecified will default to the general data directory */
|
|
30
30
|
worldStateDataDirectory?: string;
|
|
31
31
|
|
|
32
|
-
/** The number of historic blocks to maintain */
|
|
33
|
-
|
|
32
|
+
/** The number of historic checkpoints worth of blocks to maintain */
|
|
33
|
+
worldStateCheckpointHistory: number;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
@@ -84,9 +84,11 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
|
84
84
|
env: 'WS_DATA_DIRECTORY',
|
|
85
85
|
description: 'Optional directory for the world state database',
|
|
86
86
|
},
|
|
87
|
-
|
|
88
|
-
env: '
|
|
89
|
-
description:
|
|
87
|
+
worldStateCheckpointHistory: {
|
|
88
|
+
env: 'WS_NUM_HISTORIC_CHECKPOINTS',
|
|
89
|
+
description:
|
|
90
|
+
'The number of historic checkpoints worth of blocks to maintain. Values less than 1 mean all history is maintained',
|
|
91
|
+
fallback: ['WS_NUM_HISTORIC_BLOCKS'],
|
|
90
92
|
...numberConfigHelper(64),
|
|
91
93
|
},
|
|
92
94
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LoggerBindings } from '@aztec/foundation/log';
|
|
1
2
|
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
2
3
|
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
3
4
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
@@ -22,9 +23,10 @@ export async function createWorldStateSynchronizer(
|
|
|
22
23
|
l2BlockSource: L2BlockSource & L1ToL2MessageSource,
|
|
23
24
|
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
24
25
|
client: TelemetryClient = getTelemetryClient(),
|
|
26
|
+
bindings?: LoggerBindings,
|
|
25
27
|
) {
|
|
26
28
|
const instrumentation = new WorldStateInstrumentation(client);
|
|
27
|
-
const merkleTrees = await createWorldState(config, prefilledPublicData, instrumentation);
|
|
29
|
+
const merkleTrees = await createWorldState(config, prefilledPublicData, instrumentation, bindings);
|
|
28
30
|
return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, instrumentation);
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -42,6 +44,7 @@ export async function createWorldState(
|
|
|
42
44
|
Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKb' | 'l1Contracts'>,
|
|
43
45
|
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
44
46
|
instrumentation: WorldStateInstrumentation = new WorldStateInstrumentation(getTelemetryClient()),
|
|
47
|
+
bindings?: LoggerBindings,
|
|
45
48
|
) {
|
|
46
49
|
const dataDirectory = config.worldStateDataDirectory ?? config.dataDirectory;
|
|
47
50
|
const dataStoreMapSizeKb = config.worldStateDbMapSizeKb ?? config.dataStoreMapSizeKb;
|
|
@@ -65,11 +68,14 @@ export async function createWorldState(
|
|
|
65
68
|
wsTreeMapSizes,
|
|
66
69
|
prefilledPublicData,
|
|
67
70
|
instrumentation,
|
|
71
|
+
bindings,
|
|
68
72
|
)
|
|
69
73
|
: await NativeWorldStateService.tmp(
|
|
70
74
|
config.l1Contracts.rollupAddress,
|
|
71
75
|
!['true', '1'].includes(process.env.DEBUG_WORLD_STATE!),
|
|
72
76
|
prefilledPublicData,
|
|
77
|
+
instrumentation,
|
|
78
|
+
bindings,
|
|
73
79
|
);
|
|
74
80
|
|
|
75
81
|
return merkleTrees;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { GENESIS_BLOCK_HEADER_HASH,
|
|
2
|
-
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
1
|
+
import { GENESIS_BLOCK_HEADER_HASH, INITIAL_CHECKPOINT_NUMBER, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
2
|
+
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
4
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
6
6
|
import { elapsed } from '@aztec/foundation/timer';
|
|
7
7
|
import {
|
|
8
|
+
type BlockHash,
|
|
8
9
|
GENESIS_CHECKPOINT_HEADER_HASH,
|
|
9
10
|
type L2Block,
|
|
10
11
|
type L2BlockId,
|
|
@@ -64,7 +65,7 @@ export class ServerWorldStateSynchronizer
|
|
|
64
65
|
private readonly log: Logger = createLogger('world_state'),
|
|
65
66
|
) {
|
|
66
67
|
this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
|
|
67
|
-
this.historyToKeep = config.
|
|
68
|
+
this.historyToKeep = config.worldStateCheckpointHistory < 1 ? undefined : config.worldStateCheckpointHistory;
|
|
68
69
|
this.log.info(
|
|
69
70
|
`Created world state synchroniser with block history of ${
|
|
70
71
|
this.historyToKeep === undefined ? 'infinity' : this.historyToKeep
|
|
@@ -177,13 +178,10 @@ export class ServerWorldStateSynchronizer
|
|
|
177
178
|
/**
|
|
178
179
|
* Forces an immediate sync.
|
|
179
180
|
* @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
|
|
180
|
-
* @param
|
|
181
|
+
* @param blockHash - If provided, verifies the block at targetBlockNumber matches this hash. On mismatch, triggers a resync (reorg detection).
|
|
181
182
|
* @returns A promise that resolves with the block number the world state was synced to
|
|
182
183
|
*/
|
|
183
|
-
public async syncImmediate(
|
|
184
|
-
targetBlockNumber?: BlockNumber,
|
|
185
|
-
skipThrowIfTargetNotReached?: boolean,
|
|
186
|
-
): Promise<BlockNumber> {
|
|
184
|
+
public async syncImmediate(targetBlockNumber?: BlockNumber, blockHash?: BlockHash): Promise<BlockNumber> {
|
|
187
185
|
if (this.currentState !== WorldStateRunningState.RUNNING) {
|
|
188
186
|
throw new Error(`World State is not running. Unable to perform sync.`);
|
|
189
187
|
}
|
|
@@ -195,7 +193,19 @@ export class ServerWorldStateSynchronizer
|
|
|
195
193
|
// If we have been given a block number to sync to and we have reached that number then return
|
|
196
194
|
const currentBlockNumber = await this.getLatestBlockNumber();
|
|
197
195
|
if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
|
|
198
|
-
|
|
196
|
+
if (blockHash === undefined) {
|
|
197
|
+
return currentBlockNumber;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// If a block hash was provided, verify we're on the expected fork
|
|
201
|
+
const currentHash = await this.getL2BlockHash(targetBlockNumber);
|
|
202
|
+
if (currentHash === blockHash.toString()) {
|
|
203
|
+
return currentBlockNumber;
|
|
204
|
+
}
|
|
205
|
+
// Hash mismatch: a reorg may have occurred, fall through to trigger sync
|
|
206
|
+
this.log.debug(
|
|
207
|
+
`World state block hash mismatch at ${targetBlockNumber} (expected ${blockHash}, got ${currentHash}). Triggering resync.`,
|
|
208
|
+
);
|
|
199
209
|
}
|
|
200
210
|
this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
|
|
201
211
|
|
|
@@ -213,7 +223,7 @@ export class ServerWorldStateSynchronizer
|
|
|
213
223
|
|
|
214
224
|
// If we have been given a block number to sync to and we have not reached that number then fail
|
|
215
225
|
const updatedBlockNumber = await this.getLatestBlockNumber();
|
|
216
|
-
if (
|
|
226
|
+
if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
|
|
217
227
|
throw new WorldStateSynchronizerError(
|
|
218
228
|
`Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
|
|
219
229
|
{
|
|
@@ -227,6 +237,24 @@ export class ServerWorldStateSynchronizer
|
|
|
227
237
|
);
|
|
228
238
|
}
|
|
229
239
|
|
|
240
|
+
// If a block hash was provided, verify we're on the expected fork after syncing, throw otherwise
|
|
241
|
+
if (blockHash !== undefined && targetBlockNumber !== undefined) {
|
|
242
|
+
const updatedHash = await this.getL2BlockHash(targetBlockNumber);
|
|
243
|
+
if (updatedHash !== blockHash.toString()) {
|
|
244
|
+
throw new WorldStateSynchronizerError(
|
|
245
|
+
`Block hash mismatch at block ${targetBlockNumber} (expected ${blockHash} but got ${updatedHash})`,
|
|
246
|
+
{
|
|
247
|
+
cause: {
|
|
248
|
+
reason: 'block_hash_mismatch',
|
|
249
|
+
targetBlockNumber,
|
|
250
|
+
expectedHash: blockHash.toString(),
|
|
251
|
+
actualHash: updatedHash,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
230
258
|
return updatedBlockNumber;
|
|
231
259
|
}
|
|
232
260
|
|
|
@@ -263,15 +291,15 @@ export class ServerWorldStateSynchronizer
|
|
|
263
291
|
proposed: latestBlockId,
|
|
264
292
|
checkpointed: {
|
|
265
293
|
block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
|
|
266
|
-
checkpoint: { number:
|
|
294
|
+
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
267
295
|
},
|
|
268
296
|
finalized: {
|
|
269
297
|
block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash ?? '' },
|
|
270
|
-
checkpoint: { number:
|
|
298
|
+
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
271
299
|
},
|
|
272
300
|
proven: {
|
|
273
301
|
block: { number: provenBlockNumber, hash: provenBlockHash ?? '' },
|
|
274
|
-
checkpoint: { number:
|
|
302
|
+
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
275
303
|
},
|
|
276
304
|
};
|
|
277
305
|
}
|
|
@@ -300,7 +328,7 @@ export class ServerWorldStateSynchronizer
|
|
|
300
328
|
* @returns Whether the block handled was produced by this same node.
|
|
301
329
|
*/
|
|
302
330
|
private async handleL2Blocks(l2Blocks: L2Block[]) {
|
|
303
|
-
this.log.
|
|
331
|
+
this.log.debug(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
|
|
304
332
|
|
|
305
333
|
// Fetch the L1->L2 messages for the first block in a checkpoint.
|
|
306
334
|
const messagesForBlocks = new Map<BlockNumber, Fr[]>();
|
|
@@ -341,10 +369,12 @@ export class ServerWorldStateSynchronizer
|
|
|
341
369
|
* @returns Whether the block handled was produced by this same node.
|
|
342
370
|
*/
|
|
343
371
|
private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
|
|
344
|
-
this.log.
|
|
372
|
+
this.log.debug(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
|
|
345
373
|
blockNumber: l2Block.number,
|
|
346
374
|
blockHash: await l2Block.hash().then(h => h.toString()),
|
|
347
375
|
l1ToL2Messages: l1ToL2Messages.map(msg => msg.toString()),
|
|
376
|
+
blockHeader: l2Block.header.toInspect(),
|
|
377
|
+
blockStats: l2Block.getStats(),
|
|
348
378
|
});
|
|
349
379
|
const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages);
|
|
350
380
|
|
|
@@ -357,17 +387,65 @@ export class ServerWorldStateSynchronizer
|
|
|
357
387
|
}
|
|
358
388
|
|
|
359
389
|
private async handleChainFinalized(blockNumber: BlockNumber) {
|
|
360
|
-
this.log.verbose(`
|
|
390
|
+
this.log.verbose(`Updating world state finalized chain to block ${blockNumber}`);
|
|
391
|
+
// If the finalized block number is older than the oldest available block in world state,
|
|
392
|
+
// skip entirely. The finalized block number can jump backwards (e.g. when the finalization
|
|
393
|
+
// heuristic changes) and try to read block data that has already been pruned. When this
|
|
394
|
+
// happens, there is nothing useful to do — the native world state is already finalized
|
|
395
|
+
// past this point and pruning has already happened.
|
|
396
|
+
const currentSummary = await this.merkleTreeDb.getStatusSummary();
|
|
397
|
+
if (blockNumber < currentSummary.oldestHistoricalBlock || blockNumber < 1) {
|
|
398
|
+
this.log.trace(
|
|
399
|
+
`Finalized block ${blockNumber} is older than the oldest available block ${currentSummary.oldestHistoricalBlock}. Skipping.`,
|
|
400
|
+
);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
361
403
|
const summary = await this.merkleTreeDb.setFinalized(blockNumber);
|
|
404
|
+
this.log.info(`World state finalized chain updated`, {
|
|
405
|
+
finalizedBlockNumber: summary.finalizedBlockNumber,
|
|
406
|
+
unfinalizedBlockNumber: summary.unfinalizedBlockNumber,
|
|
407
|
+
oldestHistoricalBlock: summary.oldestHistoricalBlock,
|
|
408
|
+
});
|
|
362
409
|
if (this.historyToKeep === undefined) {
|
|
363
410
|
return;
|
|
364
411
|
}
|
|
365
|
-
|
|
366
|
-
|
|
412
|
+
// Get the checkpointed block for the finalized block number
|
|
413
|
+
const finalisedCheckpoint = await this.l2BlockSource.getCheckpointedBlock(summary.finalizedBlockNumber);
|
|
414
|
+
if (finalisedCheckpoint === undefined) {
|
|
415
|
+
this.log.warn(
|
|
416
|
+
`Failed to retrieve checkpointed block for finalized block number: ${summary.finalizedBlockNumber}`,
|
|
417
|
+
);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
// Compute the required historic checkpoint number
|
|
421
|
+
const newHistoricCheckpointNumber = finalisedCheckpoint.checkpointNumber - this.historyToKeep + 1;
|
|
422
|
+
if (newHistoricCheckpointNumber <= 1) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
// Retrieve the historic checkpoint
|
|
426
|
+
const historicCheckpoints = await this.l2BlockSource.getCheckpoints(
|
|
427
|
+
CheckpointNumber(newHistoricCheckpointNumber),
|
|
428
|
+
1,
|
|
429
|
+
);
|
|
430
|
+
if (historicCheckpoints.length === 0 || historicCheckpoints[0] === undefined) {
|
|
431
|
+
this.log.warn(`Failed to retrieve checkpoint number ${newHistoricCheckpointNumber} from Archiver`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const historicCheckpoint = historicCheckpoints[0];
|
|
435
|
+
if (historicCheckpoint.checkpoint.blocks.length === 0 || historicCheckpoint.checkpoint.blocks[0] === undefined) {
|
|
436
|
+
this.log.warn(`Retrieved checkpoint number ${newHistoricCheckpointNumber} has no blocks!`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
// Find the block at the start of the checkpoint and remove blocks up to this one
|
|
440
|
+
const newHistoricBlock = historicCheckpoint.checkpoint.blocks[0];
|
|
441
|
+
if (newHistoricBlock.number <= currentSummary.oldestHistoricalBlock) {
|
|
442
|
+
this.log.debug(
|
|
443
|
+
`Historic block ${newHistoricBlock.number} is not newer than oldest available ${currentSummary.oldestHistoricalBlock}. Skipping prune.`,
|
|
444
|
+
);
|
|
367
445
|
return;
|
|
368
446
|
}
|
|
369
|
-
this.log.verbose(`Pruning historic blocks to ${newHistoricBlock}`);
|
|
370
|
-
const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock));
|
|
447
|
+
this.log.verbose(`Pruning historic blocks to ${newHistoricBlock.number}`);
|
|
448
|
+
const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock.number));
|
|
371
449
|
this.log.debug(`World state summary `, status.summary);
|
|
372
450
|
}
|
|
373
451
|
|
|
@@ -378,7 +456,7 @@ export class ServerWorldStateSynchronizer
|
|
|
378
456
|
}
|
|
379
457
|
|
|
380
458
|
private async handleChainPruned(blockNumber: BlockNumber) {
|
|
381
|
-
this.log.
|
|
459
|
+
this.log.info(`Chain pruned to block ${blockNumber}`);
|
|
382
460
|
const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
|
|
383
461
|
this.provenBlockNumber = undefined;
|
|
384
462
|
this.instrumentation.updateWorldStateMetrics(status);
|
package/src/test/utils.ts
CHANGED
|
@@ -60,7 +60,16 @@ export async function updateBlockState(block: L2Block, l1ToL2Messages: Fr[], for
|
|
|
60
60
|
await Promise.all([publicDataInsert, nullifierInsert, noteHashInsert, messageInsert]);
|
|
61
61
|
|
|
62
62
|
const state = await fork.getStateReference();
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
// Capture the archive root *before* appending this block so the header's lastArchive chains off the previous block.
|
|
65
|
+
// sync_block now verifies lastArchive against the committed archive root, so a block carrying an unchained value
|
|
66
|
+
// (e.g. the random lastArchive from L2Block.random) would be rejected as a divergence from the canonical chain.
|
|
67
|
+
const previousArchive = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
68
|
+
block.header = BlockHeader.from({
|
|
69
|
+
...block.header,
|
|
70
|
+
state,
|
|
71
|
+
lastArchive: new AppendOnlyTreeSnapshot(Fr.fromBuffer(previousArchive.root), Number(previousArchive.size)),
|
|
72
|
+
});
|
|
64
73
|
await fork.updateArchive(block.header);
|
|
65
74
|
|
|
66
75
|
const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|