@aztec/world-state 0.65.0 → 0.65.2
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/native/merkle_trees_facade.d.ts +2 -1
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +21 -1
- package/dest/native/message.d.ts +28 -11
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +12 -11
- package/dest/native/native_world_state.d.ts +7 -3
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +32 -22
- package/dest/native/native_world_state_instance.d.ts +3 -1
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +17 -5
- package/dest/native/world_state_version.d.ts +12 -3
- package/dest/native/world_state_version.d.ts.map +1 -1
- package/dest/native/world_state_version.js +12 -20
- package/dest/synchronizer/config.d.ts +2 -0
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +7 -2
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +2 -0
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +28 -6
- package/dest/world-state-db/merkle_tree_operations_facade.d.ts +8 -1
- package/dest/world-state-db/merkle_tree_operations_facade.d.ts.map +1 -1
- package/dest/world-state-db/merkle_tree_operations_facade.js +10 -1
- package/package.json +10 -9
- package/src/native/merkle_trees_facade.ts +26 -0
- package/src/native/message.ts +20 -1
- package/src/native/native_world_state.ts +70 -36
- package/src/native/native_world_state_instance.ts +15 -3
- package/src/native/world_state_version.ts +12 -21
- package/src/synchronizer/config.ts +14 -1
- package/src/synchronizer/server_world_state_synchronizer.ts +32 -5
- package/src/world-state-db/merkle_tree_operations_facade.ts +14 -0
|
@@ -32,6 +32,7 @@ import { MerkleTreesFacade, MerkleTreesForkFacade, serializeLeaf } from './merkl
|
|
|
32
32
|
import {
|
|
33
33
|
WorldStateMessageType,
|
|
34
34
|
type WorldStateStatusFull,
|
|
35
|
+
type WorldStateStatusSummary,
|
|
35
36
|
blockStateReference,
|
|
36
37
|
sanitiseFullStatus,
|
|
37
38
|
sanitiseSummary,
|
|
@@ -52,6 +53,8 @@ export const WORLD_STATE_DB_VERSION = 1; // The initial version
|
|
|
52
53
|
|
|
53
54
|
export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
54
55
|
protected initialHeader: Header | undefined;
|
|
56
|
+
// This is read heavily and only changes when data is persisted, so we cache it
|
|
57
|
+
private cachedStatusSummary: WorldStateStatusSummary | undefined;
|
|
55
58
|
|
|
56
59
|
protected constructor(
|
|
57
60
|
protected readonly instance: NativeWorldState,
|
|
@@ -175,29 +178,29 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
175
178
|
.flatMap(txEffect => padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX))
|
|
176
179
|
.map(nullifier => new NullifierLeaf(nullifier));
|
|
177
180
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
return new PublicDataTreeLeaf(write.leafSlot, write.value);
|
|
187
|
-
}),
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const response = await this.instance.call(WorldStateMessageType.SYNC_BLOCK, {
|
|
192
|
-
blockNumber: l2Block.number,
|
|
193
|
-
blockHeaderHash: l2Block.header.hash(),
|
|
194
|
-
paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
|
|
195
|
-
paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
|
|
196
|
-
paddedNullifiers: paddedNullifiers.map(serializeLeaf),
|
|
197
|
-
batchesOfPublicDataWrites: batchesOfPublicDataWrites.map(batch => batch.map(serializeLeaf)),
|
|
198
|
-
blockStateRef: blockStateReference(l2Block.header.state),
|
|
181
|
+
const publicDataWrites: PublicDataTreeLeaf[] = paddedTxEffects.flatMap(txEffect => {
|
|
182
|
+
return txEffect.publicDataWrites.map(write => {
|
|
183
|
+
if (write.isEmpty()) {
|
|
184
|
+
throw new Error('Public data write must not be empty when syncing');
|
|
185
|
+
}
|
|
186
|
+
return new PublicDataTreeLeaf(write.leafSlot, write.value);
|
|
187
|
+
});
|
|
199
188
|
});
|
|
200
|
-
|
|
189
|
+
|
|
190
|
+
return await this.instance.call(
|
|
191
|
+
WorldStateMessageType.SYNC_BLOCK,
|
|
192
|
+
{
|
|
193
|
+
blockNumber: l2Block.number,
|
|
194
|
+
blockHeaderHash: l2Block.header.hash(),
|
|
195
|
+
paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
|
|
196
|
+
paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
|
|
197
|
+
paddedNullifiers: paddedNullifiers.map(serializeLeaf),
|
|
198
|
+
publicDataWrites: publicDataWrites.map(serializeLeaf),
|
|
199
|
+
blockStateRef: blockStateReference(l2Block.header.state),
|
|
200
|
+
},
|
|
201
|
+
this.sanitiseAndCacheSummaryFromFull.bind(this),
|
|
202
|
+
this.deleteCachedSummary.bind(this),
|
|
203
|
+
);
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
public async close(): Promise<void> {
|
|
@@ -210,16 +213,37 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
210
213
|
return Header.empty({ state });
|
|
211
214
|
}
|
|
212
215
|
|
|
216
|
+
private sanitiseAndCacheSummaryFromFull(response: WorldStateStatusFull) {
|
|
217
|
+
const sanitised = sanitiseFullStatus(response);
|
|
218
|
+
this.cachedStatusSummary = { ...sanitised.summary };
|
|
219
|
+
return sanitised;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private sanitiseAndCacheSummary(response: WorldStateStatusSummary) {
|
|
223
|
+
const sanitised = sanitiseSummary(response);
|
|
224
|
+
this.cachedStatusSummary = { ...sanitised };
|
|
225
|
+
return sanitised;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private deleteCachedSummary(_: string) {
|
|
229
|
+
this.cachedStatusSummary = undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
213
232
|
/**
|
|
214
233
|
* Advances the finalised block number to be the number provided
|
|
215
234
|
* @param toBlockNumber The block number that is now the tip of the finalised chain
|
|
216
235
|
* @returns The new WorldStateStatus
|
|
217
236
|
*/
|
|
218
237
|
public async setFinalised(toBlockNumber: bigint) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
238
|
+
await this.instance.call(
|
|
239
|
+
WorldStateMessageType.FINALISE_BLOCKS,
|
|
240
|
+
{
|
|
241
|
+
toBlockNumber,
|
|
242
|
+
},
|
|
243
|
+
this.sanitiseAndCacheSummary.bind(this),
|
|
244
|
+
this.deleteCachedSummary.bind(this),
|
|
245
|
+
);
|
|
246
|
+
return this.getStatusSummary();
|
|
223
247
|
}
|
|
224
248
|
|
|
225
249
|
/**
|
|
@@ -228,10 +252,14 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
228
252
|
* @returns The new WorldStateStatus
|
|
229
253
|
*/
|
|
230
254
|
public async removeHistoricalBlocks(toBlockNumber: bigint) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
255
|
+
return await this.instance.call(
|
|
256
|
+
WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
|
|
257
|
+
{
|
|
258
|
+
toBlockNumber,
|
|
259
|
+
},
|
|
260
|
+
this.sanitiseAndCacheSummaryFromFull.bind(this),
|
|
261
|
+
this.deleteCachedSummary.bind(this),
|
|
262
|
+
);
|
|
235
263
|
}
|
|
236
264
|
|
|
237
265
|
/**
|
|
@@ -240,15 +268,21 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
|
240
268
|
* @returns The new WorldStateStatus
|
|
241
269
|
*/
|
|
242
270
|
public async unwindBlocks(toBlockNumber: bigint) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
271
|
+
return await this.instance.call(
|
|
272
|
+
WorldStateMessageType.UNWIND_BLOCKS,
|
|
273
|
+
{
|
|
274
|
+
toBlockNumber,
|
|
275
|
+
},
|
|
276
|
+
this.sanitiseAndCacheSummaryFromFull.bind(this),
|
|
277
|
+
this.deleteCachedSummary.bind(this),
|
|
278
|
+
);
|
|
247
279
|
}
|
|
248
280
|
|
|
249
281
|
public async getStatusSummary() {
|
|
250
|
-
|
|
251
|
-
|
|
282
|
+
if (this.cachedStatusSummary !== undefined) {
|
|
283
|
+
return { ...this.cachedStatusSummary };
|
|
284
|
+
}
|
|
285
|
+
return await this.instance.call(WorldStateMessageType.GET_STATUS, void 0, this.sanitiseAndCacheSummary.bind(this));
|
|
252
286
|
}
|
|
253
287
|
|
|
254
288
|
updateLeaf<ID extends IndexedTreeId>(
|
|
@@ -108,16 +108,28 @@ export class NativeWorldState implements NativeWorldStateInstance {
|
|
|
108
108
|
* Sends a message to the native instance and returns the response.
|
|
109
109
|
* @param messageType - The type of message to send
|
|
110
110
|
* @param body - The message body
|
|
111
|
+
* @param responseHandler - A callback accepting the response, executed on the job queue
|
|
112
|
+
* @param errorHandler - A callback called on request error, executed on the job queue
|
|
111
113
|
* @returns The response to the message
|
|
112
114
|
*/
|
|
113
115
|
public call<T extends WorldStateMessageType>(
|
|
114
116
|
messageType: T,
|
|
115
117
|
body: WorldStateRequest[T],
|
|
118
|
+
// allows for the pre-processing of responses on the job queue before being passed back
|
|
119
|
+
responseHandler = (response: WorldStateResponse[T]): WorldStateResponse[T] => response,
|
|
120
|
+
errorHandler = (_: string) => {},
|
|
116
121
|
): Promise<WorldStateResponse[T]> {
|
|
117
|
-
return this.queue.put(() => {
|
|
122
|
+
return this.queue.put(async () => {
|
|
118
123
|
assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the native instance');
|
|
119
124
|
assert.equal(this.open, true, 'Native instance is closed');
|
|
120
|
-
|
|
125
|
+
let response: WorldStateResponse[T];
|
|
126
|
+
try {
|
|
127
|
+
response = await this._sendMessage(messageType, body);
|
|
128
|
+
} catch (error: any) {
|
|
129
|
+
errorHandler(error.message);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
return responseHandler(response);
|
|
121
133
|
});
|
|
122
134
|
}
|
|
123
135
|
|
|
@@ -188,7 +200,7 @@ export class NativeWorldState implements NativeWorldStateInstance {
|
|
|
188
200
|
data['notesCount'] = body.paddedNoteHashes.length;
|
|
189
201
|
data['nullifiersCount'] = body.paddedNullifiers.length;
|
|
190
202
|
data['l1ToL2MessagesCount'] = body.paddedL1ToL2Messages.length;
|
|
191
|
-
data['publicDataWritesCount'] = body.
|
|
203
|
+
data['publicDataWritesCount'] = body.publicDataWrites.length;
|
|
192
204
|
}
|
|
193
205
|
|
|
194
206
|
this.log.debug(`Calling messageId=${messageId} ${WorldStateMessageType[messageType]} with ${fmtLogData(data)}`);
|
|
@@ -1,38 +1,29 @@
|
|
|
1
1
|
import { EthAddress } from '@aztec/circuits.js';
|
|
2
|
+
import { jsonParseWithSchema, jsonStringify } from '@aztec/foundation/json-rpc';
|
|
2
3
|
|
|
3
4
|
import { readFile, writeFile } from 'fs/promises';
|
|
5
|
+
import { z } from 'zod';
|
|
4
6
|
|
|
5
7
|
export class WorldStateVersion {
|
|
6
|
-
constructor(readonly version: number, readonly rollupAddress: EthAddress) {}
|
|
8
|
+
constructor(public readonly version: number, public readonly rollupAddress: EthAddress) {}
|
|
7
9
|
|
|
8
10
|
static async readVersion(filename: string) {
|
|
9
11
|
const versionData = await readFile(filename, 'utf-8').catch(() => undefined);
|
|
10
|
-
|
|
11
|
-
return undefined;
|
|
12
|
-
}
|
|
13
|
-
const versionJSON = JSON.parse(versionData);
|
|
14
|
-
if (versionJSON.version === undefined || versionJSON.rollupAddress === undefined) {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
return WorldStateVersion.fromJSON(versionJSON);
|
|
12
|
+
return versionData === undefined ? undefined : jsonParseWithSchema(versionData, WorldStateVersion.schema);
|
|
18
13
|
}
|
|
19
14
|
|
|
20
15
|
public async writeVersionFile(filename: string) {
|
|
21
|
-
const data =
|
|
16
|
+
const data = jsonStringify(this);
|
|
22
17
|
await writeFile(filename, data, 'utf-8');
|
|
23
18
|
}
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
static fromJSON(obj: any): WorldStateVersion {
|
|
33
|
-
const version = obj.version;
|
|
34
|
-
const rollupAddress = EthAddress.fromString(obj.rollupAddress);
|
|
35
|
-
return new WorldStateVersion(version, rollupAddress);
|
|
20
|
+
static get schema() {
|
|
21
|
+
return z
|
|
22
|
+
.object({
|
|
23
|
+
version: z.number(),
|
|
24
|
+
rollupAddress: EthAddress.schema,
|
|
25
|
+
})
|
|
26
|
+
.transform(({ version, rollupAddress }) => new WorldStateVersion(version, rollupAddress));
|
|
36
27
|
}
|
|
37
28
|
|
|
38
29
|
static empty() {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ConfigMappingsType,
|
|
3
|
+
booleanConfigHelper,
|
|
4
|
+
getConfigFromMappings,
|
|
5
|
+
numberConfigHelper,
|
|
6
|
+
} from '@aztec/foundation/config';
|
|
2
7
|
|
|
3
8
|
/** World State synchronizer configuration values. */
|
|
4
9
|
export interface WorldStateConfig {
|
|
@@ -16,6 +21,9 @@ export interface WorldStateConfig {
|
|
|
16
21
|
|
|
17
22
|
/** Optional directory for the world state DB, if unspecified will default to the general data directory */
|
|
18
23
|
worldStateDataDirectory?: string;
|
|
24
|
+
|
|
25
|
+
/** The number of historic blocks to maintain */
|
|
26
|
+
worldStateBlockHistory: number;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
@@ -44,6 +52,11 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
|
44
52
|
env: 'WS_DATA_DIRECTORY',
|
|
45
53
|
description: 'Optional directory for the world state database',
|
|
46
54
|
},
|
|
55
|
+
worldStateBlockHistory: {
|
|
56
|
+
env: 'WS_NUM_HISTORIC_BLOCKS',
|
|
57
|
+
description: 'The number of historic blocks to maintain. Values less than 1 mean all history is maintained',
|
|
58
|
+
...numberConfigHelper(64),
|
|
59
|
+
},
|
|
47
60
|
};
|
|
48
61
|
|
|
49
62
|
/**
|
|
@@ -41,7 +41,9 @@ export class ServerWorldStateSynchronizer
|
|
|
41
41
|
private readonly merkleTreeCommitted: MerkleTreeReadOperations;
|
|
42
42
|
|
|
43
43
|
private latestBlockNumberAtStart = 0;
|
|
44
|
+
private historyToKeep: number | undefined;
|
|
44
45
|
private currentState: WorldStateRunningState = WorldStateRunningState.IDLE;
|
|
46
|
+
private latestBlockHashQuery: { blockNumber: number; hash: string | undefined } | undefined = undefined;
|
|
45
47
|
|
|
46
48
|
private syncPromise = promiseWithResolvers<void>();
|
|
47
49
|
protected blockStream: L2BlockStream | undefined;
|
|
@@ -56,6 +58,12 @@ export class ServerWorldStateSynchronizer
|
|
|
56
58
|
) {
|
|
57
59
|
this.instrumentation = new WorldStateInstrumentation(telemetry);
|
|
58
60
|
this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
|
|
61
|
+
this.historyToKeep = config.worldStateBlockHistory < 1 ? undefined : config.worldStateBlockHistory;
|
|
62
|
+
this.log.info(
|
|
63
|
+
`Created world state synchroniser with block history of ${
|
|
64
|
+
this.historyToKeep === undefined ? 'infinity' : this.historyToKeep
|
|
65
|
+
}`,
|
|
66
|
+
);
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
public getCommitted(): MerkleTreeReadOperations {
|
|
@@ -160,10 +168,19 @@ export class ServerWorldStateSynchronizer
|
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
/** Returns the L2 block hash for a given number. Used by the L2BlockStream for detecting reorgs. */
|
|
163
|
-
public getL2BlockHash(number: number): Promise<string | undefined> {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
171
|
+
public async getL2BlockHash(number: number): Promise<string | undefined> {
|
|
172
|
+
if (number === 0) {
|
|
173
|
+
return Promise.resolve(this.merkleTreeCommitted.getInitialHeader().hash().toString());
|
|
174
|
+
}
|
|
175
|
+
if (this.latestBlockHashQuery?.hash === undefined || number !== this.latestBlockHashQuery.blockNumber) {
|
|
176
|
+
this.latestBlockHashQuery = {
|
|
177
|
+
hash: await this.merkleTreeCommitted
|
|
178
|
+
.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number))
|
|
179
|
+
.then(leaf => leaf?.toString()),
|
|
180
|
+
blockNumber: number,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return this.latestBlockHashQuery.hash;
|
|
167
184
|
}
|
|
168
185
|
|
|
169
186
|
/** Returns the latest L2 block number for each tip of the chain (latest, proven, finalized). */
|
|
@@ -256,7 +273,16 @@ export class ServerWorldStateSynchronizer
|
|
|
256
273
|
|
|
257
274
|
private async handleChainFinalized(blockNumber: number) {
|
|
258
275
|
this.log.verbose(`Chain finalized at block ${blockNumber}`);
|
|
259
|
-
await this.merkleTreeDb.setFinalised(BigInt(blockNumber));
|
|
276
|
+
const summary = await this.merkleTreeDb.setFinalised(BigInt(blockNumber));
|
|
277
|
+
if (this.historyToKeep === undefined) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const newHistoricBlock = summary.finalisedBlockNumber - BigInt(this.historyToKeep) + 1n;
|
|
281
|
+
if (newHistoricBlock <= 1) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
this.log.verbose(`Pruning historic blocks to ${newHistoricBlock}`);
|
|
285
|
+
await this.merkleTreeDb.removeHistoricalBlocks(newHistoricBlock);
|
|
260
286
|
}
|
|
261
287
|
|
|
262
288
|
private handleChainProven(blockNumber: number) {
|
|
@@ -267,6 +293,7 @@ export class ServerWorldStateSynchronizer
|
|
|
267
293
|
private async handleChainPruned(blockNumber: number) {
|
|
268
294
|
this.log.info(`Chain pruned to block ${blockNumber}`);
|
|
269
295
|
const status = await this.merkleTreeDb.unwindBlocks(BigInt(blockNumber));
|
|
296
|
+
this.latestBlockHashQuery = undefined;
|
|
270
297
|
this.instrumentation.updateWorldStateMetrics(status);
|
|
271
298
|
}
|
|
272
299
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type IndexedTreeId,
|
|
4
4
|
type MerkleTreeLeafType,
|
|
5
5
|
type MerkleTreeWriteOperations,
|
|
6
|
+
type SequentialInsertionResult,
|
|
6
7
|
type TreeInfo,
|
|
7
8
|
} from '@aztec/circuit-types/interfaces';
|
|
8
9
|
import { type Header, type StateReference } from '@aztec/circuits.js';
|
|
@@ -167,6 +168,19 @@ export class MerkleTreeReadOperationsFacade implements MerkleTreeWriteOperations
|
|
|
167
168
|
return this.trees.batchInsert(treeId, leaves, subtreeHeight);
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Sequentially inserts multiple leaves into the tree.
|
|
173
|
+
* @param treeId - The ID of the tree.
|
|
174
|
+
* @param leaves - Leaves to insert into the tree.
|
|
175
|
+
* @returns Witnesses for the operations performed.
|
|
176
|
+
*/
|
|
177
|
+
public sequentialInsert<TreeHeight extends number>(
|
|
178
|
+
_treeId: IndexedTreeId,
|
|
179
|
+
_leaves: Buffer[],
|
|
180
|
+
): Promise<SequentialInsertionResult<TreeHeight>> {
|
|
181
|
+
throw new Error('Method not implemented in legacy merkle tree');
|
|
182
|
+
}
|
|
183
|
+
|
|
170
184
|
close(): Promise<void> {
|
|
171
185
|
return Promise.resolve();
|
|
172
186
|
}
|