@aztec/world-state 0.0.1-commit.d431d1c → 0.0.1-commit.d939eb5aa
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 +7 -6
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +17 -9
- package/dest/native/message.d.ts +13 -5
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/native_world_state.d.ts +10 -8
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +18 -12
- package/dest/native/native_world_state_instance.d.ts +5 -5
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +8 -7
- package/dest/native/world_state_ops_queue.js +5 -5
- package/dest/synchronizer/config.d.ts +3 -5
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +14 -16
- package/dest/synchronizer/factory.d.ts +6 -5
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +6 -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 +92 -22
- package/dest/test/utils.d.ts +6 -6
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +3 -3
- package/dest/testing.d.ts +4 -3
- package/dest/testing.d.ts.map +1 -1
- package/dest/testing.js +10 -6
- package/dest/world-state-db/merkle_tree_db.d.ts +3 -12
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
- package/package.json +9 -10
- package/src/instrumentation/instrumentation.ts +9 -1
- package/src/native/fork_checkpoint.ts +19 -3
- package/src/native/merkle_trees_facade.ts +24 -12
- package/src/native/message.ts +14 -4
- package/src/native/native_world_state.ts +22 -16
- package/src/native/native_world_state_instance.ts +14 -8
- package/src/native/world_state_ops_queue.ts +5 -5
- package/src/synchronizer/config.ts +15 -26
- package/src/synchronizer/factory.ts +13 -7
- package/src/synchronizer/server_world_state_synchronizer.ts +105 -30
- package/src/test/utils.ts +4 -4
- package/src/testing.ts +8 -9
- package/src/world-state-db/merkle_tree_db.ts +2 -12
|
@@ -96,7 +96,7 @@ export class WorldStateOpsQueue {
|
|
|
96
96
|
// then send the request immediately
|
|
97
97
|
// If a mutating request is in flight then we must wait
|
|
98
98
|
// If a mutating request is not in flight but something is queued then it must be a mutating request
|
|
99
|
-
if (this.inFlightMutatingCount
|
|
99
|
+
if (this.inFlightMutatingCount === 0 && this.requests.length === 0) {
|
|
100
100
|
this.sendEnqueuedRequest(op);
|
|
101
101
|
} else {
|
|
102
102
|
this.requests.push(op);
|
|
@@ -122,7 +122,7 @@ export class WorldStateOpsQueue {
|
|
|
122
122
|
--this.inFlightCount;
|
|
123
123
|
|
|
124
124
|
// If there are still requests in flight then do nothing further
|
|
125
|
-
if (this.inFlightCount
|
|
125
|
+
if (this.inFlightCount !== 0) {
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -134,7 +134,7 @@ export class WorldStateOpsQueue {
|
|
|
134
134
|
while (this.requests.length > 0) {
|
|
135
135
|
const next = this.requests[0];
|
|
136
136
|
if (next.mutating) {
|
|
137
|
-
if (this.inFlightCount
|
|
137
|
+
if (this.inFlightCount === 0) {
|
|
138
138
|
// send the mutating request
|
|
139
139
|
this.requests.shift();
|
|
140
140
|
this.sendEnqueuedRequest(next);
|
|
@@ -149,7 +149,7 @@ export class WorldStateOpsQueue {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// If the queue is empty, there is nothing in flight and we have been told to stop, then resolve the stop promise
|
|
152
|
-
if (this.inFlightCount
|
|
152
|
+
if (this.inFlightCount === 0 && this.stopResolve !== undefined) {
|
|
153
153
|
this.stopResolve();
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -182,7 +182,7 @@ export class WorldStateOpsQueue {
|
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
// If no outstanding requests then immediately resolve the promise
|
|
185
|
-
if (this.requests.length
|
|
185
|
+
if (this.requests.length === 0 && this.inFlightCount === 0 && this.stopResolve !== undefined) {
|
|
186
186
|
this.stopResolve();
|
|
187
187
|
}
|
|
188
188
|
return this.stopPromise;
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ConfigMappingsType,
|
|
3
|
-
booleanConfigHelper,
|
|
4
|
-
getConfigFromMappings,
|
|
5
|
-
numberConfigHelper,
|
|
6
|
-
} from '@aztec/foundation/config';
|
|
1
|
+
import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config';
|
|
7
2
|
|
|
8
3
|
/** World State synchronizer configuration values. */
|
|
9
4
|
export interface WorldStateConfig {
|
|
10
5
|
/** The frequency in which to check. */
|
|
11
6
|
worldStateBlockCheckIntervalMS: number;
|
|
12
7
|
|
|
13
|
-
/** Whether to follow only the proven chain. */
|
|
14
|
-
worldStateProvenBlocksOnly: boolean;
|
|
15
|
-
|
|
16
8
|
/** Size of the batch for each get-blocks request from the synchronizer to the archiver. */
|
|
17
9
|
worldStateBlockRequestBatchSize?: number;
|
|
18
10
|
|
|
@@ -37,8 +29,8 @@ export interface WorldStateConfig {
|
|
|
37
29
|
/** Optional directory for the world state DB, if unspecified will default to the general data directory */
|
|
38
30
|
worldStateDataDirectory?: string;
|
|
39
31
|
|
|
40
|
-
/** The number of historic blocks to maintain */
|
|
41
|
-
|
|
32
|
+
/** The number of historic checkpoints worth of blocks to maintain */
|
|
33
|
+
worldStateCheckpointHistory: number;
|
|
42
34
|
}
|
|
43
35
|
|
|
44
36
|
export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
@@ -48,48 +40,43 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
|
48
40
|
defaultValue: 100,
|
|
49
41
|
description: 'The frequency in which to check.',
|
|
50
42
|
},
|
|
51
|
-
worldStateProvenBlocksOnly: {
|
|
52
|
-
env: 'WS_PROVEN_BLOCKS_ONLY',
|
|
53
|
-
description: 'Whether to follow only the proven chain.',
|
|
54
|
-
...booleanConfigHelper(),
|
|
55
|
-
},
|
|
56
43
|
worldStateBlockRequestBatchSize: {
|
|
57
44
|
env: 'WS_BLOCK_REQUEST_BATCH_SIZE',
|
|
58
|
-
parseEnv: (val: string
|
|
45
|
+
parseEnv: (val: string) => +val,
|
|
59
46
|
description: 'Size of the batch for each get-blocks request from the synchronizer to the archiver.',
|
|
60
47
|
},
|
|
61
48
|
worldStateDbMapSizeKb: {
|
|
62
49
|
env: 'WS_DB_MAP_SIZE_KB',
|
|
63
|
-
parseEnv: (val: string
|
|
50
|
+
parseEnv: (val: string) => +val,
|
|
64
51
|
description: 'The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKb.',
|
|
65
52
|
},
|
|
66
53
|
archiveTreeMapSizeKb: {
|
|
67
54
|
env: 'ARCHIVE_TREE_MAP_SIZE_KB',
|
|
68
|
-
parseEnv: (val: string
|
|
55
|
+
parseEnv: (val: string) => +val,
|
|
69
56
|
description:
|
|
70
57
|
'The maximum possible size of the world state archive tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
71
58
|
},
|
|
72
59
|
nullifierTreeMapSizeKb: {
|
|
73
60
|
env: 'NULLIFIER_TREE_MAP_SIZE_KB',
|
|
74
|
-
parseEnv: (val: string
|
|
61
|
+
parseEnv: (val: string) => +val,
|
|
75
62
|
description:
|
|
76
63
|
'The maximum possible size of the world state nullifier tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
77
64
|
},
|
|
78
65
|
noteHashTreeMapSizeKb: {
|
|
79
66
|
env: 'NOTE_HASH_TREE_MAP_SIZE_KB',
|
|
80
|
-
parseEnv: (val: string
|
|
67
|
+
parseEnv: (val: string) => +val,
|
|
81
68
|
description:
|
|
82
69
|
'The maximum possible size of the world state note hash tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
83
70
|
},
|
|
84
71
|
messageTreeMapSizeKb: {
|
|
85
72
|
env: 'MESSAGE_TREE_MAP_SIZE_KB',
|
|
86
|
-
parseEnv: (val: string
|
|
73
|
+
parseEnv: (val: string) => +val,
|
|
87
74
|
description:
|
|
88
75
|
'The maximum possible size of the world state message tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
89
76
|
},
|
|
90
77
|
publicDataTreeMapSizeKb: {
|
|
91
78
|
env: 'PUBLIC_DATA_TREE_MAP_SIZE_KB',
|
|
92
|
-
parseEnv: (val: string
|
|
79
|
+
parseEnv: (val: string) => +val,
|
|
93
80
|
description:
|
|
94
81
|
'The maximum possible size of the world state public data tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
95
82
|
},
|
|
@@ -97,9 +84,11 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
|
97
84
|
env: 'WS_DATA_DIRECTORY',
|
|
98
85
|
description: 'Optional directory for the world state database',
|
|
99
86
|
},
|
|
100
|
-
|
|
101
|
-
env: '
|
|
102
|
-
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'],
|
|
103
92
|
...numberConfigHelper(64),
|
|
104
93
|
},
|
|
105
94
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoggerBindings } from '@aztec/foundation/log';
|
|
2
2
|
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
3
|
+
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
3
4
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
4
|
-
import type
|
|
5
|
+
import { EMPTY_GENESIS_DATA, type GenesisData } from '@aztec/stdlib/world-state';
|
|
5
6
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
6
7
|
|
|
7
8
|
import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
|
|
@@ -20,11 +21,12 @@ export interface WorldStateTreeMapSizes {
|
|
|
20
21
|
export async function createWorldStateSynchronizer(
|
|
21
22
|
config: WorldStateConfig & DataStoreConfig,
|
|
22
23
|
l2BlockSource: L2BlockSource & L1ToL2MessageSource,
|
|
23
|
-
|
|
24
|
+
genesis: GenesisData = EMPTY_GENESIS_DATA,
|
|
24
25
|
client: TelemetryClient = getTelemetryClient(),
|
|
26
|
+
bindings?: LoggerBindings,
|
|
25
27
|
) {
|
|
26
28
|
const instrumentation = new WorldStateInstrumentation(client);
|
|
27
|
-
const merkleTrees = await createWorldState(config,
|
|
29
|
+
const merkleTrees = await createWorldState(config, genesis, instrumentation, bindings);
|
|
28
30
|
return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, instrumentation);
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -40,8 +42,9 @@ export async function createWorldState(
|
|
|
40
42
|
| 'publicDataTreeMapSizeKb'
|
|
41
43
|
> &
|
|
42
44
|
Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKb' | 'l1Contracts'>,
|
|
43
|
-
|
|
45
|
+
genesis: GenesisData = EMPTY_GENESIS_DATA,
|
|
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;
|
|
@@ -63,13 +66,16 @@ export async function createWorldState(
|
|
|
63
66
|
config.l1Contracts.rollupAddress,
|
|
64
67
|
dataDirectory,
|
|
65
68
|
wsTreeMapSizes,
|
|
66
|
-
|
|
69
|
+
genesis,
|
|
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
|
+
genesis,
|
|
77
|
+
instrumentation,
|
|
78
|
+
bindings,
|
|
73
79
|
);
|
|
74
80
|
|
|
75
81
|
return merkleTrees;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
1
|
+
import { 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,
|
|
9
|
+
GENESIS_BLOCK_HEADER_HASH,
|
|
8
10
|
GENESIS_CHECKPOINT_HEADER_HASH,
|
|
11
|
+
type L2Block,
|
|
9
12
|
type L2BlockId,
|
|
10
|
-
type L2BlockNew,
|
|
11
13
|
type L2BlockSource,
|
|
12
14
|
L2BlockStream,
|
|
13
15
|
type L2BlockStreamEvent,
|
|
@@ -64,7 +66,7 @@ export class ServerWorldStateSynchronizer
|
|
|
64
66
|
private readonly log: Logger = createLogger('world_state'),
|
|
65
67
|
) {
|
|
66
68
|
this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
|
|
67
|
-
this.historyToKeep = config.
|
|
69
|
+
this.historyToKeep = config.worldStateCheckpointHistory < 1 ? undefined : config.worldStateCheckpointHistory;
|
|
68
70
|
this.log.info(
|
|
69
71
|
`Created world state synchroniser with block history of ${
|
|
70
72
|
this.historyToKeep === undefined ? 'infinity' : this.historyToKeep
|
|
@@ -101,11 +103,7 @@ export class ServerWorldStateSynchronizer
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
// Get the current latest block number
|
|
104
|
-
this.latestBlockNumberAtStart = BlockNumber(
|
|
105
|
-
await (this.config.worldStateProvenBlocksOnly
|
|
106
|
-
? this.l2BlockSource.getProvenBlockNumber()
|
|
107
|
-
: this.l2BlockSource.getBlockNumber()),
|
|
108
|
-
);
|
|
106
|
+
this.latestBlockNumberAtStart = BlockNumber(await this.l2BlockSource.getBlockNumber());
|
|
109
107
|
|
|
110
108
|
const blockToDownloadFrom = (await this.getLatestBlockNumber()) + 1;
|
|
111
109
|
|
|
@@ -129,7 +127,6 @@ export class ServerWorldStateSynchronizer
|
|
|
129
127
|
protected createBlockStream(): L2BlockStream {
|
|
130
128
|
const logger = createLogger('world-state:block_stream');
|
|
131
129
|
return new L2BlockStream(this.l2BlockSource, this, this, logger, {
|
|
132
|
-
proven: this.config.worldStateProvenBlocksOnly,
|
|
133
130
|
pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
|
|
134
131
|
batchSize: this.config.worldStateBlockRequestBatchSize,
|
|
135
132
|
ignoreCheckpoints: true,
|
|
@@ -182,13 +179,10 @@ export class ServerWorldStateSynchronizer
|
|
|
182
179
|
/**
|
|
183
180
|
* Forces an immediate sync.
|
|
184
181
|
* @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
|
|
185
|
-
* @param
|
|
182
|
+
* @param blockHash - If provided, verifies the block at targetBlockNumber matches this hash. On mismatch, triggers a resync (reorg detection).
|
|
186
183
|
* @returns A promise that resolves with the block number the world state was synced to
|
|
187
184
|
*/
|
|
188
|
-
public async syncImmediate(
|
|
189
|
-
targetBlockNumber?: BlockNumber,
|
|
190
|
-
skipThrowIfTargetNotReached?: boolean,
|
|
191
|
-
): Promise<BlockNumber> {
|
|
185
|
+
public async syncImmediate(targetBlockNumber?: BlockNumber, blockHash?: BlockHash): Promise<BlockNumber> {
|
|
192
186
|
if (this.currentState !== WorldStateRunningState.RUNNING) {
|
|
193
187
|
throw new Error(`World State is not running. Unable to perform sync.`);
|
|
194
188
|
}
|
|
@@ -200,7 +194,19 @@ export class ServerWorldStateSynchronizer
|
|
|
200
194
|
// If we have been given a block number to sync to and we have reached that number then return
|
|
201
195
|
const currentBlockNumber = await this.getLatestBlockNumber();
|
|
202
196
|
if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
|
|
203
|
-
|
|
197
|
+
if (blockHash === undefined) {
|
|
198
|
+
return currentBlockNumber;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// If a block hash was provided, verify we're on the expected fork
|
|
202
|
+
const currentHash = await this.getL2BlockHash(targetBlockNumber);
|
|
203
|
+
if (currentHash === blockHash.toString()) {
|
|
204
|
+
return currentBlockNumber;
|
|
205
|
+
}
|
|
206
|
+
// Hash mismatch: a reorg may have occurred, fall through to trigger sync
|
|
207
|
+
this.log.debug(
|
|
208
|
+
`World state block hash mismatch at ${targetBlockNumber} (expected ${blockHash}, got ${currentHash}). Triggering resync.`,
|
|
209
|
+
);
|
|
204
210
|
}
|
|
205
211
|
this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
|
|
206
212
|
|
|
@@ -218,7 +224,7 @@ export class ServerWorldStateSynchronizer
|
|
|
218
224
|
|
|
219
225
|
// If we have been given a block number to sync to and we have not reached that number then fail
|
|
220
226
|
const updatedBlockNumber = await this.getLatestBlockNumber();
|
|
221
|
-
if (
|
|
227
|
+
if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
|
|
222
228
|
throw new WorldStateSynchronizerError(
|
|
223
229
|
`Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
|
|
224
230
|
{
|
|
@@ -232,6 +238,24 @@ export class ServerWorldStateSynchronizer
|
|
|
232
238
|
);
|
|
233
239
|
}
|
|
234
240
|
|
|
241
|
+
// If a block hash was provided, verify we're on the expected fork after syncing, throw otherwise
|
|
242
|
+
if (blockHash !== undefined && targetBlockNumber !== undefined) {
|
|
243
|
+
const updatedHash = await this.getL2BlockHash(targetBlockNumber);
|
|
244
|
+
if (updatedHash !== blockHash.toString()) {
|
|
245
|
+
throw new WorldStateSynchronizerError(
|
|
246
|
+
`Block hash mismatch at block ${targetBlockNumber} (expected ${blockHash} but got ${updatedHash})`,
|
|
247
|
+
{
|
|
248
|
+
cause: {
|
|
249
|
+
reason: 'block_hash_mismatch',
|
|
250
|
+
targetBlockNumber,
|
|
251
|
+
expectedHash: blockHash.toString(),
|
|
252
|
+
actualHash: updatedHash,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
235
259
|
return updatedBlockNumber;
|
|
236
260
|
}
|
|
237
261
|
|
|
@@ -268,15 +292,19 @@ export class ServerWorldStateSynchronizer
|
|
|
268
292
|
proposed: latestBlockId,
|
|
269
293
|
checkpointed: {
|
|
270
294
|
block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
|
|
271
|
-
checkpoint: { number:
|
|
295
|
+
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
296
|
+
},
|
|
297
|
+
proposedCheckpoint: {
|
|
298
|
+
block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
|
|
299
|
+
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
272
300
|
},
|
|
273
301
|
finalized: {
|
|
274
302
|
block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash ?? '' },
|
|
275
|
-
checkpoint: { number:
|
|
303
|
+
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
276
304
|
},
|
|
277
305
|
proven: {
|
|
278
306
|
block: { number: provenBlockNumber, hash: provenBlockHash ?? '' },
|
|
279
|
-
checkpoint: { number:
|
|
307
|
+
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
280
308
|
},
|
|
281
309
|
};
|
|
282
310
|
}
|
|
@@ -304,8 +332,8 @@ export class ServerWorldStateSynchronizer
|
|
|
304
332
|
* @param l2Blocks - The L2 blocks to handle.
|
|
305
333
|
* @returns Whether the block handled was produced by this same node.
|
|
306
334
|
*/
|
|
307
|
-
private async handleL2Blocks(l2Blocks:
|
|
308
|
-
this.log.
|
|
335
|
+
private async handleL2Blocks(l2Blocks: L2Block[]) {
|
|
336
|
+
this.log.debug(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
|
|
309
337
|
|
|
310
338
|
// Fetch the L1->L2 messages for the first block in a checkpoint.
|
|
311
339
|
const messagesForBlocks = new Map<BlockNumber, Fr[]>();
|
|
@@ -345,11 +373,13 @@ export class ServerWorldStateSynchronizer
|
|
|
345
373
|
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
346
374
|
* @returns Whether the block handled was produced by this same node.
|
|
347
375
|
*/
|
|
348
|
-
private async handleL2Block(l2Block:
|
|
349
|
-
this.log.
|
|
376
|
+
private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
|
|
377
|
+
this.log.debug(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
|
|
350
378
|
blockNumber: l2Block.number,
|
|
351
379
|
blockHash: await l2Block.hash().then(h => h.toString()),
|
|
352
380
|
l1ToL2Messages: l1ToL2Messages.map(msg => msg.toString()),
|
|
381
|
+
blockHeader: l2Block.header.toInspect(),
|
|
382
|
+
blockStats: l2Block.getStats(),
|
|
353
383
|
});
|
|
354
384
|
const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages);
|
|
355
385
|
|
|
@@ -363,16 +393,59 @@ export class ServerWorldStateSynchronizer
|
|
|
363
393
|
|
|
364
394
|
private async handleChainFinalized(blockNumber: BlockNumber) {
|
|
365
395
|
this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
|
|
396
|
+
// If the finalized block number is older than the oldest available block in world state,
|
|
397
|
+
// skip entirely. The finalized block number can jump backwards (e.g. when the finalization
|
|
398
|
+
// heuristic changes) and try to read block data that has already been pruned. When this
|
|
399
|
+
// happens, there is nothing useful to do — the native world state is already finalized
|
|
400
|
+
// past this point and pruning has already happened.
|
|
401
|
+
const currentSummary = await this.merkleTreeDb.getStatusSummary();
|
|
402
|
+
if (blockNumber < currentSummary.oldestHistoricalBlock || blockNumber < 1) {
|
|
403
|
+
this.log.trace(
|
|
404
|
+
`Finalized block ${blockNumber} is older than the oldest available block ${currentSummary.oldestHistoricalBlock}. Skipping.`,
|
|
405
|
+
);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
366
408
|
const summary = await this.merkleTreeDb.setFinalized(blockNumber);
|
|
367
409
|
if (this.historyToKeep === undefined) {
|
|
368
410
|
return;
|
|
369
411
|
}
|
|
370
|
-
|
|
371
|
-
|
|
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`);
|
|
372
432
|
return;
|
|
373
433
|
}
|
|
374
|
-
|
|
375
|
-
|
|
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
|
+
);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
this.log.verbose(`Pruning historic blocks to ${newHistoricBlock.number}`);
|
|
448
|
+
const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock.number));
|
|
376
449
|
this.log.debug(`World state summary `, status.summary);
|
|
377
450
|
}
|
|
378
451
|
|
|
@@ -383,9 +456,11 @@ export class ServerWorldStateSynchronizer
|
|
|
383
456
|
}
|
|
384
457
|
|
|
385
458
|
private async handleChainPruned(blockNumber: BlockNumber) {
|
|
386
|
-
this.log.
|
|
459
|
+
this.log.info(`Chain pruned to block ${blockNumber}`);
|
|
387
460
|
const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
|
|
388
|
-
this.provenBlockNumber
|
|
461
|
+
if (this.provenBlockNumber !== undefined && this.provenBlockNumber > blockNumber) {
|
|
462
|
+
this.provenBlockNumber = undefined;
|
|
463
|
+
}
|
|
389
464
|
this.instrumentation.updateWorldStateMetrics(status);
|
|
390
465
|
}
|
|
391
466
|
|
package/src/test/utils.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { asyncMap } from '@aztec/foundation/async-map';
|
|
|
8
8
|
import { BlockNumber, type CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
|
|
9
9
|
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
10
10
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
11
|
-
import {
|
|
11
|
+
import { L2Block } from '@aztec/stdlib/block';
|
|
12
12
|
import type {
|
|
13
13
|
IndexedTreeId,
|
|
14
14
|
MerkleTreeReadOperations,
|
|
@@ -20,7 +20,7 @@ import { BlockHeader } from '@aztec/stdlib/tx';
|
|
|
20
20
|
|
|
21
21
|
import type { NativeWorldStateService } from '../native/native_world_state.js';
|
|
22
22
|
|
|
23
|
-
export async function updateBlockState(block:
|
|
23
|
+
export async function updateBlockState(block: L2Block, l1ToL2Messages: Fr[], fork: MerkleTreeWriteOperations) {
|
|
24
24
|
const insertData = async (
|
|
25
25
|
treeId: IndexedTreeId,
|
|
26
26
|
data: Buffer[][],
|
|
@@ -76,7 +76,7 @@ export async function mockBlock(
|
|
|
76
76
|
numL1ToL2Messages: number = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
|
|
77
77
|
isFirstBlockInCheckpoint: boolean = true,
|
|
78
78
|
) {
|
|
79
|
-
const block = await
|
|
79
|
+
const block = await L2Block.random(blockNum, {
|
|
80
80
|
indexWithinCheckpoint: isFirstBlockInCheckpoint ? IndexWithinCheckpoint(0) : IndexWithinCheckpoint(1),
|
|
81
81
|
txsPerBlock: size,
|
|
82
82
|
txOptions: { maxEffects },
|
|
@@ -92,7 +92,7 @@ export async function mockBlock(
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export async function mockEmptyBlock(blockNum: BlockNumber, fork: MerkleTreeWriteOperations) {
|
|
95
|
-
const l2Block =
|
|
95
|
+
const l2Block = L2Block.empty();
|
|
96
96
|
const l1ToL2Messages = Array(16).fill(0).map(Fr.zero);
|
|
97
97
|
|
|
98
98
|
l2Block.header.globalVariables.blockNumber = blockNum;
|
package/src/testing.ts
CHANGED
|
@@ -3,22 +3,19 @@ import { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
3
3
|
import { computeFeePayerBalanceLeafSlot } from '@aztec/protocol-contracts/fee-juice';
|
|
4
4
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
5
5
|
import { MerkleTreeId, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
6
|
+
import type { GenesisData } from '@aztec/stdlib/world-state';
|
|
6
7
|
|
|
7
8
|
import { NativeWorldStateService } from './native/index.js';
|
|
8
9
|
|
|
9
|
-
async function generateGenesisValues(
|
|
10
|
-
if (!prefilledPublicData.length) {
|
|
10
|
+
async function generateGenesisValues(genesis: GenesisData) {
|
|
11
|
+
if (!genesis.prefilledPublicData.length && genesis.genesisTimestamp === 0n) {
|
|
11
12
|
return {
|
|
12
13
|
genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT),
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
// Create a temporary world state to compute the genesis values.
|
|
17
|
-
const ws = await NativeWorldStateService.tmp(
|
|
18
|
-
undefined /* rollupAddress */,
|
|
19
|
-
true /* cleanupTmpDir */,
|
|
20
|
-
prefilledPublicData,
|
|
21
|
-
);
|
|
18
|
+
const ws = await NativeWorldStateService.tmp(undefined /* rollupAddress */, true /* cleanupTmpDir */, genesis);
|
|
22
19
|
const genesisArchiveRoot = new Fr((await ws.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
23
20
|
await ws.close();
|
|
24
21
|
|
|
@@ -33,6 +30,7 @@ export async function getGenesisValues(
|
|
|
33
30
|
initialAccounts: AztecAddress[],
|
|
34
31
|
initialAccountFeeJuice = defaultInitialAccountFeeJuice,
|
|
35
32
|
genesisPublicData: PublicDataTreeLeaf[] = [],
|
|
33
|
+
genesisTimestamp: bigint = 0n,
|
|
36
34
|
) {
|
|
37
35
|
// Top up the accounts with fee juice.
|
|
38
36
|
let prefilledPublicData = await Promise.all(
|
|
@@ -46,11 +44,12 @@ export async function getGenesisValues(
|
|
|
46
44
|
|
|
47
45
|
prefilledPublicData.sort((a, b) => (b.slot.lt(a.slot) ? 1 : -1));
|
|
48
46
|
|
|
49
|
-
const
|
|
47
|
+
const genesis: GenesisData = { prefilledPublicData, genesisTimestamp };
|
|
48
|
+
const { genesisArchiveRoot } = await generateGenesisValues(genesis);
|
|
50
49
|
|
|
51
50
|
return {
|
|
52
51
|
genesisArchiveRoot,
|
|
53
|
-
|
|
52
|
+
genesis,
|
|
54
53
|
fundingNeeded: BigInt(initialAccounts.length) * initialAccountFeeJuice.toBigInt(),
|
|
55
54
|
};
|
|
56
55
|
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX } from '@aztec/constants';
|
|
2
2
|
import type { BlockNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
-
import type {
|
|
5
|
-
import type { L2BlockNew } from '@aztec/stdlib/block';
|
|
4
|
+
import type { L2Block } from '@aztec/stdlib/block';
|
|
6
5
|
import type {
|
|
7
6
|
ForkMerkleTreeOperations,
|
|
8
7
|
MerkleTreeReadOperations,
|
|
9
8
|
ReadonlyWorldStateAccess,
|
|
10
9
|
} from '@aztec/stdlib/interfaces/server';
|
|
11
|
-
import type { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
12
10
|
|
|
13
11
|
import type { WorldStateStatusFull, WorldStateStatusSummary } from '../native/message.js';
|
|
14
12
|
|
|
@@ -31,21 +29,13 @@ export const INITIAL_NULLIFIER_TREE_SIZE = 2 * MAX_NULLIFIERS_PER_TX;
|
|
|
31
29
|
|
|
32
30
|
export const INITIAL_PUBLIC_DATA_TREE_SIZE = 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX;
|
|
33
31
|
|
|
34
|
-
export type TreeSnapshots = {
|
|
35
|
-
[MerkleTreeId.NULLIFIER_TREE]: IndexedTreeSnapshot;
|
|
36
|
-
[MerkleTreeId.NOTE_HASH_TREE]: TreeSnapshot<Fr>;
|
|
37
|
-
[MerkleTreeId.PUBLIC_DATA_TREE]: IndexedTreeSnapshot;
|
|
38
|
-
[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: TreeSnapshot<Fr>;
|
|
39
|
-
[MerkleTreeId.ARCHIVE]: TreeSnapshot<Fr>;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
32
|
export interface MerkleTreeAdminDatabase extends ForkMerkleTreeOperations, ReadonlyWorldStateAccess {
|
|
43
33
|
/**
|
|
44
34
|
* Handles a single L2 block (i.e. Inserts the new note hashes into the merkle tree).
|
|
45
35
|
* @param block - The L2 block to handle.
|
|
46
36
|
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
47
37
|
*/
|
|
48
|
-
handleL2BlockAndMessages(block:
|
|
38
|
+
handleL2BlockAndMessages(block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull>;
|
|
49
39
|
|
|
50
40
|
/**
|
|
51
41
|
* Gets a handle that allows reading the latest committed state
|