@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.
Files changed (33) hide show
  1. package/dest/native/merkle_trees_facade.d.ts +2 -1
  2. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  3. package/dest/native/merkle_trees_facade.js +21 -1
  4. package/dest/native/message.d.ts +28 -11
  5. package/dest/native/message.d.ts.map +1 -1
  6. package/dest/native/message.js +12 -11
  7. package/dest/native/native_world_state.d.ts +7 -3
  8. package/dest/native/native_world_state.d.ts.map +1 -1
  9. package/dest/native/native_world_state.js +32 -22
  10. package/dest/native/native_world_state_instance.d.ts +3 -1
  11. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  12. package/dest/native/native_world_state_instance.js +17 -5
  13. package/dest/native/world_state_version.d.ts +12 -3
  14. package/dest/native/world_state_version.d.ts.map +1 -1
  15. package/dest/native/world_state_version.js +12 -20
  16. package/dest/synchronizer/config.d.ts +2 -0
  17. package/dest/synchronizer/config.d.ts.map +1 -1
  18. package/dest/synchronizer/config.js +7 -2
  19. package/dest/synchronizer/server_world_state_synchronizer.d.ts +2 -0
  20. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  21. package/dest/synchronizer/server_world_state_synchronizer.js +28 -6
  22. package/dest/world-state-db/merkle_tree_operations_facade.d.ts +8 -1
  23. package/dest/world-state-db/merkle_tree_operations_facade.d.ts.map +1 -1
  24. package/dest/world-state-db/merkle_tree_operations_facade.js +10 -1
  25. package/package.json +10 -9
  26. package/src/native/merkle_trees_facade.ts +26 -0
  27. package/src/native/message.ts +20 -1
  28. package/src/native/native_world_state.ts +70 -36
  29. package/src/native/native_world_state_instance.ts +15 -3
  30. package/src/native/world_state_version.ts +12 -21
  31. package/src/synchronizer/config.ts +14 -1
  32. package/src/synchronizer/server_world_state_synchronizer.ts +32 -5
  33. 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
- // We insert the public data tree leaves with one batch per tx to avoid updating the same key twice
179
- const batchesOfPublicDataWrites: PublicDataTreeLeaf[][] = [];
180
- for (const txEffect of paddedTxEffects) {
181
- batchesOfPublicDataWrites.push(
182
- 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
- }),
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
- return sanitiseFullStatus(response);
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
- const response = await this.instance.call(WorldStateMessageType.FINALISE_BLOCKS, {
220
- toBlockNumber,
221
- });
222
- return sanitiseSummary(response);
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
- const response = await this.instance.call(WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS, {
232
- toBlockNumber,
233
- });
234
- return sanitiseFullStatus(response);
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
- const response = await this.instance.call(WorldStateMessageType.UNWIND_BLOCKS, {
244
- toBlockNumber,
245
- });
246
- return sanitiseFullStatus(response);
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
- const response = await this.instance.call(WorldStateMessageType.GET_STATUS, void 0);
251
- return sanitiseSummary(response);
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
- return this._sendMessage(messageType, body);
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.batchesOfPublicDataWrites.reduce((acc, batch) => acc + batch.length, 0);
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
- if (versionData === undefined) {
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 = JSON.stringify(this.toJSON());
16
+ const data = jsonStringify(this);
22
17
  await writeFile(filename, data, 'utf-8');
23
18
  }
24
19
 
25
- toJSON() {
26
- return {
27
- version: this.version,
28
- rollupAddress: this.rollupAddress.toChecksumString(),
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 { type ConfigMappingsType, booleanConfigHelper, getConfigFromMappings } from '@aztec/foundation/config';
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
- return number === 0
165
- ? Promise.resolve(this.merkleTreeCommitted.getInitialHeader().hash().toString())
166
- : this.merkleTreeCommitted.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number)).then(leaf => leaf?.toString());
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
  }