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

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 (57) hide show
  1. package/dest/instrumentation/instrumentation.d.ts +3 -4
  2. package/dest/instrumentation/instrumentation.d.ts.map +1 -1
  3. package/dest/instrumentation/instrumentation.js +9 -4
  4. package/dest/native/ipc_world_state_instance.d.ts +22 -59
  5. package/dest/native/ipc_world_state_instance.d.ts.map +1 -1
  6. package/dest/native/ipc_world_state_instance.js +495 -520
  7. package/dest/native/merkle_trees_facade.d.ts +3 -4
  8. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  9. package/dest/native/merkle_trees_facade.js +109 -37
  10. package/dest/native/message.d.ts +233 -14
  11. package/dest/native/message.d.ts.map +1 -1
  12. package/dest/native/message.js +42 -0
  13. package/dest/native/native_world_state.d.ts +28 -14
  14. package/dest/native/native_world_state.d.ts.map +1 -1
  15. package/dest/native/native_world_state.js +102 -101
  16. package/dest/native/native_world_state_instance.d.ts +42 -58
  17. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  18. package/dest/native/native_world_state_instance.js +222 -5
  19. package/dest/native/world_state_ops_queue.d.ts +19 -0
  20. package/dest/native/world_state_ops_queue.d.ts.map +1 -0
  21. package/dest/native/world_state_ops_queue.js +146 -0
  22. package/dest/synchronizer/errors.d.ts +8 -1
  23. package/dest/synchronizer/errors.d.ts.map +1 -1
  24. package/dest/synchronizer/errors.js +8 -1
  25. package/dest/synchronizer/factory.d.ts +1 -1
  26. package/dest/synchronizer/factory.d.ts.map +1 -1
  27. package/dest/synchronizer/factory.js +1 -1
  28. package/dest/synchronizer/index.d.ts +2 -1
  29. package/dest/synchronizer/index.d.ts.map +1 -1
  30. package/dest/synchronizer/index.js +1 -0
  31. package/dest/synchronizer/server_world_state_synchronizer.d.ts +11 -6
  32. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  33. package/dest/synchronizer/server_world_state_synchronizer.js +55 -43
  34. package/dest/test/utils.d.ts +1 -1
  35. package/dest/test/utils.d.ts.map +1 -1
  36. package/dest/test/utils.js +6 -1
  37. package/dest/testing.d.ts +2 -2
  38. package/dest/testing.d.ts.map +1 -1
  39. package/dest/testing.js +15 -4
  40. package/package.json +10 -12
  41. package/src/instrumentation/instrumentation.ts +18 -6
  42. package/src/native/ipc_world_state_instance.ts +503 -608
  43. package/src/native/merkle_trees_facade.ts +104 -59
  44. package/src/native/message.ts +300 -13
  45. package/src/native/native_world_state.ts +156 -121
  46. package/src/native/native_world_state_instance.ts +307 -96
  47. package/src/native/world_state_ops_queue.ts +190 -0
  48. package/src/synchronizer/errors.ts +8 -0
  49. package/src/synchronizer/factory.ts +1 -0
  50. package/src/synchronizer/index.ts +1 -0
  51. package/src/synchronizer/server_world_state_synchronizer.ts +63 -30
  52. package/src/test/utils.ts +10 -1
  53. package/src/testing.ts +14 -4
  54. package/dest/native/world_state_operation.d.ts +0 -7
  55. package/dest/native/world_state_operation.d.ts.map +0 -1
  56. package/dest/native/world_state_operation.js +0 -5
  57. package/src/native/world_state_operation.ts +0 -33
