@aztec/world-state 0.0.0-test.0

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 (74) hide show
  1. package/README.md +40 -0
  2. package/dest/index.d.ts +5 -0
  3. package/dest/index.d.ts.map +1 -0
  4. package/dest/index.js +4 -0
  5. package/dest/instrumentation/instrumentation.d.ts +22 -0
  6. package/dest/instrumentation/instrumentation.d.ts.map +1 -0
  7. package/dest/instrumentation/instrumentation.js +117 -0
  8. package/dest/native/fork_checkpoint.d.ts +10 -0
  9. package/dest/native/fork_checkpoint.d.ts.map +1 -0
  10. package/dest/native/fork_checkpoint.js +26 -0
  11. package/dest/native/index.d.ts +3 -0
  12. package/dest/native/index.d.ts.map +1 -0
  13. package/dest/native/index.js +2 -0
  14. package/dest/native/merkle_trees_facade.d.ts +42 -0
  15. package/dest/native/merkle_trees_facade.d.ts.map +1 -0
  16. package/dest/native/merkle_trees_facade.js +240 -0
  17. package/dest/native/message.d.ts +331 -0
  18. package/dest/native/message.d.ts.map +1 -0
  19. package/dest/native/message.js +192 -0
  20. package/dest/native/native_world_state.d.ts +57 -0
  21. package/dest/native/native_world_state.d.ts.map +1 -0
  22. package/dest/native/native_world_state.js +229 -0
  23. package/dest/native/native_world_state_instance.d.ts +33 -0
  24. package/dest/native/native_world_state_instance.d.ts.map +1 -0
  25. package/dest/native/native_world_state_instance.js +164 -0
  26. package/dest/native/world_state_ops_queue.d.ts +19 -0
  27. package/dest/native/world_state_ops_queue.d.ts.map +1 -0
  28. package/dest/native/world_state_ops_queue.js +146 -0
  29. package/dest/synchronizer/config.d.ts +23 -0
  30. package/dest/synchronizer/config.d.ts.map +1 -0
  31. package/dest/synchronizer/config.js +39 -0
  32. package/dest/synchronizer/factory.d.ts +12 -0
  33. package/dest/synchronizer/factory.d.ts.map +1 -0
  34. package/dest/synchronizer/factory.js +24 -0
  35. package/dest/synchronizer/index.d.ts +3 -0
  36. package/dest/synchronizer/index.d.ts.map +1 -0
  37. package/dest/synchronizer/index.js +2 -0
  38. package/dest/synchronizer/server_world_state_synchronizer.d.ts +79 -0
  39. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -0
  40. package/dest/synchronizer/server_world_state_synchronizer.js +277 -0
  41. package/dest/test/index.d.ts +2 -0
  42. package/dest/test/index.d.ts.map +1 -0
  43. package/dest/test/index.js +1 -0
  44. package/dest/test/utils.d.ts +19 -0
  45. package/dest/test/utils.d.ts.map +1 -0
  46. package/dest/test/utils.js +99 -0
  47. package/dest/testing.d.ts +10 -0
  48. package/dest/testing.d.ts.map +1 -0
  49. package/dest/testing.js +37 -0
  50. package/dest/world-state-db/index.d.ts +3 -0
  51. package/dest/world-state-db/index.d.ts.map +1 -0
  52. package/dest/world-state-db/index.js +1 -0
  53. package/dest/world-state-db/merkle_tree_db.d.ts +68 -0
  54. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -0
  55. package/dest/world-state-db/merkle_tree_db.js +17 -0
  56. package/package.json +98 -0
  57. package/src/index.ts +4 -0
  58. package/src/instrumentation/instrumentation.ts +174 -0
  59. package/src/native/fork_checkpoint.ts +30 -0
  60. package/src/native/index.ts +2 -0
  61. package/src/native/merkle_trees_facade.ts +331 -0
  62. package/src/native/message.ts +541 -0
  63. package/src/native/native_world_state.ts +317 -0
  64. package/src/native/native_world_state_instance.ts +238 -0
  65. package/src/native/world_state_ops_queue.ts +190 -0
  66. package/src/synchronizer/config.ts +68 -0
  67. package/src/synchronizer/factory.ts +53 -0
  68. package/src/synchronizer/index.ts +2 -0
  69. package/src/synchronizer/server_world_state_synchronizer.ts +344 -0
  70. package/src/test/index.ts +1 -0
  71. package/src/test/utils.ts +153 -0
  72. package/src/testing.ts +60 -0
  73. package/src/world-state-db/index.ts +3 -0
  74. package/src/world-state-db/merkle_tree_db.ts +79 -0
