@aztec/world-state 0.0.1-commit.2ed92850 → 0.0.1-commit.2f68f620

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 (56) hide show
  1. package/dest/instrumentation/instrumentation.d.ts +4 -3
  2. package/dest/instrumentation/instrumentation.d.ts.map +1 -1
  3. package/dest/instrumentation/instrumentation.js +13 -11
  4. package/dest/native/fork_checkpoint.d.ts +7 -1
  5. package/dest/native/fork_checkpoint.d.ts.map +1 -1
  6. package/dest/native/fork_checkpoint.js +15 -3
  7. package/dest/native/ipc_world_state_instance.d.ts +87 -0
  8. package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
  9. package/dest/native/ipc_world_state_instance.js +660 -0
  10. package/dest/native/merkle_trees_facade.d.ts +12 -8
  11. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  12. package/dest/native/merkle_trees_facade.js +63 -115
  13. package/dest/native/message.d.ts +14 -221
  14. package/dest/native/message.d.ts.map +1 -1
  15. package/dest/native/message.js +0 -42
  16. package/dest/native/native_world_state.d.ts +20 -8
  17. package/dest/native/native_world_state.d.ts.map +1 -1
  18. package/dest/native/native_world_state.js +116 -60
  19. package/dest/native/native_world_state_instance.d.ts +58 -41
  20. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  21. package/dest/native/native_world_state_instance.js +5 -202
  22. package/dest/native/world_state_operation.d.ts +7 -0
  23. package/dest/native/world_state_operation.d.ts.map +1 -0
  24. package/dest/native/world_state_operation.js +5 -0
  25. package/dest/synchronizer/config.d.ts +3 -3
  26. package/dest/synchronizer/config.d.ts.map +1 -1
  27. package/dest/synchronizer/config.js +15 -13
  28. package/dest/synchronizer/factory.d.ts +6 -5
  29. package/dest/synchronizer/factory.d.ts.map +1 -1
  30. package/dest/synchronizer/factory.js +7 -6
  31. package/dest/synchronizer/server_world_state_synchronizer.d.ts +4 -4
  32. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  33. package/dest/synchronizer/server_world_state_synchronizer.js +95 -21
  34. package/dest/testing.d.ts +4 -3
  35. package/dest/testing.d.ts.map +1 -1
  36. package/dest/testing.js +10 -6
  37. package/dest/world-state-db/merkle_tree_db.d.ts +1 -10
  38. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
  39. package/package.json +14 -11
  40. package/src/instrumentation/instrumentation.ts +15 -19
  41. package/src/native/fork_checkpoint.ts +19 -3
  42. package/src/native/ipc_world_state_instance.ts +830 -0
  43. package/src/native/merkle_trees_facade.ts +89 -109
  44. package/src/native/message.ts +13 -286
  45. package/src/native/native_world_state.ts +136 -94
  46. package/src/native/native_world_state_instance.ts +96 -278
  47. package/src/native/world_state_operation.ts +33 -0
  48. package/src/synchronizer/config.ts +21 -15
  49. package/src/synchronizer/factory.ts +18 -11
  50. package/src/synchronizer/server_world_state_synchronizer.ts +99 -22
  51. package/src/testing.ts +8 -9
  52. package/src/world-state-db/merkle_tree_db.ts +0 -10
  53. package/dest/native/world_state_ops_queue.d.ts +0 -19
  54. package/dest/native/world_state_ops_queue.d.ts.map +0 -1
  55. package/dest/native/world_state_ops_queue.js +0 -146
  56. package/src/native/world_state_ops_queue.ts +0 -190