@@ -1,3 +1,14 @@
1
+ import { AsyncApi } from '@aztec/bb.js/aztec-wsdb';
2
+ import type {
3
+ WorldStateDBStats as WsdbDBStats,
4
+ DBStats as WsdbDBStatsInner,
5
+ WorldStateMeta as WsdbMeta,
6
+ SiblingPathAndIndex as WsdbSiblingPathAndIndex,
7
+ WorldStateStatusFull as WsdbStatusFull,
8
+ WorldStateStatusSummary as WsdbStatusSummary,
9
+ TreeDBStats as WsdbTreeDBStats,
10
+ TreeMeta as WsdbTreeMeta,
11
+ } from '@aztec/bb.js/aztec-wsdb';
1
12
  import {
2
13
  ARCHIVE_HEIGHT,
3
14
  DomainSeparator,
@@ -8,211 +19,62 @@ import {
8
19
  NULLIFIER_TREE_HEIGHT,
9
20
  PUBLIC_DATA_TREE_HEIGHT,
10
21
  } from '@aztec/constants';
11
- import type { BlockNumber } from '@aztec/foundation/branded-types';
12
- import type { Fr } from '@aztec/foundation/curves/bn254';
13
22
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
14
- import type { IndexedTreeId, TreeInfo } from '@aztec/stdlib/interfaces/server';
15
23
  import { MerkleTreeId } from '@aztec/stdlib/trees';
16
- import type { GenesisData, WorldStateRevision } from '@aztec/stdlib/world-state';
17
- import { AsyncApi, WsdbService } from '@aztec/wsdb';
18
- import type {
19
- WorldStateDBStats as WsdbDBStats,
20
- DBStats as WsdbDBStatsInner,
21
- WorldStateMeta as WsdbMeta,
22
- SiblingPathAndIndex as WsdbSiblingPathAndIndex,
23
- WorldStateStatusFull as WsdbStatusFull,
24
- WorldStateStatusSummary as WsdbStatusSummary,
25
- TreeDBStats as WsdbTreeDBStats,
26
- TreeMeta as WsdbTreeMeta,
27
- } from '@aztec/wsdb';
24
+ import type { WorldStateRevision } from '@aztec/stdlib/world-state';
28
25
 
29
- import { cpus } from 'node:os';
26
+ import assert from 'assert';
27
+ import { Decoder, Encoder } from 'msgpackr';
30
28
 
31
29
  import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
32
30
  import type { WorldStateTreeMapSizes } from '../synchronizer/factory.js';
33
- import type {
34
- BlockStateReference,
35
- DBStats,
36
- SerializedBatchInsertionResult,
37
- SerializedIndexedLeaf,
38
- SerializedLeafValue,
39
- SerializedSequentialInsertionResult,
40
- SerializedSiblingPathAndIndex,
41
- TreeDBStats,
42
- TreeMeta,
43
- TreeStateReference,
44
- WorldStateDBStats,
45
- WorldStateMeta,
46
- WorldStateStatusFull,
47
- WorldStateStatusSummary,
31
+ import {
32
+ type DBStats,
33
+ type SerializedIndexedLeaf,
34
+ type SerializedLeafValue,
35
+ type TreeDBStats,
36
+ type TreeMeta,
37
+ type WorldStateDBStats,
38
+ WorldStateMessageType,
39
+ type WorldStateMeta,
40
+ type WorldStateRequest,
41
+ type WorldStateRequestCategories,
42
+ type WorldStateResponse,
43
+ type WorldStateStatusFull,
44
+ type WorldStateStatusSummary,
45
+ isWithCanonical,
46
+ isWithForkId,
47
+ isWithRevision,
48
48
  } from './message.js';
49
49
  import type { NativeWorldStateInstance } from './native_world_state_instance.js';
50
- import type { WorldStateOperationName } from './world_state_operation.js';
50
+ import { WorldStateOpsQueue } from './world_state_ops_queue.js';
51
51
 
52
- // ————— Request conversion helpers —————
52
+ // ————— Msgpack helpers —————
53
53
 
54
- function toWsdbRevision(rev: WorldStateRevision): { forkId: number; blockNumber: number; includeUncommitted: boolean } {
55
- return {
56
- forkId: rev.forkId,
57
- blockNumber: Number(rev.blockNumber),
58
- includeUncommitted: rev.includeUncommitted,
59
- };
60
- }
54
+ const msgpackEncoder = new Encoder({ useRecords: false });
55
+ const msgpackDecoder = new Decoder({ useRecords: false });
61
56
 
62
- function blockStateRefToMap(ref: Map<number, readonly [Buffer, number | bigint]>) {
63
- return [...ref.entries()].map(([treeId, [root, size]]) => ({
64
- treeId,
65
- root: new Uint8Array(root),
66
- size: Number(size),
67
- }));
57
+ /** Msgpack-encode a SerializedLeafValue into bytes for IPC transport. */
58
+ function serializeLeafToBytes(leaf: SerializedLeafValue): Uint8Array {
59
+ return Buffer.from(msgpackEncoder.pack(leaf));
68
60
  }
69
61
 
70
- function toPublicDataLeaf(leaf: SerializedLeafValue): { slot: Uint8Array; value: Uint8Array } {
71
- if (leaf instanceof Buffer || !('slot' in leaf)) {
72
- throw new Error('Expected public data leaf');
73
- }
74
- return { slot: new Uint8Array(leaf.slot), value: new Uint8Array(leaf.value) };
75
- }
76
-
77
- function toNullifierLeaf(leaf: SerializedLeafValue): { nullifier: Uint8Array } {
78
- if (leaf instanceof Buffer || !('nullifier' in leaf)) {
79
- throw new Error('Expected nullifier leaf');
80
- }
81
- return { nullifier: new Uint8Array(leaf.nullifier) };
82
- }
83
-
84
- function fromPublicDataLeaf(leaf: { slot: Uint8Array; value: Uint8Array }): Exclude<SerializedLeafValue, Buffer> {
85
- return { slot: Buffer.from(leaf.slot), value: Buffer.from(leaf.value) };
86
- }
87
-
88
- function fromNullifierLeaf(leaf: { nullifier: Uint8Array }): Exclude<SerializedLeafValue, Buffer> {
89
- return { nullifier: Buffer.from(leaf.nullifier) };
90
- }
91
-
92
- type WireIndexedLeaf<TLeaf> = { leaf: TLeaf; nextIndex: number; nextKey: Uint8Array };
93
- type WireLeafUpdateWitnessData<TLeaf> = { leaf: WireIndexedLeaf<TLeaf>; index: number; path: Uint8Array[] };
94
-
95
- function fromIndexedLeaf<TLeaf>(
96
- leaf: WireIndexedLeaf<TLeaf>,
97
- convertLeaf: (leaf: TLeaf) => Exclude<SerializedLeafValue, Buffer>,
98
- ) {
99
- return {
100
- leaf: convertLeaf(leaf.leaf),
101
- nextIndex: leaf.nextIndex,
102
- nextKey: Buffer.from(leaf.nextKey),
103
- };
104
- }
105
-
106
- function fromLeafUpdateWitnessData<TLeaf>(
107
- data: WireLeafUpdateWitnessData<TLeaf>,
108
- convertLeaf: (leaf: TLeaf) => Exclude<SerializedLeafValue, Buffer>,
109
- ) {
110
- return {
111
- leaf: fromIndexedLeaf(data.leaf, convertLeaf),
112
- index: data.index,
113
- path: data.path.map(p => Buffer.from(p)),
114
- };
115
- }
116
-
117
- function fromBatchInsertionResult<
118
- TLeaf,
119
- TResult extends {
120
- lowLeafWitnessData: Array<WireLeafUpdateWitnessData<TLeaf>>;
121
- sortedLeaves: Array<{ leaf: TLeaf; index: number }>;
122
- subtreePath: Uint8Array[];
123
- },
124
- >(result: TResult, convertLeaf: (leaf: TLeaf) => Exclude<SerializedLeafValue, Buffer>) {
125
- return {
126
- lowLeafWitnessData: result.lowLeafWitnessData.map(data => fromLeafUpdateWitnessData(data, convertLeaf)),
127
- sortedLeaves: result.sortedLeaves.map(({ leaf, index }) => [convertLeaf(leaf), index] as const),
128
- subtreePath: result.subtreePath.map(p => Buffer.from(p)),
129
- };
130
- }
62
+ // ————— Request conversion helpers —————
131
63
 
132
- function fromSequentialInsertionResult<
133
- TLeaf,
134
- TResult extends {
135
- lowLeafWitnessData: Array<WireLeafUpdateWitnessData<TLeaf>>;
136
- insertionWitnessData: Array<WireLeafUpdateWitnessData<TLeaf>>;
137
- },
138
- >(result: TResult, convertLeaf: (leaf: TLeaf) => Exclude<SerializedLeafValue, Buffer>) {
64
+ function toWsdbRevision(rev: WorldStateRevision): { forkid: number; blocknumber: number; includeuncommitted: boolean } {
139
65
  return {
140
- lowLeafWitnessData: result.lowLeafWitnessData.map(data => fromLeafUpdateWitnessData(data, convertLeaf)),
141
- insertionWitnessData: result.insertionWitnessData.map(data => fromLeafUpdateWitnessData(data, convertLeaf)),
66
+ forkid: rev.forkId,
67
+ blocknumber: Number(rev.blockNumber),
68
+ includeuncommitted: rev.includeUncommitted,
142
69
  };
143
70
  }
144
71
 
145
- function toFrLeaf(leaf: SerializedLeafValue): Uint8Array {
146
- if (!(leaf instanceof Buffer)) {
147
- throw new Error('Expected field leaf');
72
+ function blockStateRefToMap(ref: Map<number, readonly [Buffer, number | bigint]>): Map<number, [Uint8Array, number]> {
73
+ const result = new Map<number, [Uint8Array, number]>();
74
+ for (const [treeId, [root, size]] of ref.entries()) {
75
+ result.set(treeId, [new Uint8Array(root), Number(size)]);
148
76
  }
149
- return new Uint8Array(leaf);
150
- }
151
-
152
- function formatMap(map: Record<number, number> | undefined): string | undefined {
153
- if (!map || Object.keys(map).length === 0) {
154
- return undefined;
155
- }
156
- return `{${Object.entries(map)
157
- .map(([key, value]) => `${key}:${value}`)
158
- .join(',')}}`;
159
- }
160
-
161
- /** SHM request/response ring size for the spawned aztec-wsdb process (32 MiB). */
162
- const WSDB_SHM_RING_SIZE = 32 * 1024 * 1024;
163
-
164
- function getWsdbThreadCount(): number {
165
- return Math.min(16, cpus().length);
166
- }
167
-
168
- function getWsdbExtraArgs(
169
- dataDir: string,
170
- wsTreeMapSizes: WorldStateTreeMapSizes,
171
- genesis: GenesisData,
172
- threads: number,
173
- ): string[] {
174
- const options = getWsdbOptions(dataDir, wsTreeMapSizes);
175
- const args = ['--data-dir', dataDir, '--threads', threads.toString()];
176
-
177
- // Size the SHM rings generously. Responses such as batch-insertion witnesses
178
- // run to a few MB, and an SHM frame is capped at half the ring (the wrap
179
- // handling needs that headroom), so 32 MiB rings give ~16 MiB per message.
180
- // Ignored by the UDS transport. Pages are demand-faulted, so unused capacity
181
- // is cheap.
182
- args.push('--request-ring-size', WSDB_SHM_RING_SIZE.toString());
183
- args.push('--response-ring-size', WSDB_SHM_RING_SIZE.toString());
184
-
185
- const treeHeights = formatMap(options.treeHeights);
186
- if (treeHeights) {
187
- args.push('--tree-heights', treeHeights);
188
- }
189
-
190
- const treePrefill = formatMap(options.treePrefill);
191
- if (treePrefill) {
192
- args.push('--tree-prefill', treePrefill);
193
- }
194
-
195
- const mapSizes = formatMap(options.mapSizes);
196
- if (mapSizes) {
197
- args.push('--map-sizes', mapSizes);
198
- }
199
-
200
- args.push('--initial-header-generator-point', options.initialHeaderGeneratorPoint.toString());
201
-
202
- if (genesis.prefilledPublicData.length > 0) {
203
- const pairs = genesis.prefilledPublicData.map(data => [
204
- data.slot.toBuffer().toString('hex'),
205
- data.value.toBuffer().toString('hex'),
206
- ]);
207
- args.push('--prefilled-public-data', JSON.stringify(pairs));
208
- }
209
-
210
- const genesisTimestamp = Number(genesis.genesisTimestamp);
211
- if (genesisTimestamp !== 0) {
212
- args.push('--genesis-timestamp', genesisTimestamp.toString());
213
- }
214
-
215
- return args;
77
+ return result;
216
78
  }
217
79
 
218
80
  // ————— Response conversion helpers —————
@@ -235,54 +97,66 @@ function convertUint8ArraysToBuffers(obj: unknown): unknown {
235
97
  return obj;
236
98
  }
237
99
 
100
+ /** Decode a msgpack-encoded leaf value blob and convert Uint8Arrays to Buffers. */
101
+ function decodeLeafValue(encoded: Uint8Array): SerializedLeafValue {
102
+ const decoded = msgpackDecoder.unpack(Buffer.from(encoded));
103
+ return convertUint8ArraysToBuffers(decoded) as SerializedLeafValue;
104
+ }
105
+
106
+ /** Decode a msgpack-encoded indexed leaf preimage blob. */
107
+ function decodeLeafPreimage(encoded: Uint8Array): SerializedIndexedLeaf {
108
+ const decoded = msgpackDecoder.unpack(Buffer.from(encoded));
109
+ return convertUint8ArraysToBuffers(decoded) as SerializedIndexedLeaf;
110
+ }
111
+
238
112
  /** Convert Wsdb state reference (Record<number, [Uint8Array, number]>) to NAPI format. */
239
113
  function convertStateRef(
240
- state: Array<{ treeId: number; root: Uint8Array; size: number }>,
114
+ state: Record<number, [Uint8Array, number]>,
241
115
  ): Record<number, readonly [Buffer, number | bigint]> {
242
116
  const result: Record<number, readonly [Buffer, number | bigint]> = {};
243
- for (const { treeId, root, size } of state) {
244
- result[treeId] = [Buffer.from(root), BigInt(size)] as const;
117
+ for (const [key, [root, size]] of Object.entries(state)) {
118
+ result[Number(key)] = [Buffer.from(root), BigInt(size)] as const;
245
119
  }
246
120
  return result;
247
121
  }
248
122
 
249
- /** Convert Wsdb WorldStateStatusSummary to the native world-state format. */
123
+ /** Convert Wsdb WorldStateStatusSummary (lowercase) to NAPI format (camelCase). */
250
124
  function convertStatusSummary(s: WsdbStatusSummary): WorldStateStatusSummary {
251
125
  return {
252
- unfinalizedBlockNumber: s.unfinalizedBlockNumber,
253
- finalizedBlockNumber: s.finalizedBlockNumber,
254
- oldestHistoricalBlock: s.oldestHistoricalBlock,
255
- treesAreSynched: s.treesAreSynched,
126
+ unfinalizedBlockNumber: s.unfinalizedblocknumber,
127
+ finalizedBlockNumber: s.finalizedblocknumber,
128
+ oldestHistoricalBlock: s.oldesthistoricalblock,
129
+ treesAreSynched: s.treesaresynched,
256
130
  } as unknown as WorldStateStatusSummary;
257
131
  }
258
132
 
259
133
  function convertDBStats(s: WsdbDBStatsInner): DBStats {
260
134
  return {
261
135
  name: s.name,
262
- numDataItems: s.numDataItems,
263
- totalUsedSize: s.totalUsedSize,
136
+ numDataItems: s.numdataitems,
137
+ totalUsedSize: s.totalusedsize,
264
138
  } as unknown as DBStats;
265
139
  }
266
140
 
267
141
  function convertTreeDBStats(s: WsdbTreeDBStats): TreeDBStats {
268
142
  return {
269
- mapSize: s.mapSize,
270
- physicalFileSize: s.physicalFileSize,
271
- blocksDBStats: convertDBStats(s.blocksDBStats),
272
- nodesDBStats: convertDBStats(s.nodesDBStats),
273
- leafPreimagesDBStats: convertDBStats(s.leafPreimagesDBStats),
274
- leafIndicesDBStats: convertDBStats(s.leafIndicesDBStats),
275
- blockIndicesDBStats: convertDBStats(s.blockIndicesDBStats),
143
+ mapSize: s.mapsize,
144
+ physicalFileSize: s.physicalfilesize,
145
+ blocksDBStats: convertDBStats(s.blocksdbstats),
146
+ nodesDBStats: convertDBStats(s.nodesdbstats),
147
+ leafPreimagesDBStats: convertDBStats(s.leafpreimagesdbstats),
148
+ leafIndicesDBStats: convertDBStats(s.leafindicesdbstats),
149
+ blockIndicesDBStats: convertDBStats(s.blockindicesdbstats),
276
150
  } as unknown as TreeDBStats;
277
151
  }
278
152
 
279
153
  function convertWorldStateDBStats(s: WsdbDBStats): WorldStateDBStats {
280
154
  return {
281
- noteHashTreeStats: convertTreeDBStats(s.noteHashTreeStats),
282
- messageTreeStats: convertTreeDBStats(s.messageTreeStats),
283
- archiveTreeStats: convertTreeDBStats(s.archiveTreeStats),
284
- publicDataTreeStats: convertTreeDBStats(s.publicDataTreeStats),
285
- nullifierTreeStats: convertTreeDBStats(s.nullifierTreeStats),
155
+ noteHashTreeStats: convertTreeDBStats(s.notehashtreestats),
156
+ messageTreeStats: convertTreeDBStats(s.messagetreestats),
157
+ archiveTreeStats: convertTreeDBStats(s.archivetreestats),
158
+ publicDataTreeStats: convertTreeDBStats(s.publicdatatreestats),
159
+ nullifierTreeStats: convertTreeDBStats(s.nullifiertreestats),
286
160
  } as unknown as WorldStateDBStats;
287
161
  }
288
162
 
@@ -291,37 +165,37 @@ function convertTreeMeta(m: WsdbTreeMeta): TreeMeta {
291
165
  name: m.name,
292
166
  depth: m.depth,
293
167
  size: m.size,
294
- committedSize: m.committedSize,
168
+ committedSize: m.committedsize,
295
169
  root: m.root,
296
- initialSize: m.initialSize,
297
- initialRoot: m.initialRoot,
298
- oldestHistoricBlock: m.oldestHistoricBlock,
299
- unfinalizedBlockHeight: m.unfinalizedBlockHeight,
300
- finalizedBlockHeight: m.finalizedBlockHeight,
170
+ initialSize: m.initialsize,
171
+ initialRoot: m.initialroot,
172
+ oldestHistoricBlock: m.oldesthistoricblock,
173
+ unfinalizedBlockHeight: m.unfinalizedblockheight,
174
+ finalizedBlockHeight: m.finalizedblockheight,
301
175
  } as unknown as TreeMeta;
302
176
  }
303
177
 
304
178
  function convertWorldStateMeta(m: WsdbMeta): WorldStateMeta {
305
179
  return {
306
- noteHashTreeMeta: convertTreeMeta(m.noteHashTreeMeta),
307
- messageTreeMeta: convertTreeMeta(m.messageTreeMeta),
308
- archiveTreeMeta: convertTreeMeta(m.archiveTreeMeta),
309
- publicDataTreeMeta: convertTreeMeta(m.publicDataTreeMeta),
310
- nullifierTreeMeta: convertTreeMeta(m.nullifierTreeMeta),
180
+ noteHashTreeMeta: convertTreeMeta(m.notehashtreemeta),
181
+ messageTreeMeta: convertTreeMeta(m.messagetreemeta),
182
+ archiveTreeMeta: convertTreeMeta(m.archivetreemeta),
183
+ publicDataTreeMeta: convertTreeMeta(m.publicdatatreemeta),
184
+ nullifierTreeMeta: convertTreeMeta(m.nullifiertreemeta),
311
185
  } as unknown as WorldStateMeta;
312
186
  }
313
187
 
314
188
  function convertStatusFull(s: WsdbStatusFull): WorldStateStatusFull {
315
189
  return {
316
190
  summary: convertStatusSummary(s.summary),
317
- dbStats: convertWorldStateDBStats(s.dbStats),
191
+ dbStats: convertWorldStateDBStats(s.dbstats),
318
192
  meta: convertWorldStateMeta(s.meta),
319
193
  } as unknown as WorldStateStatusFull;
320
194
  }
321
195
 
322
196
  /** Convert Wsdb SiblingPathAndIndex to NAPI format. */
323
197
  function convertSiblingPathAndIndex(
324
- s: WsdbSiblingPathAndIndex | null | undefined,
198
+ s: WsdbSiblingPathAndIndex | undefined,
325
199
  ): { index: bigint; path: Buffer[] } | undefined {
326
200
  if (!s) {
327
201
  return undefined;
@@ -334,50 +208,108 @@ function convertSiblingPathAndIndex(
334
208
 
335
209
  // ————— Public API —————
336
210
 
211
+ /** Backend interface matching WsdbBackend from bb.js. */
212
+ export interface WsdbIpcBackend {
213
+ call(inputBuffer: Uint8Array): Promise<Uint8Array>;
214
+ getSocketPath(): string;
215
+ destroy?(): Promise<void>;
216
+ }
217
+
337
218
  /**
338
219
  * IPC-backed world state instance.
339
- * Uses WsdbService (spawns aztec-wsdb binary) and the generated AsyncApi
220
+ * Uses WsdbBackend (spawns aztec-wsdb binary) and the generated AsyncApi
340
221
  * to communicate via the NamedUnion IPC protocol.
341
222
  */
342
223
  export class IpcWorldState implements NativeWorldStateInstance {
343
224
  private open = true;
225
+ private queues = new Map<number, WorldStateOpsQueue>();
344
226
  private api: AsyncApi;
345
227
  /** Tracks checkpoint depth per fork (WSDB IPC doesn't return depth in response). */
346
228
  private checkpointDepths = new Map<number, number>();
347
229
 
348
230
  constructor(
349
- private readonly wsdb: WsdbService,
231
+ private readonly wsdbBackend: WsdbIpcBackend,
350
232
  private readonly instrumentation: WorldStateInstrumentation,
351
233
  bindings?: LoggerBindings,
352
234
  private readonly log: Logger = createLogger('world-state:ipc-database', bindings),
353
235
  ) {
354
- this.api = wsdb;
236
+ this.api = new AsyncApi(wsdbBackend as any);
237
+ this.queues.set(0, new WorldStateOpsQueue());
355
238
  this.log.info('Created IPC-backed world state instance');
356
239
  }
357
240
 
241
+ /** Returns the socket path of the underlying wsdb server. */
242
+ getSocketPath(): string {
243
+ return this.wsdbBackend.getSocketPath();
244
+ }
245
+
358
246
  /**
359
- * Spawn an `aztec-wsdb` subprocess and return an IPC-backed world state wrapping it.
360
- * Encapsulates wsdb binary discovery, service construction, and readiness wait.
247
+ * Required by `NativeWorldStateInstance` for compatibility with the in-process
248
+ * NAPI path. The IPC backend does not expose an in-process pointer; callers that
249
+ * need to reach the WSDB process must use {@link getSocketPath} instead.
361
250
  */
362
- static async spawn(
363
- dataDir: string,
364
- wsTreeMapSizes: WorldStateTreeMapSizes,
365
- genesis: GenesisData,
366
- instrumentation: WorldStateInstrumentation,
367
- bindings?: LoggerBindings,
368
- ): Promise<IpcWorldState> {
369
- const threads = getWsdbThreadCount();
370
- const transport = process.env.WSDB_TRANSPORT === 'shm' ? 'shm' : 'uds';
371
- const wsdb = await WsdbService.spawn({
372
- transport,
373
- extraArgs: getWsdbExtraArgs(dataDir, wsTreeMapSizes, genesis, threads),
374
- env: { HARDWARE_CONCURRENCY: threads.toString() },
375
- });
376
- return new IpcWorldState(wsdb, instrumentation, bindings);
377
- }
251
+ getHandle(): any {
252
+ throw new Error('IpcWorldState has no in-process handle; use getSocketPath() instead');
253
+ }
254
+
255
+ async call<T extends WorldStateMessageType>(
256
+ messageType: T,
257
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
258
+ responseHandler = (response: WorldStateResponse[T]): WorldStateResponse[T] => response,
259
+ errorHandler = (_: string) => {},
260
+ ): Promise<WorldStateResponse[T]> {
261
+ let forkId = -1;
262
+ let committedOnly = false;
263
+
264
+ if (isWithCanonical(body)) {
265
+ forkId = 0;
266
+ } else if (isWithForkId(body)) {
267
+ forkId = body.forkId;
268
+ } else if (isWithRevision(body)) {
269
+ forkId = body.revision.forkId;
270
+ committedOnly = body.revision.includeUncommitted === false;
271
+ } else {
272
+ const _: never = body;
273
+ throw new Error(`Unable to determine forkId for message=${WorldStateMessageType[messageType]}`);
274
+ }
275
+
276
+ let requestQueue = this.queues.get(forkId);
277
+ if (requestQueue === undefined) {
278
+ requestQueue = new WorldStateOpsQueue();
279
+ this.queues.set(forkId, requestQueue);
280
+ }
378
281
 
379
- getIpcPath(): string {
380
- return this.wsdb.getIpcPath();
282
+ // The per-fork queue is cleaned up in `finally` even on error, so the JS-side queues map cannot outlive
283
+ // the native fork (e.g. when the native fork was already destroyed by an unwind/historical-prune and
284
+ // DELETE_FORK rejects with "Fork not found").
285
+ let shouldDeleteForkQueue = false;
286
+ try {
287
+ const response = await requestQueue.execute(
288
+ async () => {
289
+ assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the IPC instance');
290
+ assert.equal(this.open, true, 'IPC instance is closed');
291
+ let response: WorldStateResponse[T];
292
+ try {
293
+ response = await this._sendMessage(messageType, body);
294
+ } catch (error: any) {
295
+ errorHandler(error.message);
296
+ throw error;
297
+ }
298
+ return responseHandler(response);
299
+ },
300
+ messageType,
301
+ committedOnly,
302
+ );
303
+ return response;
304
+ } catch (err: any) {
305
+ shouldDeleteForkQueue = forkId !== 0 && err?.message === 'Fork not found';
306
+ throw err;
307
+ } finally {
308
+ if (messageType === WorldStateMessageType.DELETE_FORK || shouldDeleteForkQueue) {
309
+ await requestQueue.stop();
310
+ this.queues.delete(forkId);
311
+ }
312
+ }
381
313
  }
382
314
 
383
315
  async close(): Promise<void> {
@@ -385,417 +317,380 @@ export class IpcWorldState implements NativeWorldStateInstance {
385
317
  return;
386
318
  }
387
319
  this.open = false;
388
- await this.wsdb.destroy();
389
- }
320
+ const queue = this.queues.get(0)!;
390
321
 
391
- getTreeInfo(treeId: MerkleTreeId, revision: WorldStateRevision): Promise<TreeInfo> {
392
- return this.execute('getTreeInfo', revision.forkId, revision.includeUncommitted === false, async () => {
393
- const resp = await this.api.getTreeInfo({ treeId, revision: toWsdbRevision(revision) });
394
- return {
395
- treeId: resp.treeId,
396
- root: Buffer.from(resp.root),
397
- size: BigInt(resp.size),
398
- depth: resp.depth,
399
- };
400
- });
401
- }
322
+ // Send shutdown command. Under normal operation, the WSDB process sends its
323
+ // response before exiting (via ShutdownRequested in ipc_server.hpp). The
324
+ // try/catch is defensive: if the process is killed externally (SIGKILL, OOM)
325
+ // before responding, the pending IPC callback would be rejected by the socket
326
+ // close handler. We proceed to destroy the backend regardless.
327
+ try {
328
+ await queue.execute(
329
+ async () => {
330
+ await this.api.wsdbShutdown({});
331
+ },
332
+ WorldStateMessageType.CLOSE,
333
+ false,
334
+ );
335
+ } catch (err: any) {
336
+ this.log.debug(`wsdbShutdown completed with error: ${err.message}`);
337
+ }
338
+ await queue.stop();
402
339
 
403
- getStateReference(revision: WorldStateRevision): Promise<Record<number, TreeStateReference>> {
404
- return this.execute('getStateReference', revision.forkId, revision.includeUncommitted === false, async () => {
405
- const resp = await this.api.getStateReference({ revision: toWsdbRevision(revision) });
406
- return convertStateRef(resp.state);
407
- });
340
+ if (this.wsdbBackend.destroy) {
341
+ await this.wsdbBackend.destroy();
342
+ }
408
343
  }
409
344
 
410
- getInitialStateReference(): Promise<Record<number, TreeStateReference>> {
411
- return this.execute('getInitialStateReference', 0, false, async () => {
412
- const resp = await this.api.getInitialStateReference({});
413
- return convertStateRef(resp.state);
414
- });
345
+ private async _sendMessage<T extends WorldStateMessageType>(
346
+ messageType: T,
347
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
348
+ ): Promise<WorldStateResponse[T]> {
349
+ const start = performance.now();
350
+ try {
351
+ const response = await this.dispatch(messageType, body);
352
+ const durationMs = performance.now() - start;
353
+ this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, { duration: durationMs });
354
+ this.instrumentation.recordRoundTrip(durationMs * 1000, messageType);
355
+ return response;
356
+ } catch (error) {
357
+ this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error);
358
+ throw error;
359
+ }
415
360
  }
416
361
 
417
- getLeafValue(
418
- treeId: MerkleTreeId,
419
- revision: WorldStateRevision,
420
- leafIndex: bigint,
421
- ): Promise<SerializedLeafValue | undefined> {
422
- return this.execute('getLeafValue', revision.forkId, revision.includeUncommitted === false, async () => {
423
- const wsdbRevision = toWsdbRevision(revision);
424
- const wsdbLeafIndex = Number(leafIndex);
425
-
426
- if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
427
- const resp = await this.api.getPublicDataLeafValue({ revision: wsdbRevision, leafIndex: wsdbLeafIndex });
428
- return resp.value ? fromPublicDataLeaf(resp.value) : undefined;
362
+ private async dispatch<T extends WorldStateMessageType>(
363
+ messageType: T,
364
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
365
+ ): Promise<WorldStateResponse[T]> {
366
+ switch (messageType) {
367
+ // ——— Tree info & state reference ———
368
+
369
+ case WorldStateMessageType.GET_TREE_INFO: {
370
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_TREE_INFO];
371
+ const resp = await this.api.wsdbGetTreeInfo({
372
+ treeid: b.treeId,
373
+ revision: toWsdbRevision(b.revision),
374
+ });
375
+ return {
376
+ treeId: resp.treeid,
377
+ root: Buffer.from(resp.root),
378
+ size: resp.size,
379
+ depth: resp.depth,
380
+ } as WorldStateResponse[T];
429
381
  }
430
382
 
431
- if (treeId === MerkleTreeId.NULLIFIER_TREE) {
432
- const resp = await this.api.getNullifierLeafValue({ revision: wsdbRevision, leafIndex: wsdbLeafIndex });
433
- return resp.value ? fromNullifierLeaf(resp.value) : undefined;
383
+ case WorldStateMessageType.GET_STATE_REFERENCE: {
384
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_STATE_REFERENCE];
385
+ const resp = await this.api.wsdbGetStateReference({
386
+ revision: toWsdbRevision(b.revision),
387
+ });
388
+ return { state: convertStateRef(resp.state) } as WorldStateResponse[T];
434
389
  }
435
390
 
436
- const resp = await this.api.getLeafValue({ treeId, revision: wsdbRevision, leafIndex: wsdbLeafIndex });
437
- return resp.value ? Buffer.from(resp.value) : undefined;
438
- });
439
- }
391
+ case WorldStateMessageType.GET_INITIAL_STATE_REFERENCE: {
392
+ const resp = await this.api.wsdbGetInitialStateReference({});
393
+ return { state: convertStateRef(resp.state) } as WorldStateResponse[T];
394
+ }
440
395
 
441
- getLeafPreimage(
442
- treeId: IndexedTreeId,
443
- revision: WorldStateRevision,
444
- leafIndex: bigint,
445
- ): Promise<SerializedIndexedLeaf | undefined> {
446
- return this.execute('getLeafPreimage', revision.forkId, revision.includeUncommitted === false, async () => {
447
- const resp =
448
- treeId === MerkleTreeId.PUBLIC_DATA_TREE
449
- ? await this.api.getPublicDataLeafPreimage({
450
- revision: toWsdbRevision(revision),
451
- leafIndex: Number(leafIndex),
452
- })
453
- : await this.api.getNullifierLeafPreimage({
454
- revision: toWsdbRevision(revision),
455
- leafIndex: Number(leafIndex),
456
- });
457
- return resp.preimage ? (convertUint8ArraysToBuffers(resp.preimage) as SerializedIndexedLeaf) : undefined;
458
- });
459
- }
396
+ // ——— Leaf queries ———
460
397
 
461
- getSiblingPath(treeId: MerkleTreeId, revision: WorldStateRevision, leafIndex: bigint): Promise<Buffer[]> {
462
- return this.execute('getSiblingPath', revision.forkId, revision.includeUncommitted === false, async () => {
463
- const resp = await this.api.getSiblingPath({
464
- treeId,
465
- revision: toWsdbRevision(revision),
466
- leafIndex: Number(leafIndex),
467
- });
468
- return resp.path.map(p => Buffer.from(p));
469
- });
470
- }
398
+ case WorldStateMessageType.GET_LEAF_VALUE: {
399
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_LEAF_VALUE];
400
+ const resp = await this.api.wsdbGetLeafValue({
401
+ treeid: b.treeId,
402
+ revision: toWsdbRevision(b.revision),
403
+ leafindex: Number(b.leafIndex),
404
+ });
405
+ if (!resp.value) {
406
+ return undefined as WorldStateResponse[T];
407
+ }
408
+ return decodeLeafValue(resp.value) as WorldStateResponse[T];
409
+ }
471
410
 
472
- getBlockNumbersForLeafIndices(
473
- treeId: MerkleTreeId,
474
- revision: WorldStateRevision,
475
- leafIndices: bigint[],
476
- ): Promise<(bigint | undefined)[]> {
477
- return this.execute(
478
- 'getBlockNumbersForLeafIndices',
479
- revision.forkId,
480
- revision.includeUncommitted === false,
481
- async () => {
482
- const resp = await this.api.getBlockNumbersForLeafIndices({
483
- treeId,
484
- revision: toWsdbRevision(revision),
485
- leafIndices: leafIndices.map(Number),
411
+ case WorldStateMessageType.GET_LEAF_PREIMAGE: {
412
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_LEAF_PREIMAGE];
413
+ const resp = await this.api.wsdbGetLeafPreimage({
414
+ treeid: b.treeId,
415
+ revision: toWsdbRevision(b.revision),
416
+ leafindex: Number(b.leafIndex),
486
417
  });
487
- return resp.blockNumbers.map(n => (n != null ? BigInt(n) : undefined));
488
- },
489
- );
490
- }
418
+ if (!resp.preimage) {
419
+ return undefined as WorldStateResponse[T];
420
+ }
421
+ return decodeLeafPreimage(resp.preimage) as WorldStateResponse[T];
422
+ }
491
423
 
492
- findLeafIndices(
493
- treeId: MerkleTreeId,
494
- revision: WorldStateRevision,
495
- leaves: SerializedLeafValue[],
496
- startIndex: bigint,
497
- ): Promise<(bigint | undefined)[]> {
498
- return this.execute('findLeafIndices', revision.forkId, revision.includeUncommitted === false, async () => {
499
- const wsdbRevision = toWsdbRevision(revision);
500
- const wsdbStartIndex = Number(startIndex);
501
- const resp =
502
- treeId === MerkleTreeId.PUBLIC_DATA_TREE
503
- ? await this.api.findPublicDataLeafIndices({
504
- revision: wsdbRevision,
505
- leaves: leaves.map(toPublicDataLeaf),
506
- startIndex: wsdbStartIndex,
507
- })
508
- : treeId === MerkleTreeId.NULLIFIER_TREE
509
- ? await this.api.findNullifierLeafIndices({
510
- revision: wsdbRevision,
511
- leaves: leaves.map(toNullifierLeaf),
512
- startIndex: wsdbStartIndex,
513
- })
514
- : await this.api.findLeafIndices({
515
- treeId,
516
- revision: wsdbRevision,
517
- leaves: leaves.map(toFrLeaf),
518
- startIndex: wsdbStartIndex,
519
- });
520
- return resp.indices.map(n => (n != null ? BigInt(n) : undefined));
521
- });
522
- }
424
+ case WorldStateMessageType.GET_SIBLING_PATH: {
425
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_SIBLING_PATH];
426
+ const resp = await this.api.wsdbGetSiblingPath({
427
+ treeid: b.treeId,
428
+ revision: toWsdbRevision(b.revision),
429
+ leafindex: Number(b.leafIndex),
430
+ });
431
+ return resp.path.map(p => Buffer.from(p)) as WorldStateResponse[T];
432
+ }
523
433
 
524
- findLowLeaf(
525
- treeId: IndexedTreeId,
526
- revision: WorldStateRevision,
527
- key: Fr,
528
- ): Promise<{ index: bigint; alreadyPresent: boolean }> {
529
- return this.execute('findLowLeaf', revision.forkId, revision.includeUncommitted === false, async () => {
530
- const resp = await this.api.findLowLeaf({
531
- treeId,
532
- revision: toWsdbRevision(revision),
533
- key: new Uint8Array(key.toBuffer()),
534
- });
535
- return {
536
- alreadyPresent: resp.alreadyPresent,
537
- index: BigInt(resp.index),
538
- };
539
- });
540
- }
434
+ case WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES: {
435
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES];
436
+ const resp = await this.api.wsdbGetBlockNumbersForLeafIndices({
437
+ treeid: b.treeId,
438
+ revision: toWsdbRevision(b.revision),
439
+ leafindices: b.leafIndices.map(Number),
440
+ });
441
+ return {
442
+ blockNumbers: resp.blocknumbers.map(n => (n != null ? BigInt(n) : undefined)),
443
+ } as WorldStateResponse[T];
444
+ }
541
445
 
542
- findSiblingPaths(
543
- treeId: MerkleTreeId,
544
- revision: WorldStateRevision,
545
- leaves: SerializedLeafValue[],
546
- ): Promise<(SerializedSiblingPathAndIndex | undefined)[]> {
547
- return this.execute('findSiblingPaths', revision.forkId, revision.includeUncommitted === false, async () => {
548
- const wsdbRevision = toWsdbRevision(revision);
549
- const resp =
550
- treeId === MerkleTreeId.PUBLIC_DATA_TREE
551
- ? await this.api.findPublicDataSiblingPaths({
552
- revision: wsdbRevision,
553
- leaves: leaves.map(toPublicDataLeaf),
554
- })
555
- : treeId === MerkleTreeId.NULLIFIER_TREE
556
- ? await this.api.findNullifierSiblingPaths({
557
- revision: wsdbRevision,
558
- leaves: leaves.map(toNullifierLeaf),
559
- })
560
- : await this.api.findSiblingPaths({
561
- treeId,
562
- revision: wsdbRevision,
563
- leaves: leaves.map(toFrLeaf),
564
- });
565
- return resp.paths.map(convertSiblingPathAndIndex);
566
- });
567
- }
446
+ // ——— Find operations ———
568
447
 
569
- updateArchive(forkId: number, blockStateRef: BlockStateReference, blockHeaderHash: Buffer): Promise<void> {
570
- return this.execute('updateArchive', forkId, false, async () => {
571
- await this.api.updateArchive({
572
- blockStateRef: blockStateRefToMap(blockStateRef),
573
- blockHeaderHash: new Uint8Array(blockHeaderHash),
574
- forkId,
575
- });
576
- });
577
- }
448
+ case WorldStateMessageType.FIND_LEAF_INDICES: {
449
+ const b = body as WorldStateRequest[WorldStateMessageType.FIND_LEAF_INDICES];
450
+ const resp = await this.api.wsdbFindLeafIndices({
451
+ treeid: b.treeId,
452
+ revision: toWsdbRevision(b.revision),
453
+ leaves: b.leaves.map(serializeLeafToBytes),
454
+ startindex: Number(b.startIndex),
455
+ });
456
+ return {
457
+ indices: resp.indices.map(n => (n != null ? BigInt(n) : undefined)),
458
+ } as WorldStateResponse[T];
459
+ }
578
460
 
579
- appendLeaves(treeId: MerkleTreeId, forkId: number, leaves: SerializedLeafValue[]): Promise<void> {
580
- return this.execute('appendLeaves', forkId, false, async () => {
581
- if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
582
- await this.api.appendPublicDataLeaves({ leaves: leaves.map(toPublicDataLeaf), forkId });
583
- } else if (treeId === MerkleTreeId.NULLIFIER_TREE) {
584
- await this.api.appendNullifierLeaves({ leaves: leaves.map(toNullifierLeaf), forkId });
585
- } else {
586
- await this.api.appendLeaves({ treeId, leaves: leaves.map(toFrLeaf), forkId });
461
+ case WorldStateMessageType.FIND_LOW_LEAF: {
462
+ const b = body as WorldStateRequest[WorldStateMessageType.FIND_LOW_LEAF];
463
+ const resp = await this.api.wsdbFindLowLeaf({
464
+ treeid: b.treeId,
465
+ revision: toWsdbRevision(b.revision),
466
+ key: new Uint8Array(b.key.toBuffer()),
467
+ });
468
+ return {
469
+ alreadyPresent: resp.alreadypresent,
470
+ index: BigInt(resp.index),
471
+ } as WorldStateResponse[T];
587
472
  }
588
- });
589
- }
590
473
 
591
- batchInsert(
592
- treeId: IndexedTreeId,
593
- forkId: number,
594
- leaves: SerializedLeafValue[],
595
- subtreeDepth: number,
596
- ): Promise<SerializedBatchInsertionResult> {
597
- return this.execute('batchInsert', forkId, false, async () => {
598
- const resp =
599
- treeId === MerkleTreeId.PUBLIC_DATA_TREE
600
- ? await this.api.batchInsertPublicData({
601
- leaves: leaves.map(toPublicDataLeaf),
602
- subtreeDepth,
603
- forkId,
604
- })
605
- : await this.api.batchInsertNullifier({
606
- leaves: leaves.map(toNullifierLeaf),
607
- subtreeDepth,
608
- forkId,
609
- });
610
- return treeId === MerkleTreeId.PUBLIC_DATA_TREE
611
- ? fromBatchInsertionResult(resp.result as any, fromPublicDataLeaf)
612
- : fromBatchInsertionResult(resp.result as any, fromNullifierLeaf);
613
- });
614
- }
474
+ case WorldStateMessageType.FIND_SIBLING_PATHS: {
475
+ const b = body as WorldStateRequest[WorldStateMessageType.FIND_SIBLING_PATHS];
476
+ const resp = await this.api.wsdbFindSiblingPaths({
477
+ treeid: b.treeId,
478
+ revision: toWsdbRevision(b.revision),
479
+ leaves: b.leaves.map(serializeLeafToBytes),
480
+ });
481
+ return {
482
+ paths: resp.paths.map(convertSiblingPathAndIndex),
483
+ } as WorldStateResponse[T];
484
+ }
615
485
 
616
- sequentialInsert(
617
- treeId: IndexedTreeId,
618
- forkId: number,
619
- leaves: SerializedLeafValue[],
620
- ): Promise<SerializedSequentialInsertionResult> {
621
- return this.execute('sequentialInsert', forkId, false, async () => {
622
- const resp =
623
- treeId === MerkleTreeId.PUBLIC_DATA_TREE
624
- ? await this.api.sequentialInsertPublicData({
625
- leaves: leaves.map(toPublicDataLeaf),
626
- forkId,
627
- })
628
- : await this.api.sequentialInsertNullifier({
629
- leaves: leaves.map(toNullifierLeaf),
630
- forkId,
631
- });
632
- return treeId === MerkleTreeId.PUBLIC_DATA_TREE
633
- ? fromSequentialInsertionResult(resp.result as any, fromPublicDataLeaf)
634
- : fromSequentialInsertionResult(resp.result as any, fromNullifierLeaf);
635
- });
636
- }
486
+ // ——— Mutations ———
637
487
 
638
- syncBlock(input: {
639
- blockNumber: BlockNumber;
640
- blockStateRef: BlockStateReference;
641
- blockHeaderHash: Buffer;
642
- paddedNoteHashes: readonly SerializedLeafValue[];
643
- paddedL1ToL2Messages: readonly SerializedLeafValue[];
644
- paddedNullifiers: readonly SerializedLeafValue[];
645
- publicDataWrites: readonly SerializedLeafValue[];
646
- }): Promise<WorldStateStatusFull> {
647
- return this.execute('syncBlock', 0, false, async () => {
648
- const resp = await this.api.syncBlock({
649
- blockNumber: Number(input.blockNumber),
650
- blockStateRef: blockStateRefToMap(input.blockStateRef),
651
- blockHeaderHash: new Uint8Array(input.blockHeaderHash),
652
- paddedNoteHashes: input.paddedNoteHashes.map(toFrLeaf),
653
- paddedL1ToL2Messages: input.paddedL1ToL2Messages.map(toFrLeaf),
654
- paddedNullifiers: input.paddedNullifiers.map(toNullifierLeaf),
655
- publicDataWrites: input.publicDataWrites.map(toPublicDataLeaf),
656
- });
657
- return convertStatusFull(resp.status);
658
- });
659
- }
488
+ case WorldStateMessageType.APPEND_LEAVES: {
489
+ const b = body as WorldStateRequest[WorldStateMessageType.APPEND_LEAVES];
490
+ await this.api.wsdbAppendLeaves({
491
+ treeid: b.treeId,
492
+ leaves: b.leaves.map(serializeLeafToBytes),
493
+ forkid: b.forkId,
494
+ });
495
+ return undefined as WorldStateResponse[T];
496
+ }
660
497
 
661
- createFork(input: { latest: boolean; blockNumber: BlockNumber }): Promise<number> {
662
- return this.execute('createFork', 0, false, async () => {
663
- const resp = await this.api.createFork({
664
- latest: input.latest,
665
- blockNumber: Number(input.blockNumber),
666
- });
667
- return resp.forkId;
668
- });
669
- }
498
+ case WorldStateMessageType.BATCH_INSERT: {
499
+ const b = body as WorldStateRequest[WorldStateMessageType.BATCH_INSERT];
500
+ const resp = await this.api.wsdbBatchInsert({
501
+ treeid: b.treeId,
502
+ leaves: b.leaves.map(serializeLeafToBytes),
503
+ subtreedepth: b.subtreeDepth,
504
+ forkid: b.forkId,
505
+ });
506
+ const decoded = msgpackDecoder.unpack(Buffer.from(resp.result));
507
+ return convertUint8ArraysToBuffers(decoded) as WorldStateResponse[T];
508
+ }
670
509
 
671
- deleteFork(forkId: number): Promise<void> {
672
- return this.execute('deleteFork', forkId, false, async () => {
673
- await this.api.deleteFork({ forkId });
674
- });
675
- }
510
+ case WorldStateMessageType.SEQUENTIAL_INSERT: {
511
+ const b = body as WorldStateRequest[WorldStateMessageType.SEQUENTIAL_INSERT];
512
+ const resp = await this.api.wsdbSequentialInsert({
513
+ treeid: b.treeId,
514
+ leaves: b.leaves.map(serializeLeafToBytes),
515
+ forkid: b.forkId,
516
+ });
517
+ const decoded = msgpackDecoder.unpack(Buffer.from(resp.result));
518
+ return convertUint8ArraysToBuffers(decoded) as WorldStateResponse[T];
519
+ }
676
520
 
677
- finalizeBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusSummary> {
678
- return this.execute('finalizeBlocks', 0, false, async () => {
679
- const resp = await this.api.finalizeBlocks({ toBlockNumber: Number(toBlockNumber) });
680
- return convertStatusSummary(resp.status);
681
- });
682
- }
521
+ case WorldStateMessageType.UPDATE_ARCHIVE: {
522
+ const b = body as WorldStateRequest[WorldStateMessageType.UPDATE_ARCHIVE];
523
+ await this.api.wsdbUpdateArchive({
524
+ blockstateref: blockStateRefToMap(b.blockStateRef as Map<number, readonly [Buffer, number | bigint]>) as any,
525
+ blockheaderhash: new Uint8Array(b.blockHeaderHash),
526
+ forkid: b.forkId,
527
+ });
528
+ return undefined as WorldStateResponse[T];
529
+ }
683
530
 
684
- unwindBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusFull> {
685
- return this.execute('unwindBlocks', 0, false, async () => {
686
- const resp = await this.api.unwindBlocks({ toBlockNumber: Number(toBlockNumber) });
687
- return convertStatusFull(resp.status);
688
- });
689
- }
531
+ // ——— Commit / Rollback ———
690
532
 
691
- removeHistoricalBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusFull> {
692
- return this.execute('removeHistoricalBlocks', 0, false, async () => {
693
- const resp = await this.api.removeHistoricalBlocks({ toBlockNumber: Number(toBlockNumber) });
694
- return convertStatusFull(resp.status);
695
- });
696
- }
533
+ case WorldStateMessageType.COMMIT: {
534
+ await this.api.wsdbCommit({});
535
+ return undefined as WorldStateResponse[T];
536
+ }
697
537
 
698
- getStatus(): Promise<WorldStateStatusSummary> {
699
- return this.execute('getStatus', 0, false, async () => {
700
- const resp = await this.api.getStatus({});
701
- return convertStatusSummary(resp.status);
702
- });
703
- }
538
+ case WorldStateMessageType.ROLLBACK: {
539
+ await this.api.wsdbRollback({});
540
+ return undefined as WorldStateResponse[T];
541
+ }
704
542
 
705
- createCheckpoint(forkId: number): Promise<number> {
706
- return this.execute('createCheckpoint', forkId, false, async () => {
707
- await this.api.createCheckpoint({ forkId });
708
- const depth = (this.checkpointDepths.get(forkId) ?? 0) + 1;
709
- this.checkpointDepths.set(forkId, depth);
710
- return depth;
711
- });
712
- }
543
+ // ——— Block sync ———
544
+
545
+ case WorldStateMessageType.SYNC_BLOCK: {
546
+ const b = body as WorldStateRequest[WorldStateMessageType.SYNC_BLOCK];
547
+ const resp = await this.api.wsdbSyncBlock({
548
+ blocknumber: Number(b.blockNumber),
549
+ blockstateref: blockStateRefToMap(b.blockStateRef as Map<number, readonly [Buffer, number | bigint]>) as any,
550
+ blockheaderhash: new Uint8Array(b.blockHeaderHash),
551
+ // Forwarded so the wsdb (IPC) sync path enforces the same archive-root divergence check as the napi path.
552
+ expectedarchiveroot: new Uint8Array(b.expectedArchiveRoot),
553
+ expectedpreviousarchiveroot: new Uint8Array(b.expectedPreviousArchiveRoot),
554
+ paddednotehashes: b.paddedNoteHashes.map(l => new Uint8Array(l as Buffer)),
555
+ paddedl1tol2messages: b.paddedL1ToL2Messages.map(l => new Uint8Array(l as Buffer)),
556
+ paddednullifiers: b.paddedNullifiers.map(l => ({
557
+ nullifier: new Uint8Array((l as { nullifier: Buffer }).nullifier),
558
+ })),
559
+ publicdatawrites: b.publicDataWrites.map(l => ({
560
+ slot: new Uint8Array((l as { slot: Buffer; value: Buffer }).slot),
561
+ value: new Uint8Array((l as { slot: Buffer; value: Buffer }).value),
562
+ })),
563
+ });
713
564
 
714
- commitCheckpoint(forkId: number): Promise<void> {
715
- return this.execute('commitCheckpoint', forkId, false, async () => {
716
- await this.api.commitCheckpoint({ forkId });
717
- const depth = Math.max(0, (this.checkpointDepths.get(forkId) ?? 0) - 1);
718
- this.checkpointDepths.set(forkId, depth);
719
- });
720
- }
565
+ return convertStatusFull(resp.status) as WorldStateResponse[T];
566
+ }
721
567
 
722
- revertCheckpoint(forkId: number): Promise<void> {
723
- return this.execute('revertCheckpoint', forkId, false, async () => {
724
- await this.api.revertCheckpoint({ forkId });
725
- const depth = Math.max(0, (this.checkpointDepths.get(forkId) ?? 0) - 1);
726
- this.checkpointDepths.set(forkId, depth);
727
- });
728
- }
568
+ // ——— Fork management ———
569
+
570
+ case WorldStateMessageType.CREATE_FORK: {
571
+ const b = body as WorldStateRequest[WorldStateMessageType.CREATE_FORK];
572
+ const resp = await this.api.wsdbCreateFork({
573
+ latest: b.latest,
574
+ blocknumber: Number(b.blockNumber),
575
+ });
576
+ return { forkId: resp.forkid } as WorldStateResponse[T];
577
+ }
578
+
579
+ case WorldStateMessageType.DELETE_FORK: {
580
+ const b = body as WorldStateRequest[WorldStateMessageType.DELETE_FORK];
581
+ await this.api.wsdbDeleteFork({ forkid: b.forkId });
582
+ return undefined as WorldStateResponse[T];
583
+ }
584
+
585
+ // ——— Block finalization ———
586
+
587
+ case WorldStateMessageType.FINALIZE_BLOCKS: {
588
+ const b = body as WorldStateRequest[WorldStateMessageType.FINALIZE_BLOCKS];
589
+ const resp = await this.api.wsdbFinalizeBlocks({ toblocknumber: Number(b.toBlockNumber) });
590
+ return convertStatusSummary(resp.status) as WorldStateResponse[T];
591
+ }
592
+
593
+ case WorldStateMessageType.UNWIND_BLOCKS: {
594
+ const b = body as WorldStateRequest[WorldStateMessageType.UNWIND_BLOCKS];
595
+ const resp = await this.api.wsdbUnwindBlocks({ toblocknumber: Number(b.toBlockNumber) });
596
+ return convertStatusFull(resp.status) as WorldStateResponse[T];
597
+ }
598
+
599
+ case WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS: {
600
+ const b = body as WorldStateRequest[WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS];
601
+ const resp = await this.api.wsdbRemoveHistoricalBlocks({ toblocknumber: Number(b.toBlockNumber) });
602
+ return convertStatusFull(resp.status) as WorldStateResponse[T];
603
+ }
604
+
605
+ // ——— Status ———
606
+
607
+ case WorldStateMessageType.GET_STATUS: {
608
+ const resp = await this.api.wsdbGetStatus({});
609
+ return convertStatusSummary(resp.status) as WorldStateResponse[T];
610
+ }
611
+
612
+ // ——— Checkpoints ———
613
+
614
+ case WorldStateMessageType.CREATE_CHECKPOINT: {
615
+ const b = body as WorldStateRequest[WorldStateMessageType.CREATE_CHECKPOINT];
616
+ await this.api.wsdbCreateCheckpoint({ forkid: b.forkId });
617
+ const depth = (this.checkpointDepths.get(b.forkId) ?? 0) + 1;
618
+ this.checkpointDepths.set(b.forkId, depth);
619
+ return { depth } as WorldStateResponse[T];
620
+ }
621
+
622
+ case WorldStateMessageType.COMMIT_CHECKPOINT: {
623
+ const b = body as WorldStateRequest[WorldStateMessageType.COMMIT_CHECKPOINT];
624
+ await this.api.wsdbCommitCheckpoint({ forkid: b.forkId });
625
+ const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
626
+ this.checkpointDepths.set(b.forkId, depth);
627
+ return undefined as WorldStateResponse[T];
628
+ }
729
629
 
730
- commitAllCheckpoints(forkId: number, depth: number): Promise<void> {
731
- return this.execute('commitAllCheckpoints', forkId, false, async () => {
732
- const currentDepth = this.checkpointDepths.get(forkId) ?? 0;
733
- if (depth === 0) {
734
- await this.api.commitAllCheckpoints({ forkId });
735
- } else {
736
- for (let d = currentDepth; d > depth; d--) {
737
- await this.api.commitCheckpoint({ forkId });
630
+ case WorldStateMessageType.REVERT_CHECKPOINT: {
631
+ const b = body as WorldStateRequest[WorldStateMessageType.REVERT_CHECKPOINT];
632
+ await this.api.wsdbRevertCheckpoint({ forkid: b.forkId });
633
+ const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
634
+ this.checkpointDepths.set(b.forkId, depth);
635
+ return undefined as WorldStateResponse[T];
636
+ }
637
+
638
+ case WorldStateMessageType.COMMIT_ALL_CHECKPOINTS: {
639
+ const b = body as WorldStateRequest[WorldStateMessageType.COMMIT_ALL_CHECKPOINTS];
640
+ const targetDepth = b.depth ?? 0;
641
+ const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
642
+ if (targetDepth === 0) {
643
+ // Commit everything — use the bulk operation
644
+ await this.api.wsdbCommitAllCheckpoints({ forkid: b.forkId });
645
+ } else {
646
+ // Commit one level at a time down to target depth
647
+ for (let d = currentDepth; d > targetDepth; d--) {
648
+ await this.api.wsdbCommitCheckpoint({ forkid: b.forkId });
649
+ }
738
650
  }
651
+ this.checkpointDepths.set(b.forkId, targetDepth);
652
+ return undefined as WorldStateResponse[T];
739
653
  }
740
- this.checkpointDepths.set(forkId, depth);
741
- });
742
- }
743
654
 
744
- revertAllCheckpoints(forkId: number, depth: number): Promise<void> {
745
- return this.execute('revertAllCheckpoints', forkId, false, async () => {
746
- const currentDepth = this.checkpointDepths.get(forkId) ?? 0;
747
- if (depth === 0) {
748
- await this.api.revertAllCheckpoints({ forkId });
749
- } else {
750
- for (let d = currentDepth; d > depth; d--) {
751
- await this.api.revertCheckpoint({ forkId });
655
+ case WorldStateMessageType.REVERT_ALL_CHECKPOINTS: {
656
+ const b = body as WorldStateRequest[WorldStateMessageType.REVERT_ALL_CHECKPOINTS];
657
+ const targetDepth = b.depth ?? 0;
658
+ const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
659
+ if (targetDepth === 0) {
660
+ // Revert everything — use the bulk operation
661
+ await this.api.wsdbRevertAllCheckpoints({ forkid: b.forkId });
662
+ } else {
663
+ // Revert one level at a time down to target depth
664
+ for (let d = currentDepth; d > targetDepth; d--) {
665
+ await this.api.wsdbRevertCheckpoint({ forkid: b.forkId });
666
+ }
752
667
  }
668
+ this.checkpointDepths.set(b.forkId, targetDepth);
669
+ return undefined as WorldStateResponse[T];
753
670
  }
754
- this.checkpointDepths.set(forkId, depth);
755
- });
756
- }
757
671
 
758
- copyStores(dstPath: string, compact: boolean): Promise<void> {
759
- return this.execute('copyStores', 0, false, async () => {
760
- await this.api.copyStores({ dstPath, compact });
761
- });
762
- }
672
+ // ——— Misc ———
763
673
 
764
- // Read/write ordering is enforced server-side by the wsdb per-fork scheduler,
765
- // so the client no longer queues: every op is sent immediately and the server
766
- // serializes writes per fork while running reads concurrently. `_forkId` and
767
- // `_committedOnly` are retained in the signature (they describe the op) but are
768
- // no longer needed for client-side ordering.
769
- private async execute<T>(
770
- operation: WorldStateOperationName,
771
- _forkId: number,
772
- _committedOnly: boolean,
773
- request: () => Promise<T>,
774
- ): Promise<T> {
775
- if (!this.open) {
776
- throw new Error('IPC instance is closed');
777
- }
778
- return await this.send(operation, request);
779
- }
674
+ case WorldStateMessageType.COPY_STORES: {
675
+ const b = body as WorldStateRequest[WorldStateMessageType.COPY_STORES];
676
+ await this.api.wsdbCopyStores({ dstpath: b.dstPath, compact: b.compact });
677
+ return undefined as WorldStateResponse[T];
678
+ }
780
679
 
781
- private async send<T>(operation: WorldStateOperationName, request: () => Promise<T>): Promise<T> {
782
- const start = performance.now();
783
- try {
784
- const response = await request();
785
- const durationMs = performance.now() - start;
786
- this.log.trace(`Call ${operation} took (ms)`, { duration: durationMs });
787
- this.instrumentation.recordRoundTrip(durationMs * 1000, operation);
788
- return response;
789
- } catch (error) {
790
- this.log.error(`Call ${operation} failed: ${error}`, error);
791
- throw error;
680
+ case WorldStateMessageType.CLOSE: {
681
+ await this.api.wsdbShutdown({});
682
+ return undefined as WorldStateResponse[T];
683
+ }
684
+
685
+ default:
686
+ throw new Error(`Unknown message type: ${messageType}`);
792
687
  }
793
688
  }
794
689
  }
795
690
 
796
691
  /**
797
692
  * Helper to create WsdbOptions from standard world state config.
798
- * Returns the options needed to construct a wsdb service command line.
693
+ * Returns the options needed to construct a WsdbBackend.
799
694
  */
800
695
  export function getWsdbOptions(
801
696
  dataDir: string,