@@ -0,0 +1,317 @@
1
+ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
2
+ import { padArrayEnd } from '@aztec/foundation/collection';
3
+ import { EthAddress } from '@aztec/foundation/eth-address';
4
+ import { Fr } from '@aztec/foundation/fields';
5
+ import { createLogger } from '@aztec/foundation/log';
6
+ import type { L2Block } from '@aztec/stdlib/block';
7
+ import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
8
+ import type {
9
+ IndexedTreeId,
10
+ MerkleTreeReadOperations,
11
+ MerkleTreeWriteOperations,
12
+ } from '@aztec/stdlib/interfaces/server';
13
+ import { MerkleTreeId, NullifierLeaf, type NullifierLeafPreimage, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
14
+ import { BlockHeader, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
15
+ import { getTelemetryClient } from '@aztec/telemetry-client';
16
+
17
+ import assert from 'assert/strict';
18
+ import { mkdtemp, rm } from 'fs/promises';
19
+ import { tmpdir } from 'os';
20
+ import { join } from 'path';
21
+
22
+ import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
23
+ import type { MerkleTreeAdminDatabase as MerkleTreeDatabase } from '../world-state-db/merkle_tree_db.js';
24
+ import { MerkleTreesFacade, MerkleTreesForkFacade, serializeLeaf } from './merkle_trees_facade.js';
25
+ import {
26
+ WorldStateMessageType,
27
+ type WorldStateStatusFull,
28
+ type WorldStateStatusSummary,
29
+ blockStateReference,
30
+ sanitiseFullStatus,
31
+ sanitiseSummary,
32
+ treeStateReferenceToSnapshot,
33
+ worldStateRevision,
34
+ } from './message.js';
35
+ import { NativeWorldState } from './native_world_state_instance.js';
36
+
37
+ // The current version of the world state database schema
38
+ // Increment this when making incompatible changes to the database schema
39
+ export const WORLD_STATE_DB_VERSION = 1; // The initial version
40
+
41
+ export class NativeWorldStateService implements MerkleTreeDatabase {
42
+ protected initialHeader: BlockHeader | undefined;
43
+ // This is read heavily and only changes when data is persisted, so we cache it
44
+ private cachedStatusSummary: WorldStateStatusSummary | undefined;
45
+
46
+ protected constructor(
47
+ protected readonly instance: NativeWorldState,
48
+ protected readonly worldStateInstrumentation: WorldStateInstrumentation,
49
+ protected readonly log = createLogger('world-state:database'),
50
+ private readonly cleanup = () => Promise.resolve(),
51
+ ) {}
52
+
53
+ static async new(
54
+ rollupAddress: EthAddress,
55
+ dataDir: string,
56
+ dbMapSizeKb: number,
57
+ prefilledPublicData: PublicDataTreeLeaf[] = [],
58
+ instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
59
+ log = createLogger('world-state:database'),
60
+ cleanup = () => Promise.resolve(),
61
+ ): Promise<NativeWorldStateService> {
62
+ const worldStateDirectory = join(dataDir, 'world_state');
63
+ // Create a version manager to handle versioning
64
+ const versionManager = new DatabaseVersionManager(
65
+ WORLD_STATE_DB_VERSION,
66
+ rollupAddress,
67
+ worldStateDirectory,
68
+ (dir: string) => {
69
+ return Promise.resolve(new NativeWorldState(dir, dbMapSizeKb, prefilledPublicData, instrumentation));
70
+ },
71
+ );
72
+
73
+ const [instance] = await versionManager.open();
74
+ const worldState = new this(instance, instrumentation, log, cleanup);
75
+ try {
76
+ await worldState.init();
77
+ } catch (e) {
78
+ log.error(`Error initialising world state: ${e}`);
79
+ throw e;
80
+ }
81
+
82
+ return worldState;
83
+ }
84
+
85
+ static async tmp(
86
+ rollupAddress = EthAddress.ZERO,
87
+ cleanupTmpDir = true,
88
+ prefilledPublicData: PublicDataTreeLeaf[] = [],
89
+ instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
90
+ ): Promise<NativeWorldStateService> {
91
+ const log = createLogger('world-state:database');
92
+ const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
93
+ const dbMapSizeKb = 10 * 1024 * 1024;
94
+ log.debug(`Created temporary world state database at: ${dataDir} with size: ${dbMapSizeKb}`);
95
+
96
+ // pass a cleanup callback because process.on('beforeExit', cleanup) does not work under Jest
97
+ const cleanup = async () => {
98
+ if (cleanupTmpDir) {
99
+ await rm(dataDir, { recursive: true, force: true, maxRetries: 3 });
100
+ log.debug(`Deleted temporary world state database: ${dataDir}`);
101
+ } else {
102
+ log.debug(`Leaving temporary world state database: ${dataDir}`);
103
+ }
104
+ };
105
+
106
+ return this.new(rollupAddress, dataDir, dbMapSizeKb, prefilledPublicData, instrumentation, log, cleanup);
107
+ }
108
+
109
+ protected async init() {
110
+ const status = await this.getStatusSummary();
111
+ if (!status.treesAreSynched) {
112
+ throw new Error('World state trees are out of sync, please delete your data directory and re-sync');
113
+ }
114
+ this.initialHeader = await this.buildInitialHeader();
115
+ const committed = this.getCommitted();
116
+
117
+ // validate the initial state
118
+ const archive = await committed.getTreeInfo(MerkleTreeId.ARCHIVE);
119
+ if (archive.size === 0n) {
120
+ throw new Error("Archive tree can't be empty");
121
+ }
122
+
123
+ // the initial header _must_ be the first element in the archive tree
124
+ // if this assertion fails, check that the hashing done in Header in yarn-project matches the initial header hash done in world_state.cpp
125
+ const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [await this.initialHeader.hash()]);
126
+ const initialHeaderIndex = indices[0];
127
+ assert.strictEqual(initialHeaderIndex, 0n, 'Invalid initial archive state');
128
+ }
129
+
130
+ public getCommitted(): MerkleTreeReadOperations {
131
+ return new MerkleTreesFacade(this.instance, this.initialHeader!, worldStateRevision(false, 0, 0));
132
+ }
133
+
134
+ public getSnapshot(blockNumber: number): MerkleTreeReadOperations {
135
+ return new MerkleTreesFacade(this.instance, this.initialHeader!, worldStateRevision(false, 0, blockNumber));
136
+ }
137
+
138
+ public async fork(blockNumber?: number): Promise<MerkleTreeWriteOperations> {
139
+ const resp = await this.instance.call(WorldStateMessageType.CREATE_FORK, {
140
+ latest: blockNumber === undefined,
141
+ blockNumber: blockNumber ?? 0,
142
+ canonical: true,
143
+ });
144
+ return new MerkleTreesForkFacade(this.instance, this.initialHeader!, worldStateRevision(true, resp.forkId, 0));
145
+ }
146
+
147
+ public getInitialHeader(): BlockHeader {
148
+ return this.initialHeader!;
149
+ }
150
+
151
+ public async handleL2BlockAndMessages(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
152
+ // We have to pad both the values within tx effects because that's how the trees are built by circuits.
153
+ const paddedNoteHashes = l2Block.body.txEffects.flatMap(txEffect =>
154
+ padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
155
+ );
156
+ const paddedL1ToL2Messages = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
157
+
158
+ const paddedNullifiers = l2Block.body.txEffects
159
+ .flatMap(txEffect => padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX))
160
+ .map(nullifier => new NullifierLeaf(nullifier));
161
+
162
+ const publicDataWrites: PublicDataTreeLeaf[] = l2Block.body.txEffects.flatMap(txEffect => {
163
+ return txEffect.publicDataWrites.map(write => {
164
+ if (write.isEmpty()) {
165
+ throw new Error('Public data write must not be empty when syncing');
166
+ }
167
+ return new PublicDataTreeLeaf(write.leafSlot, write.value);
168
+ });
169
+ });
170
+
171
+ try {
172
+ return await this.instance.call(
173
+ WorldStateMessageType.SYNC_BLOCK,
174
+ {
175
+ blockNumber: l2Block.number,
176
+ blockHeaderHash: await l2Block.header.hash(),
177
+ paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
178
+ paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
179
+ paddedNullifiers: paddedNullifiers.map(serializeLeaf),
180
+ publicDataWrites: publicDataWrites.map(serializeLeaf),
181
+ blockStateRef: blockStateReference(l2Block.header.state),
182
+ canonical: true,
183
+ },
184
+ this.sanitiseAndCacheSummaryFromFull.bind(this),
185
+ this.deleteCachedSummary.bind(this),
186
+ );
187
+ } catch (err) {
188
+ this.worldStateInstrumentation.incCriticalErrors('synch_pending_block');
189
+ throw err;
190
+ }
191
+ }
192
+
193
+ public async close(): Promise<void> {
194
+ await this.instance.close();
195
+ await this.cleanup();
196
+ }
197
+
198
+ private async buildInitialHeader(): Promise<BlockHeader> {
199
+ const state = await this.getInitialStateReference();
200
+ return BlockHeader.empty({ state });
201
+ }
202
+
203
+ private sanitiseAndCacheSummaryFromFull(response: WorldStateStatusFull) {
204
+ const sanitised = sanitiseFullStatus(response);
205
+ this.cachedStatusSummary = { ...sanitised.summary };
206
+ return sanitised;
207
+ }
208
+
209
+ private sanitiseAndCacheSummary(response: WorldStateStatusSummary) {
210
+ const sanitised = sanitiseSummary(response);
211
+ this.cachedStatusSummary = { ...sanitised };
212
+ return sanitised;
213
+ }
214
+
215
+ private deleteCachedSummary(_: string) {
216
+ this.cachedStatusSummary = undefined;
217
+ }
218
+
219
+ /**
220
+ * Advances the finalised block number to be the number provided
221
+ * @param toBlockNumber The block number that is now the tip of the finalised chain
222
+ * @returns The new WorldStateStatus
223
+ */
224
+ public async setFinalised(toBlockNumber: bigint) {
225
+ try {
226
+ await this.instance.call(
227
+ WorldStateMessageType.FINALISE_BLOCKS,
228
+ {
229
+ toBlockNumber,
230
+ canonical: true,
231
+ },
232
+ this.sanitiseAndCacheSummary.bind(this),
233
+ this.deleteCachedSummary.bind(this),
234
+ );
235
+ } catch (err) {
236
+ this.worldStateInstrumentation.incCriticalErrors('finalize_block');
237
+ throw err;
238
+ }
239
+ return this.getStatusSummary();
240
+ }
241
+
242
+ /**
243
+ * Removes all historical snapshots up to but not including the given block number
244
+ * @param toBlockNumber The block number of the new oldest historical block
245
+ * @returns The new WorldStateStatus
246
+ */
247
+ public async removeHistoricalBlocks(toBlockNumber: bigint) {
248
+ try {
249
+ return await this.instance.call(
250
+ WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
251
+ {
252
+ toBlockNumber,
253
+ canonical: true,
254
+ },
255
+ this.sanitiseAndCacheSummaryFromFull.bind(this),
256
+ this.deleteCachedSummary.bind(this),
257
+ );
258
+ } catch (err) {
259
+ this.worldStateInstrumentation.incCriticalErrors('prune_historical_block');
260
+ throw err;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Removes all pending blocks down to but not including the given block number
266
+ * @param toBlockNumber The block number of the new tip of the pending chain,
267
+ * @returns The new WorldStateStatus
268
+ */
269
+ public async unwindBlocks(toBlockNumber: bigint) {
270
+ try {
271
+ return await this.instance.call(
272
+ WorldStateMessageType.UNWIND_BLOCKS,
273
+ {
274
+ toBlockNumber,
275
+ canonical: true,
276
+ },
277
+ this.sanitiseAndCacheSummaryFromFull.bind(this),
278
+ this.deleteCachedSummary.bind(this),
279
+ );
280
+ } catch (err) {
281
+ this.worldStateInstrumentation.incCriticalErrors('prune_pending_block');
282
+ throw err;
283
+ }
284
+ }
285
+
286
+ public async getStatusSummary() {
287
+ if (this.cachedStatusSummary !== undefined) {
288
+ return { ...this.cachedStatusSummary };
289
+ }
290
+ return await this.instance.call(
291
+ WorldStateMessageType.GET_STATUS,
292
+ { canonical: true },
293
+ this.sanitiseAndCacheSummary.bind(this),
294
+ );
295
+ }
296
+
297
+ updateLeaf<ID extends IndexedTreeId>(
298
+ _treeId: ID,
299
+ _leaf: NullifierLeafPreimage | Buffer,
300
+ _index: bigint,
301
+ ): Promise<void> {
302
+ return Promise.reject(new Error('Method not implemented'));
303
+ }
304
+
305
+ private async getInitialStateReference(): Promise<StateReference> {
306
+ const resp = await this.instance.call(WorldStateMessageType.GET_INITIAL_STATE_REFERENCE, { canonical: true });
307
+
308
+ return new StateReference(
309
+ treeStateReferenceToSnapshot(resp.state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]),
310
+ new PartialStateReference(
311
+ treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NOTE_HASH_TREE]),
312
+ treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NULLIFIER_TREE]),
313
+ treeStateReferenceToSnapshot(resp.state[MerkleTreeId.PUBLIC_DATA_TREE]),
314
+ ),
315
+ );
316
+ }
317
+ }
@@ -0,0 +1,238 @@
1
+ import {
2
+ ARCHIVE_HEIGHT,
3
+ GeneratorIndex,
4
+ L1_TO_L2_MSG_TREE_HEIGHT,
5
+ MAX_NULLIFIERS_PER_TX,
6
+ MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
7
+ NOTE_HASH_TREE_HEIGHT,
8
+ NULLIFIER_TREE_HEIGHT,
9
+ PUBLIC_DATA_TREE_HEIGHT,
10
+ } from '@aztec/constants';
11
+ import { createLogger } from '@aztec/foundation/log';
12
+ import { NativeWorldState as BaseNativeWorldState, MsgpackChannel } from '@aztec/native';
13
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
14
+ import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
15
+
16
+ import assert from 'assert';
17
+ import { cpus } from 'os';
18
+
19
+ import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
20
+ import {
21
+ WorldStateMessageType,
22
+ type WorldStateRequest,
23
+ type WorldStateRequestCategories,
24
+ type WorldStateResponse,
25
+ isWithCanonical,
26
+ isWithForkId,
27
+ isWithRevision,
28
+ } from './message.js';
29
+ import { WorldStateOpsQueue } from './world_state_ops_queue.js';
30
+
31
+ const MAX_WORLD_STATE_THREADS = +(process.env.HARDWARE_CONCURRENCY || '16');
32
+
33
+ export interface NativeWorldStateInstance {
34
+ call<T extends WorldStateMessageType>(
35
+ messageType: T,
36
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
37
+ ): Promise<WorldStateResponse[T]>;
38
+ }
39
+
40
+ /**
41
+ * Strongly-typed interface to access the WorldState class in the native world_state_napi module.
42
+ */
43
+ export class NativeWorldState implements NativeWorldStateInstance {
44
+ private open = true;
45
+
46
+ // We maintain a map of queue to fork
47
+ private queues = new Map<number, WorldStateOpsQueue>();
48
+
49
+ private instance: MsgpackChannel<WorldStateMessageType, WorldStateRequest, WorldStateResponse>;
50
+
51
+ /** Creates a new native WorldState instance */
52
+ constructor(
53
+ dataDir: string,
54
+ dbMapSizeKb: number,
55
+ prefilledPublicData: PublicDataTreeLeaf[] = [],
56
+ private instrumentation: WorldStateInstrumentation,
57
+ private log = createLogger('world-state:database'),
58
+ ) {
59
+ const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
60
+ log.info(
61
+ `Creating world state data store at directory ${dataDir} with map size ${dbMapSizeKb} KB and ${threads} threads.`,
62
+ );
63
+ const prefilledPublicDataBufferArray = prefilledPublicData.map(d => [d.slot.toBuffer(), d.value.toBuffer()]);
64
+ const ws = new BaseNativeWorldState(
65
+ dataDir,
66
+ {
67
+ [MerkleTreeId.NULLIFIER_TREE]: NULLIFIER_TREE_HEIGHT,
68
+ [MerkleTreeId.NOTE_HASH_TREE]: NOTE_HASH_TREE_HEIGHT,
69
+ [MerkleTreeId.PUBLIC_DATA_TREE]: PUBLIC_DATA_TREE_HEIGHT,
70
+ [MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: L1_TO_L2_MSG_TREE_HEIGHT,
71
+ [MerkleTreeId.ARCHIVE]: ARCHIVE_HEIGHT,
72
+ },
73
+ {
74
+ [MerkleTreeId.NULLIFIER_TREE]: 2 * MAX_NULLIFIERS_PER_TX,
75
+ [MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
76
+ },
77
+ prefilledPublicDataBufferArray,
78
+ GeneratorIndex.BLOCK_HASH,
79
+ dbMapSizeKb,
80
+ threads,
81
+ );
82
+ this.instance = new MsgpackChannel(ws);
83
+ // Manually create the queue for the canonical fork
84
+ this.queues.set(0, new WorldStateOpsQueue());
85
+ }
86
+
87
+ /**
88
+ * Sends a message to the native instance and returns the response.
89
+ * @param messageType - The type of message to send
90
+ * @param body - The message body
91
+ * @param responseHandler - A callback accepting the response, executed on the job queue
92
+ * @param errorHandler - A callback called on request error, executed on the job queue
93
+ * @returns The response to the message
94
+ */
95
+ public async call<T extends WorldStateMessageType>(
96
+ messageType: T,
97
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
98
+ // allows for the pre-processing of responses on the job queue before being passed back
99
+ responseHandler = (response: WorldStateResponse[T]): WorldStateResponse[T] => response,
100
+ errorHandler = (_: string) => {},
101
+ ): Promise<WorldStateResponse[T]> {
102
+ // Here we determine which fork the request is being executed against and whether it requires uncommitted data
103
+ // We use the fork Id to select the appropriate request queue and the uncommitted data flag to pass to the queue
104
+ let forkId = -1;
105
+ // We assume it includes uncommitted unless explicitly told otherwise
106
+ let committedOnly = false;
107
+
108
+ // Canonical requests ALWAYS go against the canonical fork
109
+ // These include things like block syncs/unwinds etc
110
+ // These requests don't contain a fork ID
111
+ if (isWithCanonical(body)) {
112
+ forkId = 0;
113
+ } else if (isWithForkId(body)) {
114
+ forkId = body.forkId;
115
+ } else if (isWithRevision(body)) {
116
+ forkId = body.revision.forkId;
117
+ committedOnly = body.revision.includeUncommitted === false;
118
+ } else {
119
+ const _: never = body;
120
+ throw new Error(`Unable to determine forkId for message=${WorldStateMessageType[messageType]}`);
121
+ }
122
+
123
+ // Get the queue or create a new one
124
+ let requestQueue = this.queues.get(forkId);
125
+ if (requestQueue === undefined) {
126
+ requestQueue = new WorldStateOpsQueue();
127
+ this.queues.set(forkId, requestQueue);
128
+ }
129
+
130
+ // Enqueue the request and wait for the response
131
+ const response = await requestQueue.execute(
132
+ async () => {
133
+ assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the native instance');
134
+ assert.equal(this.open, true, 'Native instance is closed');
135
+ let response: WorldStateResponse[T];
136
+ try {
137
+ response = await this._sendMessage(messageType, body);
138
+ } catch (error: any) {
139
+ errorHandler(error.message);
140
+ throw error;
141
+ }
142
+ return responseHandler(response);
143
+ },
144
+ messageType,
145
+ committedOnly,
146
+ );
147
+
148
+ // If the request was to delete the fork then we clean it up here
149
+ if (messageType === WorldStateMessageType.DELETE_FORK) {
150
+ await requestQueue.stop();
151
+ this.queues.delete(forkId);
152
+ }
153
+ return response;
154
+ }
155
+
156
+ /**
157
+ * Stops the native instance.
158
+ */
159
+ public async close(): Promise<void> {
160
+ if (!this.open) {
161
+ return;
162
+ }
163
+ this.open = false;
164
+ const queue = this.queues.get(0)!;
165
+
166
+ await queue.execute(
167
+ async () => {
168
+ await this._sendMessage(WorldStateMessageType.CLOSE, { canonical: true });
169
+ },
170
+ WorldStateMessageType.CLOSE,
171
+ false,
172
+ );
173
+ await queue.stop();
174
+ }
175
+
176
+ private async _sendMessage<T extends WorldStateMessageType>(
177
+ messageType: T,
178
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
179
+ ): Promise<WorldStateResponse[T]> {
180
+ let logMetadata: Record<string, any> = {};
181
+
182
+ if (body) {
183
+ if ('treeId' in body) {
184
+ logMetadata['treeId'] = MerkleTreeId[body.treeId];
185
+ }
186
+
187
+ if ('revision' in body) {
188
+ logMetadata = { ...logMetadata, ...body.revision };
189
+ }
190
+
191
+ if ('forkId' in body) {
192
+ logMetadata['forkId'] = body.forkId;
193
+ }
194
+
195
+ if ('blockNumber' in body) {
196
+ logMetadata['blockNumber'] = body.blockNumber;
197
+ }
198
+
199
+ if ('toBlockNumber' in body) {
200
+ logMetadata['toBlockNumber'] = body.toBlockNumber;
201
+ }
202
+
203
+ if ('leafIndex' in body) {
204
+ logMetadata['leafIndex'] = body.leafIndex;
205
+ }
206
+
207
+ if ('blockHeaderHash' in body) {
208
+ logMetadata['blockHeaderHash'] = '0x' + body.blockHeaderHash.toString('hex');
209
+ }
210
+
211
+ if ('leaves' in body) {
212
+ logMetadata['leavesCount'] = body.leaves.length;
213
+ }
214
+
215
+ // sync operation
216
+ if ('paddedNoteHashes' in body) {
217
+ logMetadata['notesCount'] = body.paddedNoteHashes.length;
218
+ logMetadata['nullifiersCount'] = body.paddedNullifiers.length;
219
+ logMetadata['l1ToL2MessagesCount'] = body.paddedL1ToL2Messages.length;
220
+ logMetadata['publicDataWritesCount'] = body.publicDataWrites.length;
221
+ }
222
+ }
223
+
224
+ try {
225
+ const { duration, response } = await this.instance.sendMessage(messageType, body);
226
+ this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, {
227
+ duration,
228
+ ...logMetadata,
229
+ });
230
+
231
+ this.instrumentation.recordRoundTrip(duration.totalUs, messageType);
232
+ return response;
233
+ } catch (error) {
234
+ this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error, logMetadata);
235
+ throw error;
236
+ }
237
+ }
238
+ }