@aztec/world-state 0.0.1-commit.96bb3f7 → 0.0.1-commit.993d240
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/instrumentation/instrumentation.d.ts +1 -1
- package/dest/instrumentation/instrumentation.d.ts.map +1 -1
- package/dest/instrumentation/instrumentation.js +9 -2
- package/dest/native/fork_checkpoint.d.ts +7 -1
- package/dest/native/fork_checkpoint.d.ts.map +1 -1
- package/dest/native/fork_checkpoint.js +15 -3
- package/dest/native/ipc_world_state_instance.d.ts +50 -0
- package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
- package/dest/native/ipc_world_state_instance.js +628 -0
- package/dest/native/merkle_trees_facade.d.ts +9 -6
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +45 -16
- package/dest/native/message.d.ts +13 -5
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/native_world_state.d.ts +34 -8
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +81 -27
- package/dest/native/native_world_state_instance.d.ts +6 -5
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +33 -26
- package/dest/native/world_state_ops_queue.js +5 -5
- package/dest/synchronizer/config.d.ts +3 -5
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +15 -18
- package/dest/synchronizer/factory.d.ts +6 -5
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +7 -6
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +4 -5
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +109 -37
- package/dest/test/utils.d.ts +7 -7
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -5
- package/dest/testing.d.ts +4 -3
- package/dest/testing.d.ts.map +1 -1
- package/dest/testing.js +10 -6
- package/dest/world-state-db/merkle_tree_db.d.ts +3 -12
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/instrumentation/instrumentation.ts +9 -1
- package/src/native/fork_checkpoint.ts +19 -3
- package/src/native/ipc_world_state_instance.ts +717 -0
- package/src/native/merkle_trees_facade.ts +51 -17
- package/src/native/message.ts +14 -4
- package/src/native/native_world_state.ts +108 -32
- package/src/native/native_world_state_instance.ts +44 -32
- package/src/native/world_state_ops_queue.ts +5 -5
- package/src/synchronizer/config.ts +16 -23
- package/src/synchronizer/factory.ts +19 -11
- package/src/synchronizer/server_world_state_synchronizer.ts +119 -46
- package/src/test/utils.ts +6 -6
- package/src/testing.ts +8 -9
- package/src/world-state-db/merkle_tree_db.ts +2 -12
|
@@ -0,0 +1,717 @@
|
|
|
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';
|
|
12
|
+
import {
|
|
13
|
+
ARCHIVE_HEIGHT,
|
|
14
|
+
DomainSeparator,
|
|
15
|
+
L1_TO_L2_MSG_TREE_HEIGHT,
|
|
16
|
+
MAX_NULLIFIERS_PER_TX,
|
|
17
|
+
MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
18
|
+
NOTE_HASH_TREE_HEIGHT,
|
|
19
|
+
NULLIFIER_TREE_HEIGHT,
|
|
20
|
+
PUBLIC_DATA_TREE_HEIGHT,
|
|
21
|
+
} from '@aztec/constants';
|
|
22
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
23
|
+
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
24
|
+
import type { WorldStateRevision } from '@aztec/stdlib/world-state';
|
|
25
|
+
|
|
26
|
+
import assert from 'assert';
|
|
27
|
+
import { Decoder, Encoder } from 'msgpackr';
|
|
28
|
+
|
|
29
|
+
import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
|
|
30
|
+
import type { WorldStateTreeMapSizes } from '../synchronizer/factory.js';
|
|
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
|
+
} from './message.js';
|
|
49
|
+
import type { NativeWorldStateInstance } from './native_world_state_instance.js';
|
|
50
|
+
import { WorldStateOpsQueue } from './world_state_ops_queue.js';
|
|
51
|
+
|
|
52
|
+
// ————— Msgpack helpers —————
|
|
53
|
+
|
|
54
|
+
const msgpackEncoder = new Encoder({ useRecords: false });
|
|
55
|
+
const msgpackDecoder = new Decoder({ useRecords: false });
|
|
56
|
+
|
|
57
|
+
/** Msgpack-encode a SerializedLeafValue into bytes for IPC transport. */
|
|
58
|
+
function serializeLeafToBytes(leaf: SerializedLeafValue): Uint8Array {
|
|
59
|
+
return Buffer.from(msgpackEncoder.pack(leaf));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ————— Request conversion helpers —————
|
|
63
|
+
|
|
64
|
+
function toWsdbRevision(rev: WorldStateRevision): { forkid: number; blocknumber: number; includeuncommitted: boolean } {
|
|
65
|
+
return {
|
|
66
|
+
forkid: rev.forkId,
|
|
67
|
+
blocknumber: Number(rev.blockNumber),
|
|
68
|
+
includeuncommitted: rev.includeUncommitted,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
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)]);
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ————— Response conversion helpers —————
|
|
81
|
+
|
|
82
|
+
/** Convert Uint8Array fields to Buffer recursively (for opaque blob responses). */
|
|
83
|
+
function convertUint8ArraysToBuffers(obj: unknown): unknown {
|
|
84
|
+
if (obj instanceof Uint8Array) {
|
|
85
|
+
return Buffer.from(obj);
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(obj)) {
|
|
88
|
+
return obj.map(convertUint8ArraysToBuffers);
|
|
89
|
+
}
|
|
90
|
+
if (obj !== null && typeof obj === 'object') {
|
|
91
|
+
const result: Record<string, unknown> = {};
|
|
92
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
93
|
+
result[key] = convertUint8ArraysToBuffers(value);
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
return obj;
|
|
98
|
+
}
|
|
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
|
+
|
|
112
|
+
/** Convert Wsdb state reference (Record<number, [Uint8Array, number]>) to NAPI format. */
|
|
113
|
+
function convertStateRef(
|
|
114
|
+
state: Record<number, [Uint8Array, number]>,
|
|
115
|
+
): Record<number, readonly [Buffer, number | bigint]> {
|
|
116
|
+
const result: Record<number, readonly [Buffer, number | bigint]> = {};
|
|
117
|
+
for (const [key, [root, size]] of Object.entries(state)) {
|
|
118
|
+
result[Number(key)] = [Buffer.from(root), BigInt(size)] as const;
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Convert Wsdb WorldStateStatusSummary (lowercase) to NAPI format (camelCase). */
|
|
124
|
+
function convertStatusSummary(s: WsdbStatusSummary): WorldStateStatusSummary {
|
|
125
|
+
return {
|
|
126
|
+
unfinalizedBlockNumber: s.unfinalizedblocknumber,
|
|
127
|
+
finalizedBlockNumber: s.finalizedblocknumber,
|
|
128
|
+
oldestHistoricalBlock: s.oldesthistoricalblock,
|
|
129
|
+
treesAreSynched: s.treesaresynched,
|
|
130
|
+
} as unknown as WorldStateStatusSummary;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function convertDBStats(s: WsdbDBStatsInner): DBStats {
|
|
134
|
+
return {
|
|
135
|
+
name: s.name,
|
|
136
|
+
numDataItems: s.numdataitems,
|
|
137
|
+
totalUsedSize: s.totalusedsize,
|
|
138
|
+
} as unknown as DBStats;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function convertTreeDBStats(s: WsdbTreeDBStats): TreeDBStats {
|
|
142
|
+
return {
|
|
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),
|
|
150
|
+
} as unknown as TreeDBStats;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function convertWorldStateDBStats(s: WsdbDBStats): WorldStateDBStats {
|
|
154
|
+
return {
|
|
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),
|
|
160
|
+
} as unknown as WorldStateDBStats;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function convertTreeMeta(m: WsdbTreeMeta): TreeMeta {
|
|
164
|
+
return {
|
|
165
|
+
name: m.name,
|
|
166
|
+
depth: m.depth,
|
|
167
|
+
size: m.size,
|
|
168
|
+
committedSize: m.committedsize,
|
|
169
|
+
root: m.root,
|
|
170
|
+
initialSize: m.initialsize,
|
|
171
|
+
initialRoot: m.initialroot,
|
|
172
|
+
oldestHistoricBlock: m.oldesthistoricblock,
|
|
173
|
+
unfinalizedBlockHeight: m.unfinalizedblockheight,
|
|
174
|
+
finalizedBlockHeight: m.finalizedblockheight,
|
|
175
|
+
} as unknown as TreeMeta;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function convertWorldStateMeta(m: WsdbMeta): WorldStateMeta {
|
|
179
|
+
return {
|
|
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),
|
|
185
|
+
} as unknown as WorldStateMeta;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function convertStatusFull(s: WsdbStatusFull): WorldStateStatusFull {
|
|
189
|
+
return {
|
|
190
|
+
summary: convertStatusSummary(s.summary),
|
|
191
|
+
dbStats: convertWorldStateDBStats(s.dbstats),
|
|
192
|
+
meta: convertWorldStateMeta(s.meta),
|
|
193
|
+
} as unknown as WorldStateStatusFull;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Convert Wsdb SiblingPathAndIndex to NAPI format. */
|
|
197
|
+
function convertSiblingPathAndIndex(
|
|
198
|
+
s: WsdbSiblingPathAndIndex | undefined,
|
|
199
|
+
): { index: bigint; path: Buffer[] } | undefined {
|
|
200
|
+
if (!s) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
index: BigInt(s.index),
|
|
205
|
+
path: s.path.map(p => Buffer.from(p)),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ————— Public API —————
|
|
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
|
+
|
|
218
|
+
/**
|
|
219
|
+
* IPC-backed world state instance.
|
|
220
|
+
* Uses WsdbBackend (spawns aztec-wsdb binary) and the generated AsyncApi
|
|
221
|
+
* to communicate via the NamedUnion IPC protocol.
|
|
222
|
+
*/
|
|
223
|
+
export class IpcWorldState implements NativeWorldStateInstance {
|
|
224
|
+
private open = true;
|
|
225
|
+
private queues = new Map<number, WorldStateOpsQueue>();
|
|
226
|
+
private api: AsyncApi;
|
|
227
|
+
/** Tracks checkpoint depth per fork (WSDB IPC doesn't return depth in response). */
|
|
228
|
+
private checkpointDepths = new Map<number, number>();
|
|
229
|
+
|
|
230
|
+
constructor(
|
|
231
|
+
private readonly wsdbBackend: WsdbIpcBackend,
|
|
232
|
+
private readonly instrumentation: WorldStateInstrumentation,
|
|
233
|
+
bindings?: LoggerBindings,
|
|
234
|
+
private readonly log: Logger = createLogger('world-state:ipc-database', bindings),
|
|
235
|
+
) {
|
|
236
|
+
this.api = new AsyncApi(wsdbBackend as any);
|
|
237
|
+
this.queues.set(0, new WorldStateOpsQueue());
|
|
238
|
+
this.log.info('Created IPC-backed world state instance');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Returns the socket path of the underlying wsdb server. */
|
|
242
|
+
getSocketPath(): string {
|
|
243
|
+
return this.wsdbBackend.getSocketPath();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
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.
|
|
250
|
+
*/
|
|
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
|
+
}
|
|
281
|
+
|
|
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
|
+
try {
|
|
286
|
+
const response = await requestQueue.execute(
|
|
287
|
+
async () => {
|
|
288
|
+
assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the IPC instance');
|
|
289
|
+
assert.equal(this.open, true, 'IPC instance is closed');
|
|
290
|
+
let response: WorldStateResponse[T];
|
|
291
|
+
try {
|
|
292
|
+
response = await this._sendMessage(messageType, body);
|
|
293
|
+
} catch (error: any) {
|
|
294
|
+
errorHandler(error.message);
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
return responseHandler(response);
|
|
298
|
+
},
|
|
299
|
+
messageType,
|
|
300
|
+
committedOnly,
|
|
301
|
+
);
|
|
302
|
+
return response;
|
|
303
|
+
} finally {
|
|
304
|
+
if (messageType === WorldStateMessageType.DELETE_FORK) {
|
|
305
|
+
await requestQueue.stop();
|
|
306
|
+
this.queues.delete(forkId);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async close(): Promise<void> {
|
|
312
|
+
if (!this.open) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
this.open = false;
|
|
316
|
+
const queue = this.queues.get(0)!;
|
|
317
|
+
|
|
318
|
+
// Send shutdown command. Under normal operation, the WSDB process sends its
|
|
319
|
+
// response before exiting (via ShutdownRequested in ipc_server.hpp). The
|
|
320
|
+
// try/catch is defensive: if the process is killed externally (SIGKILL, OOM)
|
|
321
|
+
// before responding, the pending IPC callback would be rejected by the socket
|
|
322
|
+
// close handler. We proceed to destroy the backend regardless.
|
|
323
|
+
try {
|
|
324
|
+
await queue.execute(
|
|
325
|
+
async () => {
|
|
326
|
+
await this.api.wsdbShutdown({});
|
|
327
|
+
},
|
|
328
|
+
WorldStateMessageType.CLOSE,
|
|
329
|
+
false,
|
|
330
|
+
);
|
|
331
|
+
} catch (err: any) {
|
|
332
|
+
this.log.debug(`wsdbShutdown completed with error: ${err.message}`);
|
|
333
|
+
}
|
|
334
|
+
await queue.stop();
|
|
335
|
+
|
|
336
|
+
if (this.wsdbBackend.destroy) {
|
|
337
|
+
await this.wsdbBackend.destroy();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private async _sendMessage<T extends WorldStateMessageType>(
|
|
342
|
+
messageType: T,
|
|
343
|
+
body: WorldStateRequest[T] & WorldStateRequestCategories,
|
|
344
|
+
): Promise<WorldStateResponse[T]> {
|
|
345
|
+
const start = performance.now();
|
|
346
|
+
try {
|
|
347
|
+
const response = await this.dispatch(messageType, body);
|
|
348
|
+
const durationMs = performance.now() - start;
|
|
349
|
+
this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, { duration: durationMs });
|
|
350
|
+
this.instrumentation.recordRoundTrip(durationMs * 1000, messageType);
|
|
351
|
+
return response;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error);
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private async dispatch<T extends WorldStateMessageType>(
|
|
359
|
+
messageType: T,
|
|
360
|
+
body: WorldStateRequest[T] & WorldStateRequestCategories,
|
|
361
|
+
): Promise<WorldStateResponse[T]> {
|
|
362
|
+
switch (messageType) {
|
|
363
|
+
// ——— Tree info & state reference ———
|
|
364
|
+
|
|
365
|
+
case WorldStateMessageType.GET_TREE_INFO: {
|
|
366
|
+
const b = body as WorldStateRequest[WorldStateMessageType.GET_TREE_INFO];
|
|
367
|
+
const resp = await this.api.wsdbGetTreeInfo({
|
|
368
|
+
treeid: b.treeId,
|
|
369
|
+
revision: toWsdbRevision(b.revision),
|
|
370
|
+
});
|
|
371
|
+
return {
|
|
372
|
+
treeId: resp.treeid,
|
|
373
|
+
root: Buffer.from(resp.root),
|
|
374
|
+
size: resp.size,
|
|
375
|
+
depth: resp.depth,
|
|
376
|
+
} as WorldStateResponse[T];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
case WorldStateMessageType.GET_STATE_REFERENCE: {
|
|
380
|
+
const b = body as WorldStateRequest[WorldStateMessageType.GET_STATE_REFERENCE];
|
|
381
|
+
const resp = await this.api.wsdbGetStateReference({
|
|
382
|
+
revision: toWsdbRevision(b.revision),
|
|
383
|
+
});
|
|
384
|
+
return { state: convertStateRef(resp.state) } as WorldStateResponse[T];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
case WorldStateMessageType.GET_INITIAL_STATE_REFERENCE: {
|
|
388
|
+
const resp = await this.api.wsdbGetInitialStateReference({});
|
|
389
|
+
return { state: convertStateRef(resp.state) } as WorldStateResponse[T];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ——— Leaf queries ———
|
|
393
|
+
|
|
394
|
+
case WorldStateMessageType.GET_LEAF_VALUE: {
|
|
395
|
+
const b = body as WorldStateRequest[WorldStateMessageType.GET_LEAF_VALUE];
|
|
396
|
+
const resp = await this.api.wsdbGetLeafValue({
|
|
397
|
+
treeid: b.treeId,
|
|
398
|
+
revision: toWsdbRevision(b.revision),
|
|
399
|
+
leafindex: Number(b.leafIndex),
|
|
400
|
+
});
|
|
401
|
+
if (!resp.value) {
|
|
402
|
+
return undefined as WorldStateResponse[T];
|
|
403
|
+
}
|
|
404
|
+
return decodeLeafValue(resp.value) as WorldStateResponse[T];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
case WorldStateMessageType.GET_LEAF_PREIMAGE: {
|
|
408
|
+
const b = body as WorldStateRequest[WorldStateMessageType.GET_LEAF_PREIMAGE];
|
|
409
|
+
const resp = await this.api.wsdbGetLeafPreimage({
|
|
410
|
+
treeid: b.treeId,
|
|
411
|
+
revision: toWsdbRevision(b.revision),
|
|
412
|
+
leafindex: Number(b.leafIndex),
|
|
413
|
+
});
|
|
414
|
+
if (!resp.preimage) {
|
|
415
|
+
return undefined as WorldStateResponse[T];
|
|
416
|
+
}
|
|
417
|
+
return decodeLeafPreimage(resp.preimage) as WorldStateResponse[T];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
case WorldStateMessageType.GET_SIBLING_PATH: {
|
|
421
|
+
const b = body as WorldStateRequest[WorldStateMessageType.GET_SIBLING_PATH];
|
|
422
|
+
const resp = await this.api.wsdbGetSiblingPath({
|
|
423
|
+
treeid: b.treeId,
|
|
424
|
+
revision: toWsdbRevision(b.revision),
|
|
425
|
+
leafindex: Number(b.leafIndex),
|
|
426
|
+
});
|
|
427
|
+
return resp.path.map(p => Buffer.from(p)) as WorldStateResponse[T];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
case WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES: {
|
|
431
|
+
const b = body as WorldStateRequest[WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES];
|
|
432
|
+
const resp = await this.api.wsdbGetBlockNumbersForLeafIndices({
|
|
433
|
+
treeid: b.treeId,
|
|
434
|
+
revision: toWsdbRevision(b.revision),
|
|
435
|
+
leafindices: b.leafIndices.map(Number),
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
blockNumbers: resp.blocknumbers.map(n => (n != null ? BigInt(n) : undefined)),
|
|
439
|
+
} as WorldStateResponse[T];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ——— Find operations ———
|
|
443
|
+
|
|
444
|
+
case WorldStateMessageType.FIND_LEAF_INDICES: {
|
|
445
|
+
const b = body as WorldStateRequest[WorldStateMessageType.FIND_LEAF_INDICES];
|
|
446
|
+
const resp = await this.api.wsdbFindLeafIndices({
|
|
447
|
+
treeid: b.treeId,
|
|
448
|
+
revision: toWsdbRevision(b.revision),
|
|
449
|
+
leaves: b.leaves.map(serializeLeafToBytes),
|
|
450
|
+
startindex: Number(b.startIndex),
|
|
451
|
+
});
|
|
452
|
+
return {
|
|
453
|
+
indices: resp.indices.map(n => (n != null ? BigInt(n) : undefined)),
|
|
454
|
+
} as WorldStateResponse[T];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
case WorldStateMessageType.FIND_LOW_LEAF: {
|
|
458
|
+
const b = body as WorldStateRequest[WorldStateMessageType.FIND_LOW_LEAF];
|
|
459
|
+
const resp = await this.api.wsdbFindLowLeaf({
|
|
460
|
+
treeid: b.treeId,
|
|
461
|
+
revision: toWsdbRevision(b.revision),
|
|
462
|
+
key: new Uint8Array(b.key.toBuffer()),
|
|
463
|
+
});
|
|
464
|
+
return {
|
|
465
|
+
alreadyPresent: resp.alreadypresent,
|
|
466
|
+
index: BigInt(resp.index),
|
|
467
|
+
} as WorldStateResponse[T];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
case WorldStateMessageType.FIND_SIBLING_PATHS: {
|
|
471
|
+
const b = body as WorldStateRequest[WorldStateMessageType.FIND_SIBLING_PATHS];
|
|
472
|
+
const resp = await this.api.wsdbFindSiblingPaths({
|
|
473
|
+
treeid: b.treeId,
|
|
474
|
+
revision: toWsdbRevision(b.revision),
|
|
475
|
+
leaves: b.leaves.map(serializeLeafToBytes),
|
|
476
|
+
});
|
|
477
|
+
return {
|
|
478
|
+
paths: resp.paths.map(convertSiblingPathAndIndex),
|
|
479
|
+
} as WorldStateResponse[T];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ——— Mutations ———
|
|
483
|
+
|
|
484
|
+
case WorldStateMessageType.APPEND_LEAVES: {
|
|
485
|
+
const b = body as WorldStateRequest[WorldStateMessageType.APPEND_LEAVES];
|
|
486
|
+
await this.api.wsdbAppendLeaves({
|
|
487
|
+
treeid: b.treeId,
|
|
488
|
+
leaves: b.leaves.map(serializeLeafToBytes),
|
|
489
|
+
forkid: b.forkId,
|
|
490
|
+
});
|
|
491
|
+
return undefined as WorldStateResponse[T];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
case WorldStateMessageType.BATCH_INSERT: {
|
|
495
|
+
const b = body as WorldStateRequest[WorldStateMessageType.BATCH_INSERT];
|
|
496
|
+
const resp = await this.api.wsdbBatchInsert({
|
|
497
|
+
treeid: b.treeId,
|
|
498
|
+
leaves: b.leaves.map(serializeLeafToBytes),
|
|
499
|
+
subtreedepth: b.subtreeDepth,
|
|
500
|
+
forkid: b.forkId,
|
|
501
|
+
});
|
|
502
|
+
const decoded = msgpackDecoder.unpack(Buffer.from(resp.result));
|
|
503
|
+
return convertUint8ArraysToBuffers(decoded) as WorldStateResponse[T];
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
case WorldStateMessageType.SEQUENTIAL_INSERT: {
|
|
507
|
+
const b = body as WorldStateRequest[WorldStateMessageType.SEQUENTIAL_INSERT];
|
|
508
|
+
const resp = await this.api.wsdbSequentialInsert({
|
|
509
|
+
treeid: b.treeId,
|
|
510
|
+
leaves: b.leaves.map(serializeLeafToBytes),
|
|
511
|
+
forkid: b.forkId,
|
|
512
|
+
});
|
|
513
|
+
const decoded = msgpackDecoder.unpack(Buffer.from(resp.result));
|
|
514
|
+
return convertUint8ArraysToBuffers(decoded) as WorldStateResponse[T];
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
case WorldStateMessageType.UPDATE_ARCHIVE: {
|
|
518
|
+
const b = body as WorldStateRequest[WorldStateMessageType.UPDATE_ARCHIVE];
|
|
519
|
+
await this.api.wsdbUpdateArchive({
|
|
520
|
+
blockstateref: blockStateRefToMap(b.blockStateRef as Map<number, readonly [Buffer, number | bigint]>) as any,
|
|
521
|
+
blockheaderhash: new Uint8Array(b.blockHeaderHash),
|
|
522
|
+
forkid: b.forkId,
|
|
523
|
+
});
|
|
524
|
+
return undefined as WorldStateResponse[T];
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ——— Commit / Rollback ———
|
|
528
|
+
|
|
529
|
+
case WorldStateMessageType.COMMIT: {
|
|
530
|
+
await this.api.wsdbCommit({});
|
|
531
|
+
return undefined as WorldStateResponse[T];
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
case WorldStateMessageType.ROLLBACK: {
|
|
535
|
+
await this.api.wsdbRollback({});
|
|
536
|
+
return undefined as WorldStateResponse[T];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ——— Block sync ———
|
|
540
|
+
|
|
541
|
+
case WorldStateMessageType.SYNC_BLOCK: {
|
|
542
|
+
const b = body as WorldStateRequest[WorldStateMessageType.SYNC_BLOCK];
|
|
543
|
+
const resp = await this.api.wsdbSyncBlock({
|
|
544
|
+
blocknumber: Number(b.blockNumber),
|
|
545
|
+
blockstateref: blockStateRefToMap(b.blockStateRef as Map<number, readonly [Buffer, number | bigint]>) as any,
|
|
546
|
+
blockheaderhash: new Uint8Array(b.blockHeaderHash),
|
|
547
|
+
paddednotehashes: b.paddedNoteHashes.map(l => new Uint8Array(l as Buffer)),
|
|
548
|
+
paddedl1tol2messages: b.paddedL1ToL2Messages.map(l => new Uint8Array(l as Buffer)),
|
|
549
|
+
paddednullifiers: b.paddedNullifiers.map(l => ({
|
|
550
|
+
nullifier: new Uint8Array((l as { nullifier: Buffer }).nullifier),
|
|
551
|
+
})),
|
|
552
|
+
publicdatawrites: b.publicDataWrites.map(l => ({
|
|
553
|
+
slot: new Uint8Array((l as { slot: Buffer; value: Buffer }).slot),
|
|
554
|
+
value: new Uint8Array((l as { slot: Buffer; value: Buffer }).value),
|
|
555
|
+
})),
|
|
556
|
+
});
|
|
557
|
+
return convertStatusFull(resp.status) as WorldStateResponse[T];
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ——— Fork management ———
|
|
561
|
+
|
|
562
|
+
case WorldStateMessageType.CREATE_FORK: {
|
|
563
|
+
const b = body as WorldStateRequest[WorldStateMessageType.CREATE_FORK];
|
|
564
|
+
const resp = await this.api.wsdbCreateFork({
|
|
565
|
+
latest: b.latest,
|
|
566
|
+
blocknumber: Number(b.blockNumber),
|
|
567
|
+
});
|
|
568
|
+
return { forkId: resp.forkid } as WorldStateResponse[T];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
case WorldStateMessageType.DELETE_FORK: {
|
|
572
|
+
const b = body as WorldStateRequest[WorldStateMessageType.DELETE_FORK];
|
|
573
|
+
await this.api.wsdbDeleteFork({ forkid: b.forkId });
|
|
574
|
+
return undefined as WorldStateResponse[T];
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// ——— Block finalization ———
|
|
578
|
+
|
|
579
|
+
case WorldStateMessageType.FINALIZE_BLOCKS: {
|
|
580
|
+
const b = body as WorldStateRequest[WorldStateMessageType.FINALIZE_BLOCKS];
|
|
581
|
+
const resp = await this.api.wsdbFinalizeBlocks({ toblocknumber: Number(b.toBlockNumber) });
|
|
582
|
+
return convertStatusSummary(resp.status) as WorldStateResponse[T];
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
case WorldStateMessageType.UNWIND_BLOCKS: {
|
|
586
|
+
const b = body as WorldStateRequest[WorldStateMessageType.UNWIND_BLOCKS];
|
|
587
|
+
const resp = await this.api.wsdbUnwindBlocks({ toblocknumber: Number(b.toBlockNumber) });
|
|
588
|
+
return convertStatusFull(resp.status) as WorldStateResponse[T];
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
case WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS: {
|
|
592
|
+
const b = body as WorldStateRequest[WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS];
|
|
593
|
+
const resp = await this.api.wsdbRemoveHistoricalBlocks({ toblocknumber: Number(b.toBlockNumber) });
|
|
594
|
+
return convertStatusFull(resp.status) as WorldStateResponse[T];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ——— Status ———
|
|
598
|
+
|
|
599
|
+
case WorldStateMessageType.GET_STATUS: {
|
|
600
|
+
const resp = await this.api.wsdbGetStatus({});
|
|
601
|
+
return convertStatusSummary(resp.status) as WorldStateResponse[T];
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ——— Checkpoints ———
|
|
605
|
+
|
|
606
|
+
case WorldStateMessageType.CREATE_CHECKPOINT: {
|
|
607
|
+
const b = body as WorldStateRequest[WorldStateMessageType.CREATE_CHECKPOINT];
|
|
608
|
+
await this.api.wsdbCreateCheckpoint({ forkid: b.forkId });
|
|
609
|
+
const depth = (this.checkpointDepths.get(b.forkId) ?? 0) + 1;
|
|
610
|
+
this.checkpointDepths.set(b.forkId, depth);
|
|
611
|
+
return { depth } as WorldStateResponse[T];
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
case WorldStateMessageType.COMMIT_CHECKPOINT: {
|
|
615
|
+
const b = body as WorldStateRequest[WorldStateMessageType.COMMIT_CHECKPOINT];
|
|
616
|
+
await this.api.wsdbCommitCheckpoint({ forkid: b.forkId });
|
|
617
|
+
const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
|
|
618
|
+
this.checkpointDepths.set(b.forkId, depth);
|
|
619
|
+
return undefined as WorldStateResponse[T];
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
case WorldStateMessageType.REVERT_CHECKPOINT: {
|
|
623
|
+
const b = body as WorldStateRequest[WorldStateMessageType.REVERT_CHECKPOINT];
|
|
624
|
+
await this.api.wsdbRevertCheckpoint({ 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
|
+
}
|
|
629
|
+
|
|
630
|
+
case WorldStateMessageType.COMMIT_ALL_CHECKPOINTS: {
|
|
631
|
+
const b = body as WorldStateRequest[WorldStateMessageType.COMMIT_ALL_CHECKPOINTS];
|
|
632
|
+
const targetDepth = b.depth ?? 0;
|
|
633
|
+
const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
|
|
634
|
+
if (targetDepth === 0) {
|
|
635
|
+
// Commit everything — use the bulk operation
|
|
636
|
+
await this.api.wsdbCommitAllCheckpoints({ forkid: b.forkId });
|
|
637
|
+
} else {
|
|
638
|
+
// Commit one level at a time down to target depth
|
|
639
|
+
for (let d = currentDepth; d > targetDepth; d--) {
|
|
640
|
+
await this.api.wsdbCommitCheckpoint({ forkid: b.forkId });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
this.checkpointDepths.set(b.forkId, targetDepth);
|
|
644
|
+
return undefined as WorldStateResponse[T];
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
case WorldStateMessageType.REVERT_ALL_CHECKPOINTS: {
|
|
648
|
+
const b = body as WorldStateRequest[WorldStateMessageType.REVERT_ALL_CHECKPOINTS];
|
|
649
|
+
const targetDepth = b.depth ?? 0;
|
|
650
|
+
const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
|
|
651
|
+
if (targetDepth === 0) {
|
|
652
|
+
// Revert everything — use the bulk operation
|
|
653
|
+
await this.api.wsdbRevertAllCheckpoints({ forkid: b.forkId });
|
|
654
|
+
} else {
|
|
655
|
+
// Revert one level at a time down to target depth
|
|
656
|
+
for (let d = currentDepth; d > targetDepth; d--) {
|
|
657
|
+
await this.api.wsdbRevertCheckpoint({ forkid: b.forkId });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
this.checkpointDepths.set(b.forkId, targetDepth);
|
|
661
|
+
return undefined as WorldStateResponse[T];
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// ——— Misc ———
|
|
665
|
+
|
|
666
|
+
case WorldStateMessageType.COPY_STORES: {
|
|
667
|
+
const b = body as WorldStateRequest[WorldStateMessageType.COPY_STORES];
|
|
668
|
+
await this.api.wsdbCopyStores({ dstpath: b.dstPath, compact: b.compact });
|
|
669
|
+
return undefined as WorldStateResponse[T];
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
case WorldStateMessageType.CLOSE: {
|
|
673
|
+
await this.api.wsdbShutdown({});
|
|
674
|
+
return undefined as WorldStateResponse[T];
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
default:
|
|
678
|
+
throw new Error(`Unknown message type: ${messageType}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Helper to create WsdbOptions from standard world state config.
|
|
685
|
+
* Returns the options needed to construct a WsdbBackend.
|
|
686
|
+
*/
|
|
687
|
+
export function getWsdbOptions(
|
|
688
|
+
dataDir: string,
|
|
689
|
+
wsTreeMapSizes: WorldStateTreeMapSizes,
|
|
690
|
+
): {
|
|
691
|
+
treeHeights: Record<number, number>;
|
|
692
|
+
treePrefill: Record<number, number>;
|
|
693
|
+
mapSizes: Record<number, number>;
|
|
694
|
+
initialHeaderGeneratorPoint: number;
|
|
695
|
+
} {
|
|
696
|
+
return {
|
|
697
|
+
treeHeights: {
|
|
698
|
+
[MerkleTreeId.NULLIFIER_TREE]: NULLIFIER_TREE_HEIGHT,
|
|
699
|
+
[MerkleTreeId.NOTE_HASH_TREE]: NOTE_HASH_TREE_HEIGHT,
|
|
700
|
+
[MerkleTreeId.PUBLIC_DATA_TREE]: PUBLIC_DATA_TREE_HEIGHT,
|
|
701
|
+
[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: L1_TO_L2_MSG_TREE_HEIGHT,
|
|
702
|
+
[MerkleTreeId.ARCHIVE]: ARCHIVE_HEIGHT,
|
|
703
|
+
},
|
|
704
|
+
treePrefill: {
|
|
705
|
+
[MerkleTreeId.NULLIFIER_TREE]: 2 * MAX_NULLIFIERS_PER_TX,
|
|
706
|
+
[MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
707
|
+
},
|
|
708
|
+
mapSizes: {
|
|
709
|
+
[MerkleTreeId.NULLIFIER_TREE]: wsTreeMapSizes.nullifierTreeMapSizeKb,
|
|
710
|
+
[MerkleTreeId.NOTE_HASH_TREE]: wsTreeMapSizes.noteHashTreeMapSizeKb,
|
|
711
|
+
[MerkleTreeId.PUBLIC_DATA_TREE]: wsTreeMapSizes.publicDataTreeMapSizeKb,
|
|
712
|
+
[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: wsTreeMapSizes.messageTreeMapSizeKb,
|
|
713
|
+
[MerkleTreeId.ARCHIVE]: wsTreeMapSizes.archiveTreeMapSizeKb,
|
|
714
|
+
},
|
|
715
|
+
initialHeaderGeneratorPoint: DomainSeparator.BLOCK_HEADER_HASH,
|
|
716
|
+
};
|
|
717
|
+
}
|