@@ -0,0 +1,660 @@
1
+ import { ARCHIVE_HEIGHT, DomainSeparator, L1_TO_L2_MSG_TREE_HEIGHT, MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_TREE_HEIGHT, NULLIFIER_TREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT } from '@aztec/constants';
2
+ import { createLogger } from '@aztec/foundation/log';
3
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
4
+ import { WsdbService } from '@aztec/wsdb';
5
+ import { cpus } from 'node:os';
6
+ // ————— Request conversion helpers —————
7
+ function toWsdbRevision(rev) {
8
+ return {
9
+ forkId: rev.forkId,
10
+ blockNumber: Number(rev.blockNumber),
11
+ includeUncommitted: rev.includeUncommitted
12
+ };
13
+ }
14
+ function blockStateRefToMap(ref) {
15
+ return [
16
+ ...ref.entries()
17
+ ].map(([treeId, [root, size]])=>({
18
+ treeId,
19
+ root: new Uint8Array(root),
20
+ size: Number(size)
21
+ }));
22
+ }
23
+ function toPublicDataLeaf(leaf) {
24
+ if (leaf instanceof Buffer || !('slot' in leaf)) {
25
+ throw new Error('Expected public data leaf');
26
+ }
27
+ return {
28
+ slot: new Uint8Array(leaf.slot),
29
+ value: new Uint8Array(leaf.value)
30
+ };
31
+ }
32
+ function toNullifierLeaf(leaf) {
33
+ if (leaf instanceof Buffer || !('nullifier' in leaf)) {
34
+ throw new Error('Expected nullifier leaf');
35
+ }
36
+ return {
37
+ nullifier: new Uint8Array(leaf.nullifier)
38
+ };
39
+ }
40
+ function fromPublicDataLeaf(leaf) {
41
+ return {
42
+ slot: Buffer.from(leaf.slot),
43
+ value: Buffer.from(leaf.value)
44
+ };
45
+ }
46
+ function fromNullifierLeaf(leaf) {
47
+ return {
48
+ nullifier: Buffer.from(leaf.nullifier)
49
+ };
50
+ }
51
+ function fromIndexedLeaf(leaf, convertLeaf) {
52
+ return {
53
+ leaf: convertLeaf(leaf.leaf),
54
+ nextIndex: leaf.nextIndex,
55
+ nextKey: Buffer.from(leaf.nextKey)
56
+ };
57
+ }
58
+ function fromLeafUpdateWitnessData(data, convertLeaf) {
59
+ return {
60
+ leaf: fromIndexedLeaf(data.leaf, convertLeaf),
61
+ index: data.index,
62
+ path: data.path.map((p)=>Buffer.from(p))
63
+ };
64
+ }
65
+ function fromBatchInsertionResult(result, convertLeaf) {
66
+ return {
67
+ lowLeafWitnessData: result.lowLeafWitnessData.map((data)=>fromLeafUpdateWitnessData(data, convertLeaf)),
68
+ sortedLeaves: result.sortedLeaves.map(({ leaf, index })=>[
69
+ convertLeaf(leaf),
70
+ index
71
+ ]),
72
+ subtreePath: result.subtreePath.map((p)=>Buffer.from(p))
73
+ };
74
+ }
75
+ function fromSequentialInsertionResult(result, convertLeaf) {
76
+ return {
77
+ lowLeafWitnessData: result.lowLeafWitnessData.map((data)=>fromLeafUpdateWitnessData(data, convertLeaf)),
78
+ insertionWitnessData: result.insertionWitnessData.map((data)=>fromLeafUpdateWitnessData(data, convertLeaf))
79
+ };
80
+ }
81
+ function toFrLeaf(leaf) {
82
+ if (!(leaf instanceof Buffer)) {
83
+ throw new Error('Expected field leaf');
84
+ }
85
+ return new Uint8Array(leaf);
86
+ }
87
+ function formatMap(map) {
88
+ if (!map || Object.keys(map).length === 0) {
89
+ return undefined;
90
+ }
91
+ return `{${Object.entries(map).map(([key, value])=>`${key}:${value}`).join(',')}}`;
92
+ }
93
+ /** SHM request/response ring size for the spawned aztec-wsdb process (32 MiB). */ const WSDB_SHM_RING_SIZE = 32 * 1024 * 1024;
94
+ function getWsdbThreadCount() {
95
+ return Math.min(16, cpus().length);
96
+ }
97
+ function getWsdbExtraArgs(dataDir, wsTreeMapSizes, genesis, threads) {
98
+ const options = getWsdbOptions(dataDir, wsTreeMapSizes);
99
+ const args = [
100
+ '--data-dir',
101
+ dataDir,
102
+ '--threads',
103
+ threads.toString()
104
+ ];
105
+ // Size the SHM rings generously. Responses such as batch-insertion witnesses
106
+ // run to a few MB, and an SHM frame is capped at half the ring (the wrap
107
+ // handling needs that headroom), so 32 MiB rings give ~16 MiB per message.
108
+ // Ignored by the UDS transport. Pages are demand-faulted, so unused capacity
109
+ // is cheap.
110
+ args.push('--request-ring-size', WSDB_SHM_RING_SIZE.toString());
111
+ args.push('--response-ring-size', WSDB_SHM_RING_SIZE.toString());
112
+ const treeHeights = formatMap(options.treeHeights);
113
+ if (treeHeights) {
114
+ args.push('--tree-heights', treeHeights);
115
+ }
116
+ const treePrefill = formatMap(options.treePrefill);
117
+ if (treePrefill) {
118
+ args.push('--tree-prefill', treePrefill);
119
+ }
120
+ const mapSizes = formatMap(options.mapSizes);
121
+ if (mapSizes) {
122
+ args.push('--map-sizes', mapSizes);
123
+ }
124
+ args.push('--initial-header-generator-point', options.initialHeaderGeneratorPoint.toString());
125
+ if (genesis.prefilledPublicData.length > 0) {
126
+ const pairs = genesis.prefilledPublicData.map((data)=>[
127
+ data.slot.toBuffer().toString('hex'),
128
+ data.value.toBuffer().toString('hex')
129
+ ]);
130
+ args.push('--prefilled-public-data', JSON.stringify(pairs));
131
+ }
132
+ const genesisTimestamp = Number(genesis.genesisTimestamp);
133
+ if (genesisTimestamp !== 0) {
134
+ args.push('--genesis-timestamp', genesisTimestamp.toString());
135
+ }
136
+ return args;
137
+ }
138
+ // ————— Response conversion helpers —————
139
+ /** Convert Uint8Array fields to Buffer recursively (for opaque blob responses). */ function convertUint8ArraysToBuffers(obj) {
140
+ if (obj instanceof Uint8Array) {
141
+ return Buffer.from(obj);
142
+ }
143
+ if (Array.isArray(obj)) {
144
+ return obj.map(convertUint8ArraysToBuffers);
145
+ }
146
+ if (obj !== null && typeof obj === 'object') {
147
+ const result = {};
148
+ for (const [key, value] of Object.entries(obj)){
149
+ result[key] = convertUint8ArraysToBuffers(value);
150
+ }
151
+ return result;
152
+ }
153
+ return obj;
154
+ }
155
+ /** Convert Wsdb state reference (Record<number, [Uint8Array, number]>) to NAPI format. */ function convertStateRef(state) {
156
+ const result = {};
157
+ for (const { treeId, root, size } of state){
158
+ result[treeId] = [
159
+ Buffer.from(root),
160
+ BigInt(size)
161
+ ];
162
+ }
163
+ return result;
164
+ }
165
+ /** Convert Wsdb WorldStateStatusSummary to the native world-state format. */ function convertStatusSummary(s) {
166
+ return {
167
+ unfinalizedBlockNumber: s.unfinalizedBlockNumber,
168
+ finalizedBlockNumber: s.finalizedBlockNumber,
169
+ oldestHistoricalBlock: s.oldestHistoricalBlock,
170
+ treesAreSynched: s.treesAreSynched
171
+ };
172
+ }
173
+ function convertDBStats(s) {
174
+ return {
175
+ name: s.name,
176
+ numDataItems: s.numDataItems,
177
+ totalUsedSize: s.totalUsedSize
178
+ };
179
+ }
180
+ function convertTreeDBStats(s) {
181
+ return {
182
+ mapSize: s.mapSize,
183
+ physicalFileSize: s.physicalFileSize,
184
+ blocksDBStats: convertDBStats(s.blocksDBStats),
185
+ nodesDBStats: convertDBStats(s.nodesDBStats),
186
+ leafPreimagesDBStats: convertDBStats(s.leafPreimagesDBStats),
187
+ leafIndicesDBStats: convertDBStats(s.leafIndicesDBStats),
188
+ blockIndicesDBStats: convertDBStats(s.blockIndicesDBStats)
189
+ };
190
+ }
191
+ function convertWorldStateDBStats(s) {
192
+ return {
193
+ noteHashTreeStats: convertTreeDBStats(s.noteHashTreeStats),
194
+ messageTreeStats: convertTreeDBStats(s.messageTreeStats),
195
+ archiveTreeStats: convertTreeDBStats(s.archiveTreeStats),
196
+ publicDataTreeStats: convertTreeDBStats(s.publicDataTreeStats),
197
+ nullifierTreeStats: convertTreeDBStats(s.nullifierTreeStats)
198
+ };
199
+ }
200
+ function convertTreeMeta(m) {
201
+ return {
202
+ name: m.name,
203
+ depth: m.depth,
204
+ size: m.size,
205
+ committedSize: m.committedSize,
206
+ root: m.root,
207
+ initialSize: m.initialSize,
208
+ initialRoot: m.initialRoot,
209
+ oldestHistoricBlock: m.oldestHistoricBlock,
210
+ unfinalizedBlockHeight: m.unfinalizedBlockHeight,
211
+ finalizedBlockHeight: m.finalizedBlockHeight
212
+ };
213
+ }
214
+ function convertWorldStateMeta(m) {
215
+ return {
216
+ noteHashTreeMeta: convertTreeMeta(m.noteHashTreeMeta),
217
+ messageTreeMeta: convertTreeMeta(m.messageTreeMeta),
218
+ archiveTreeMeta: convertTreeMeta(m.archiveTreeMeta),
219
+ publicDataTreeMeta: convertTreeMeta(m.publicDataTreeMeta),
220
+ nullifierTreeMeta: convertTreeMeta(m.nullifierTreeMeta)
221
+ };
222
+ }
223
+ function convertStatusFull(s) {
224
+ return {
225
+ summary: convertStatusSummary(s.summary),
226
+ dbStats: convertWorldStateDBStats(s.dbStats),
227
+ meta: convertWorldStateMeta(s.meta)
228
+ };
229
+ }
230
+ /** Convert Wsdb SiblingPathAndIndex to NAPI format. */ function convertSiblingPathAndIndex(s) {
231
+ if (!s) {
232
+ return undefined;
233
+ }
234
+ return {
235
+ index: BigInt(s.index),
236
+ path: s.path.map((p)=>Buffer.from(p))
237
+ };
238
+ }
239
+ // ————— Public API —————
240
+ /**
241
+ * IPC-backed world state instance.
242
+ * Uses WsdbService (spawns aztec-wsdb binary) and the generated AsyncApi
243
+ * to communicate via the NamedUnion IPC protocol.
244
+ */ export class IpcWorldState {
245
+ wsdb;
246
+ instrumentation;
247
+ log;
248
+ open;
249
+ api;
250
+ /** Tracks checkpoint depth per fork (WSDB IPC doesn't return depth in response). */ checkpointDepths;
251
+ constructor(wsdb, instrumentation, bindings, log = createLogger('world-state:ipc-database', bindings)){
252
+ this.wsdb = wsdb;
253
+ this.instrumentation = instrumentation;
254
+ this.log = log;
255
+ this.open = true;
256
+ this.checkpointDepths = new Map();
257
+ this.api = wsdb;
258
+ this.log.info('Created IPC-backed world state instance');
259
+ }
260
+ /**
261
+ * Spawn an `aztec-wsdb` subprocess and return an IPC-backed world state wrapping it.
262
+ * Encapsulates wsdb binary discovery, service construction, and readiness wait.
263
+ */ static async spawn(dataDir, wsTreeMapSizes, genesis, instrumentation, bindings) {
264
+ const threads = getWsdbThreadCount();
265
+ const transport = process.env.WSDB_TRANSPORT === 'shm' ? 'shm' : 'uds';
266
+ const wsdb = await WsdbService.spawn({
267
+ transport,
268
+ extraArgs: getWsdbExtraArgs(dataDir, wsTreeMapSizes, genesis, threads),
269
+ env: {
270
+ HARDWARE_CONCURRENCY: threads.toString()
271
+ }
272
+ });
273
+ return new IpcWorldState(wsdb, instrumentation, bindings);
274
+ }
275
+ getIpcPath() {
276
+ return this.wsdb.getIpcPath();
277
+ }
278
+ async close() {
279
+ if (!this.open) {
280
+ return;
281
+ }
282
+ this.open = false;
283
+ await this.wsdb.destroy();
284
+ }
285
+ getTreeInfo(treeId, revision) {
286
+ return this.execute('getTreeInfo', revision.forkId, revision.includeUncommitted === false, async ()=>{
287
+ const resp = await this.api.getTreeInfo({
288
+ treeId,
289
+ revision: toWsdbRevision(revision)
290
+ });
291
+ return {
292
+ treeId: resp.treeId,
293
+ root: Buffer.from(resp.root),
294
+ size: BigInt(resp.size),
295
+ depth: resp.depth
296
+ };
297
+ });
298
+ }
299
+ getStateReference(revision) {
300
+ return this.execute('getStateReference', revision.forkId, revision.includeUncommitted === false, async ()=>{
301
+ const resp = await this.api.getStateReference({
302
+ revision: toWsdbRevision(revision)
303
+ });
304
+ return convertStateRef(resp.state);
305
+ });
306
+ }
307
+ getInitialStateReference() {
308
+ return this.execute('getInitialStateReference', 0, false, async ()=>{
309
+ const resp = await this.api.getInitialStateReference({});
310
+ return convertStateRef(resp.state);
311
+ });
312
+ }
313
+ getLeafValue(treeId, revision, leafIndex) {
314
+ return this.execute('getLeafValue', revision.forkId, revision.includeUncommitted === false, async ()=>{
315
+ const wsdbRevision = toWsdbRevision(revision);
316
+ const wsdbLeafIndex = Number(leafIndex);
317
+ if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
318
+ const resp = await this.api.getPublicDataLeafValue({
319
+ revision: wsdbRevision,
320
+ leafIndex: wsdbLeafIndex
321
+ });
322
+ return resp.value ? fromPublicDataLeaf(resp.value) : undefined;
323
+ }
324
+ if (treeId === MerkleTreeId.NULLIFIER_TREE) {
325
+ const resp = await this.api.getNullifierLeafValue({
326
+ revision: wsdbRevision,
327
+ leafIndex: wsdbLeafIndex
328
+ });
329
+ return resp.value ? fromNullifierLeaf(resp.value) : undefined;
330
+ }
331
+ const resp = await this.api.getLeafValue({
332
+ treeId,
333
+ revision: wsdbRevision,
334
+ leafIndex: wsdbLeafIndex
335
+ });
336
+ return resp.value ? Buffer.from(resp.value) : undefined;
337
+ });
338
+ }
339
+ getLeafPreimage(treeId, revision, leafIndex) {
340
+ return this.execute('getLeafPreimage', revision.forkId, revision.includeUncommitted === false, async ()=>{
341
+ const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.getPublicDataLeafPreimage({
342
+ revision: toWsdbRevision(revision),
343
+ leafIndex: Number(leafIndex)
344
+ }) : await this.api.getNullifierLeafPreimage({
345
+ revision: toWsdbRevision(revision),
346
+ leafIndex: Number(leafIndex)
347
+ });
348
+ return resp.preimage ? convertUint8ArraysToBuffers(resp.preimage) : undefined;
349
+ });
350
+ }
351
+ getSiblingPath(treeId, revision, leafIndex) {
352
+ return this.execute('getSiblingPath', revision.forkId, revision.includeUncommitted === false, async ()=>{
353
+ const resp = await this.api.getSiblingPath({
354
+ treeId,
355
+ revision: toWsdbRevision(revision),
356
+ leafIndex: Number(leafIndex)
357
+ });
358
+ return resp.path.map((p)=>Buffer.from(p));
359
+ });
360
+ }
361
+ getBlockNumbersForLeafIndices(treeId, revision, leafIndices) {
362
+ return this.execute('getBlockNumbersForLeafIndices', revision.forkId, revision.includeUncommitted === false, async ()=>{
363
+ const resp = await this.api.getBlockNumbersForLeafIndices({
364
+ treeId,
365
+ revision: toWsdbRevision(revision),
366
+ leafIndices: leafIndices.map(Number)
367
+ });
368
+ return resp.blockNumbers.map((n)=>n != null ? BigInt(n) : undefined);
369
+ });
370
+ }
371
+ findLeafIndices(treeId, revision, leaves, startIndex) {
372
+ return this.execute('findLeafIndices', revision.forkId, revision.includeUncommitted === false, async ()=>{
373
+ const wsdbRevision = toWsdbRevision(revision);
374
+ const wsdbStartIndex = Number(startIndex);
375
+ const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.findPublicDataLeafIndices({
376
+ revision: wsdbRevision,
377
+ leaves: leaves.map(toPublicDataLeaf),
378
+ startIndex: wsdbStartIndex
379
+ }) : treeId === MerkleTreeId.NULLIFIER_TREE ? await this.api.findNullifierLeafIndices({
380
+ revision: wsdbRevision,
381
+ leaves: leaves.map(toNullifierLeaf),
382
+ startIndex: wsdbStartIndex
383
+ }) : await this.api.findLeafIndices({
384
+ treeId,
385
+ revision: wsdbRevision,
386
+ leaves: leaves.map(toFrLeaf),
387
+ startIndex: wsdbStartIndex
388
+ });
389
+ return resp.indices.map((n)=>n != null ? BigInt(n) : undefined);
390
+ });
391
+ }
392
+ findLowLeaf(treeId, revision, key) {
393
+ return this.execute('findLowLeaf', revision.forkId, revision.includeUncommitted === false, async ()=>{
394
+ const resp = await this.api.findLowLeaf({
395
+ treeId,
396
+ revision: toWsdbRevision(revision),
397
+ key: new Uint8Array(key.toBuffer())
398
+ });
399
+ return {
400
+ alreadyPresent: resp.alreadyPresent,
401
+ index: BigInt(resp.index)
402
+ };
403
+ });
404
+ }
405
+ findSiblingPaths(treeId, revision, leaves) {
406
+ return this.execute('findSiblingPaths', revision.forkId, revision.includeUncommitted === false, async ()=>{
407
+ const wsdbRevision = toWsdbRevision(revision);
408
+ const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.findPublicDataSiblingPaths({
409
+ revision: wsdbRevision,
410
+ leaves: leaves.map(toPublicDataLeaf)
411
+ }) : treeId === MerkleTreeId.NULLIFIER_TREE ? await this.api.findNullifierSiblingPaths({
412
+ revision: wsdbRevision,
413
+ leaves: leaves.map(toNullifierLeaf)
414
+ }) : await this.api.findSiblingPaths({
415
+ treeId,
416
+ revision: wsdbRevision,
417
+ leaves: leaves.map(toFrLeaf)
418
+ });
419
+ return resp.paths.map(convertSiblingPathAndIndex);
420
+ });
421
+ }
422
+ updateArchive(forkId, blockStateRef, blockHeaderHash) {
423
+ return this.execute('updateArchive', forkId, false, async ()=>{
424
+ await this.api.updateArchive({
425
+ blockStateRef: blockStateRefToMap(blockStateRef),
426
+ blockHeaderHash: new Uint8Array(blockHeaderHash),
427
+ forkId
428
+ });
429
+ });
430
+ }
431
+ appendLeaves(treeId, forkId, leaves) {
432
+ return this.execute('appendLeaves', forkId, false, async ()=>{
433
+ if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
434
+ await this.api.appendPublicDataLeaves({
435
+ leaves: leaves.map(toPublicDataLeaf),
436
+ forkId
437
+ });
438
+ } else if (treeId === MerkleTreeId.NULLIFIER_TREE) {
439
+ await this.api.appendNullifierLeaves({
440
+ leaves: leaves.map(toNullifierLeaf),
441
+ forkId
442
+ });
443
+ } else {
444
+ await this.api.appendLeaves({
445
+ treeId,
446
+ leaves: leaves.map(toFrLeaf),
447
+ forkId
448
+ });
449
+ }
450
+ });
451
+ }
452
+ batchInsert(treeId, forkId, leaves, subtreeDepth) {
453
+ return this.execute('batchInsert', forkId, false, async ()=>{
454
+ const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.batchInsertPublicData({
455
+ leaves: leaves.map(toPublicDataLeaf),
456
+ subtreeDepth,
457
+ forkId
458
+ }) : await this.api.batchInsertNullifier({
459
+ leaves: leaves.map(toNullifierLeaf),
460
+ subtreeDepth,
461
+ forkId
462
+ });
463
+ return treeId === MerkleTreeId.PUBLIC_DATA_TREE ? fromBatchInsertionResult(resp.result, fromPublicDataLeaf) : fromBatchInsertionResult(resp.result, fromNullifierLeaf);
464
+ });
465
+ }
466
+ sequentialInsert(treeId, forkId, leaves) {
467
+ return this.execute('sequentialInsert', forkId, false, async ()=>{
468
+ const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.sequentialInsertPublicData({
469
+ leaves: leaves.map(toPublicDataLeaf),
470
+ forkId
471
+ }) : await this.api.sequentialInsertNullifier({
472
+ leaves: leaves.map(toNullifierLeaf),
473
+ forkId
474
+ });
475
+ return treeId === MerkleTreeId.PUBLIC_DATA_TREE ? fromSequentialInsertionResult(resp.result, fromPublicDataLeaf) : fromSequentialInsertionResult(resp.result, fromNullifierLeaf);
476
+ });
477
+ }
478
+ syncBlock(input) {
479
+ return this.execute('syncBlock', 0, false, async ()=>{
480
+ const resp = await this.api.syncBlock({
481
+ blockNumber: Number(input.blockNumber),
482
+ blockStateRef: blockStateRefToMap(input.blockStateRef),
483
+ blockHeaderHash: new Uint8Array(input.blockHeaderHash),
484
+ paddedNoteHashes: input.paddedNoteHashes.map(toFrLeaf),
485
+ paddedL1ToL2Messages: input.paddedL1ToL2Messages.map(toFrLeaf),
486
+ paddedNullifiers: input.paddedNullifiers.map(toNullifierLeaf),
487
+ publicDataWrites: input.publicDataWrites.map(toPublicDataLeaf)
488
+ });
489
+ return convertStatusFull(resp.status);
490
+ });
491
+ }
492
+ createFork(input) {
493
+ return this.execute('createFork', 0, false, async ()=>{
494
+ const resp = await this.api.createFork({
495
+ latest: input.latest,
496
+ blockNumber: Number(input.blockNumber)
497
+ });
498
+ return resp.forkId;
499
+ });
500
+ }
501
+ deleteFork(forkId) {
502
+ return this.execute('deleteFork', forkId, false, async ()=>{
503
+ await this.api.deleteFork({
504
+ forkId
505
+ });
506
+ });
507
+ }
508
+ finalizeBlocks(toBlockNumber) {
509
+ return this.execute('finalizeBlocks', 0, false, async ()=>{
510
+ const resp = await this.api.finalizeBlocks({
511
+ toBlockNumber: Number(toBlockNumber)
512
+ });
513
+ return convertStatusSummary(resp.status);
514
+ });
515
+ }
516
+ unwindBlocks(toBlockNumber) {
517
+ return this.execute('unwindBlocks', 0, false, async ()=>{
518
+ const resp = await this.api.unwindBlocks({
519
+ toBlockNumber: Number(toBlockNumber)
520
+ });
521
+ return convertStatusFull(resp.status);
522
+ });
523
+ }
524
+ removeHistoricalBlocks(toBlockNumber) {
525
+ return this.execute('removeHistoricalBlocks', 0, false, async ()=>{
526
+ const resp = await this.api.removeHistoricalBlocks({
527
+ toBlockNumber: Number(toBlockNumber)
528
+ });
529
+ return convertStatusFull(resp.status);
530
+ });
531
+ }
532
+ getStatus() {
533
+ return this.execute('getStatus', 0, false, async ()=>{
534
+ const resp = await this.api.getStatus({});
535
+ return convertStatusSummary(resp.status);
536
+ });
537
+ }
538
+ createCheckpoint(forkId) {
539
+ return this.execute('createCheckpoint', forkId, false, async ()=>{
540
+ await this.api.createCheckpoint({
541
+ forkId
542
+ });
543
+ const depth = (this.checkpointDepths.get(forkId) ?? 0) + 1;
544
+ this.checkpointDepths.set(forkId, depth);
545
+ return depth;
546
+ });
547
+ }
548
+ commitCheckpoint(forkId) {
549
+ return this.execute('commitCheckpoint', forkId, false, async ()=>{
550
+ await this.api.commitCheckpoint({
551
+ forkId
552
+ });
553
+ const depth = Math.max(0, (this.checkpointDepths.get(forkId) ?? 0) - 1);
554
+ this.checkpointDepths.set(forkId, depth);
555
+ });
556
+ }
557
+ revertCheckpoint(forkId) {
558
+ return this.execute('revertCheckpoint', forkId, false, async ()=>{
559
+ await this.api.revertCheckpoint({
560
+ forkId
561
+ });
562
+ const depth = Math.max(0, (this.checkpointDepths.get(forkId) ?? 0) - 1);
563
+ this.checkpointDepths.set(forkId, depth);
564
+ });
565
+ }
566
+ commitAllCheckpoints(forkId, depth) {
567
+ return this.execute('commitAllCheckpoints', forkId, false, async ()=>{
568
+ const currentDepth = this.checkpointDepths.get(forkId) ?? 0;
569
+ if (depth === 0) {
570
+ await this.api.commitAllCheckpoints({
571
+ forkId
572
+ });
573
+ } else {
574
+ for(let d = currentDepth; d > depth; d--){
575
+ await this.api.commitCheckpoint({
576
+ forkId
577
+ });
578
+ }
579
+ }
580
+ this.checkpointDepths.set(forkId, depth);
581
+ });
582
+ }
583
+ revertAllCheckpoints(forkId, depth) {
584
+ return this.execute('revertAllCheckpoints', forkId, false, async ()=>{
585
+ const currentDepth = this.checkpointDepths.get(forkId) ?? 0;
586
+ if (depth === 0) {
587
+ await this.api.revertAllCheckpoints({
588
+ forkId
589
+ });
590
+ } else {
591
+ for(let d = currentDepth; d > depth; d--){
592
+ await this.api.revertCheckpoint({
593
+ forkId
594
+ });
595
+ }
596
+ }
597
+ this.checkpointDepths.set(forkId, depth);
598
+ });
599
+ }
600
+ copyStores(dstPath, compact) {
601
+ return this.execute('copyStores', 0, false, async ()=>{
602
+ await this.api.copyStores({
603
+ dstPath,
604
+ compact
605
+ });
606
+ });
607
+ }
608
+ // Read/write ordering is enforced server-side by the wsdb per-fork scheduler,
609
+ // so the client no longer queues: every op is sent immediately and the server
610
+ // serializes writes per fork while running reads concurrently. `_forkId` and
611
+ // `_committedOnly` are retained in the signature (they describe the op) but are
612
+ // no longer needed for client-side ordering.
613
+ async execute(operation, _forkId, _committedOnly, request) {
614
+ if (!this.open) {
615
+ throw new Error('IPC instance is closed');
616
+ }
617
+ return await this.send(operation, request);
618
+ }
619
+ async send(operation, request) {
620
+ const start = performance.now();
621
+ try {
622
+ const response = await request();
623
+ const durationMs = performance.now() - start;
624
+ this.log.trace(`Call ${operation} took (ms)`, {
625
+ duration: durationMs
626
+ });
627
+ this.instrumentation.recordRoundTrip(durationMs * 1000, operation);
628
+ return response;
629
+ } catch (error) {
630
+ this.log.error(`Call ${operation} failed: ${error}`, error);
631
+ throw error;
632
+ }
633
+ }
634
+ }
635
+ /**
636
+ * Helper to create WsdbOptions from standard world state config.
637
+ * Returns the options needed to construct a wsdb service command line.
638
+ */ export function getWsdbOptions(dataDir, wsTreeMapSizes) {
639
+ return {
640
+ treeHeights: {
641
+ [MerkleTreeId.NULLIFIER_TREE]: NULLIFIER_TREE_HEIGHT,
642
+ [MerkleTreeId.NOTE_HASH_TREE]: NOTE_HASH_TREE_HEIGHT,
643
+ [MerkleTreeId.PUBLIC_DATA_TREE]: PUBLIC_DATA_TREE_HEIGHT,
644
+ [MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: L1_TO_L2_MSG_TREE_HEIGHT,
645
+ [MerkleTreeId.ARCHIVE]: ARCHIVE_HEIGHT
646
+ },
647
+ treePrefill: {
648
+ [MerkleTreeId.NULLIFIER_TREE]: 2 * MAX_NULLIFIERS_PER_TX,
649
+ [MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
650
+ },
651
+ mapSizes: {
652
+ [MerkleTreeId.NULLIFIER_TREE]: wsTreeMapSizes.nullifierTreeMapSizeKb,
653
+ [MerkleTreeId.NOTE_HASH_TREE]: wsTreeMapSizes.noteHashTreeMapSizeKb,
654
+ [MerkleTreeId.PUBLIC_DATA_TREE]: wsTreeMapSizes.publicDataTreeMapSizeKb,
655
+ [MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: wsTreeMapSizes.messageTreeMapSizeKb,
656
+ [MerkleTreeId.ARCHIVE]: wsTreeMapSizes.archiveTreeMapSizeKb
657
+ },
658
+ initialHeaderGeneratorPoint: DomainSeparator.BLOCK_HEADER_HASH
659
+ };
660
+ }