@aztec/world-state 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,462 @@
1
+ import {
2
+ type BatchInsertionResult,
3
+ type HandleL2BlockAndMessagesResult,
4
+ type IndexedTreeId,
5
+ type L2Block,
6
+ type MerkleTreeAdminOperations,
7
+ MerkleTreeId,
8
+ type MerkleTreeLeafType,
9
+ SiblingPath,
10
+ type TreeInfo,
11
+ TxEffect,
12
+ } from '@aztec/circuit-types';
13
+ import {
14
+ Fr,
15
+ Header,
16
+ MAX_NOTE_HASHES_PER_TX,
17
+ MAX_NULLIFIERS_PER_TX,
18
+ MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
19
+ NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
20
+ NullifierLeaf,
21
+ NullifierLeafPreimage,
22
+ PartialStateReference,
23
+ PublicDataTreeLeaf,
24
+ PublicDataTreeLeafPreimage,
25
+ StateReference,
26
+ } from '@aztec/circuits.js';
27
+ import { padArrayEnd } from '@aztec/foundation/collection';
28
+ import { SerialQueue } from '@aztec/foundation/queue';
29
+ import { serializeToBuffer } from '@aztec/foundation/serialize';
30
+ import type { IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
31
+
32
+ import bindings from 'bindings';
33
+ import { Decoder, Encoder, addExtension } from 'msgpackr';
34
+ import { isAnyArrayBuffer } from 'util/types';
35
+
36
+ import { type MerkleTreeDb, type TreeSnapshots } from '../world-state-db/merkle_tree_db.js';
37
+ import { MerkleTreeAdminOperationsFacade } from '../world-state-db/merkle_tree_operations_facade.js';
38
+ import {
39
+ MessageHeader,
40
+ type SerializedIndexedLeaf,
41
+ type SerializedLeafValue,
42
+ TypedMessage,
43
+ WorldStateMessageType,
44
+ type WorldStateRequest,
45
+ type WorldStateResponse,
46
+ blockStateReference,
47
+ treeStateReferenceToSnapshot,
48
+ worldStateRevision,
49
+ } from './message.js';
50
+
51
+ // small extension to pack an NodeJS Fr instance to a representation that the C++ code can understand
52
+ // this only works for writes. Unpacking from C++ can't create Fr instances because the data is passed
53
+ // as raw, untagged, buffers. On the NodeJS side we don't know what the buffer represents
54
+ // Adding a tag would be a solution, but it would have to be done on both sides and it's unclear where else
55
+ // C++ fr instances are sent/received/stored.
56
+ addExtension({
57
+ Class: Fr,
58
+ write: fr => fr.toBuffer(),
59
+ });
60
+
61
+ export interface NativeInstance {
62
+ call(msg: Buffer | Uint8Array): Promise<any>;
63
+ }
64
+
65
+ export class NativeWorldStateService implements MerkleTreeDb {
66
+ private nextMessageId = 1;
67
+
68
+ private encoder = new Encoder({
69
+ // always encode JS objects as MessagePack maps
70
+ // this makes it compatible with other MessagePack decoders
71
+ useRecords: false,
72
+ int64AsType: 'bigint',
73
+ });
74
+
75
+ private decoder = new Decoder({
76
+ useRecords: false,
77
+ int64AsType: 'bigint',
78
+ });
79
+
80
+ private queue = new SerialQueue();
81
+
82
+ protected constructor(private instance: NativeInstance) {
83
+ this.queue.start();
84
+ }
85
+
86
+ static async create(
87
+ dataDir: string,
88
+ libraryName = 'world_state_napi',
89
+ className = 'WorldState',
90
+ ): Promise<NativeWorldStateService> {
91
+ const library = bindings(libraryName);
92
+ const instance = new library[className](dataDir);
93
+ const worldState = new NativeWorldStateService(instance);
94
+ await worldState.init();
95
+ return worldState;
96
+ }
97
+
98
+ protected async init() {
99
+ const archive = await this.getTreeInfo(MerkleTreeId.ARCHIVE, false);
100
+ if (archive.size === 0n) {
101
+ // TODO (alexg) move this to the native module
102
+ // const header = await this.buildInitialHeader(true);
103
+ // await this.appendLeaves(MerkleTreeId.ARCHIVE, [header.hash()]);
104
+ // await this.commit();
105
+ }
106
+ }
107
+
108
+ public asLatest(): MerkleTreeAdminOperations {
109
+ return new MerkleTreeAdminOperationsFacade(this, true);
110
+ }
111
+
112
+ // async buildInitialHeader(ic: boolean = false): Promise<Header> {
113
+ // const state = await this.getStateReference(ic);
114
+ // return new Header(
115
+ // AppendOnlyTreeSnapshot.zero(),
116
+ // ContentCommitment.empty(),
117
+ // state,
118
+ // GlobalVariables.empty(),
119
+ // Fr.ZERO,
120
+ // );
121
+ // }
122
+
123
+ public getInitialHeader(): Header {
124
+ // TODO (alexg) implement this
125
+ return Header.empty();
126
+ }
127
+
128
+ async appendLeaves<ID extends MerkleTreeId>(treeId: ID, leaves: MerkleTreeLeafType<ID>[]): Promise<void> {
129
+ await this.call(WorldStateMessageType.APPEND_LEAVES, {
130
+ leaves: leaves.map(leaf => leaf as any),
131
+ treeId,
132
+ });
133
+ }
134
+
135
+ async batchInsert<TreeHeight extends number, SubtreeSiblingPathHeight extends number, ID extends IndexedTreeId>(
136
+ treeId: ID,
137
+ rawLeaves: Buffer[],
138
+ subtreeHeight: number,
139
+ ): Promise<BatchInsertionResult<TreeHeight, SubtreeSiblingPathHeight>> {
140
+ const leaves = rawLeaves.map((leaf: Buffer) => hydrateLeaf(treeId, leaf)).map(serializeLeaf);
141
+ const resp = await this.call(WorldStateMessageType.BATCH_INSERT, { leaves, treeId, subtreeDepth: subtreeHeight });
142
+
143
+ return {
144
+ newSubtreeSiblingPath: new SiblingPath<SubtreeSiblingPathHeight>(
145
+ resp.subtree_path.length as any,
146
+ resp.subtree_path,
147
+ ),
148
+ sortedNewLeaves: resp.sorted_leaves
149
+ .map(([leaf]) => leaf)
150
+ .map(deserializeLeafValue)
151
+ .map(serializeToBuffer),
152
+ sortedNewLeavesIndexes: resp.sorted_leaves.map(([, index]) => index),
153
+ lowLeavesWitnessData: resp.low_leaf_witness_data.map(data => ({
154
+ index: BigInt(data.index),
155
+ leafPreimage: deserializeIndexedLeaf(data.leaf),
156
+ siblingPath: new SiblingPath<TreeHeight>(data.path.length as any, data.path),
157
+ })),
158
+ };
159
+ }
160
+
161
+ async commit(): Promise<void> {
162
+ await this.call(WorldStateMessageType.COMMIT, void 0);
163
+ }
164
+
165
+ findLeafIndex(
166
+ treeId: MerkleTreeId,
167
+ value: MerkleTreeLeafType<MerkleTreeId>,
168
+ includeUncommitted: boolean,
169
+ ): Promise<bigint | undefined> {
170
+ return this.findLeafIndexAfter(treeId, value, 0n, includeUncommitted);
171
+ }
172
+
173
+ async findLeafIndexAfter(
174
+ treeId: MerkleTreeId,
175
+ leaf: MerkleTreeLeafType<MerkleTreeId>,
176
+ startIndex: bigint,
177
+ includeUncommitted: boolean,
178
+ ): Promise<bigint | undefined> {
179
+ const index = await this.call(WorldStateMessageType.FIND_LEAF_INDEX, {
180
+ leaf: serializeLeaf(hydrateLeaf(treeId, leaf)),
181
+ revision: worldStateRevision(includeUncommitted),
182
+ treeId,
183
+ startIndex,
184
+ });
185
+
186
+ if (typeof index === 'number' || typeof index === 'bigint') {
187
+ return BigInt(index);
188
+ } else {
189
+ return undefined;
190
+ }
191
+ }
192
+
193
+ async getLeafPreimage(
194
+ treeId: IndexedTreeId,
195
+ leafIndex: bigint,
196
+ args: boolean,
197
+ ): Promise<IndexedTreeLeafPreimage | undefined> {
198
+ const resp = await this.call(WorldStateMessageType.GET_LEAF_PREIMAGE, {
199
+ leafIndex,
200
+ revision: worldStateRevision(args),
201
+ treeId,
202
+ });
203
+
204
+ return resp ? deserializeIndexedLeaf(resp) : undefined;
205
+ }
206
+
207
+ async getLeafValue(
208
+ treeId: MerkleTreeId,
209
+ leafIndex: bigint,
210
+ includeUncommitted: boolean,
211
+ ): Promise<MerkleTreeLeafType<MerkleTreeId> | undefined> {
212
+ const resp = await this.call(WorldStateMessageType.GET_LEAF_VALUE, {
213
+ leafIndex,
214
+ revision: worldStateRevision(includeUncommitted),
215
+ treeId,
216
+ });
217
+
218
+ if (!resp) {
219
+ return undefined;
220
+ }
221
+
222
+ const leaf = deserializeLeafValue(resp);
223
+ if (leaf instanceof Fr) {
224
+ return leaf;
225
+ } else {
226
+ return leaf.toBuffer();
227
+ }
228
+ }
229
+
230
+ async getPreviousValueIndex(
231
+ treeId: IndexedTreeId,
232
+ value: bigint,
233
+ includeUncommitted: boolean,
234
+ ): Promise<{ index: bigint; alreadyPresent: boolean } | undefined> {
235
+ const resp = await this.call(WorldStateMessageType.FIND_LOW_LEAF, {
236
+ key: new Fr(value),
237
+ revision: worldStateRevision(includeUncommitted),
238
+ treeId,
239
+ });
240
+ return {
241
+ alreadyPresent: resp.alreadyPresent,
242
+ index: BigInt(resp.index),
243
+ };
244
+ }
245
+
246
+ async getSiblingPath(
247
+ treeId: MerkleTreeId,
248
+ leafIndex: bigint,
249
+ includeUncommitted: boolean,
250
+ ): Promise<SiblingPath<number>> {
251
+ const siblingPath = await this.call(WorldStateMessageType.GET_SIBLING_PATH, {
252
+ leafIndex,
253
+ revision: worldStateRevision(includeUncommitted),
254
+ treeId,
255
+ });
256
+
257
+ return new SiblingPath(siblingPath.length, siblingPath);
258
+ }
259
+
260
+ getSnapshot(_block: number): Promise<TreeSnapshots> {
261
+ return Promise.reject(new Error('getSnapshot not implemented'));
262
+ }
263
+
264
+ async getStateReference(includeUncommitted: boolean): Promise<StateReference> {
265
+ const resp = await this.call(WorldStateMessageType.GET_STATE_REFERENCE, {
266
+ revision: worldStateRevision(includeUncommitted),
267
+ });
268
+
269
+ return new StateReference(
270
+ treeStateReferenceToSnapshot(resp.state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]),
271
+ new PartialStateReference(
272
+ treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NOTE_HASH_TREE]),
273
+ treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NULLIFIER_TREE]),
274
+ treeStateReferenceToSnapshot(resp.state[MerkleTreeId.PUBLIC_DATA_TREE]),
275
+ ),
276
+ );
277
+ }
278
+
279
+ async getTreeInfo(treeId: MerkleTreeId, includeUncommitted: boolean): Promise<TreeInfo> {
280
+ const resp = await this.call(WorldStateMessageType.GET_TREE_INFO, {
281
+ treeId: treeId,
282
+ revision: worldStateRevision(includeUncommitted),
283
+ });
284
+
285
+ return {
286
+ depth: resp.depth,
287
+ root: resp.root,
288
+ size: BigInt(resp.size),
289
+ treeId,
290
+ };
291
+ }
292
+
293
+ async handleL2BlockAndMessages(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<HandleL2BlockAndMessagesResult> {
294
+ // We have to pad both the tx effects and the values within tx effects because that's how the trees are built
295
+ // by circuits.
296
+ const paddedTxEffects = padArrayEnd(
297
+ l2Block.body.txEffects,
298
+ TxEffect.empty(),
299
+ l2Block.body.numberOfTxsIncludingPadded,
300
+ );
301
+
302
+ const paddedNoteHashes = paddedTxEffects.flatMap(txEffect =>
303
+ padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
304
+ );
305
+ const paddedL1ToL2Messages = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
306
+
307
+ const paddedNullifiers = paddedTxEffects
308
+ .flatMap(txEffect => padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX))
309
+ .map(nullifier => new NullifierLeaf(nullifier));
310
+
311
+ // We insert the public data tree leaves with one batch per tx to avoid updating the same key twice
312
+ const batchesOfPaddedPublicDataWrites: PublicDataTreeLeaf[][] = [];
313
+ for (const txEffect of paddedTxEffects) {
314
+ const batch: PublicDataTreeLeaf[] = Array(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX).fill(
315
+ PublicDataTreeLeaf.empty(),
316
+ );
317
+ for (const [i, write] of txEffect.publicDataWrites.entries()) {
318
+ batch[i] = new PublicDataTreeLeaf(write.leafIndex, write.newValue);
319
+ }
320
+
321
+ batchesOfPaddedPublicDataWrites.push(batch);
322
+ }
323
+
324
+ return await this.call(WorldStateMessageType.SYNC_BLOCK, {
325
+ blockHash: l2Block.hash(),
326
+ paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
327
+ paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
328
+ paddedNullifiers: paddedNullifiers.map(serializeLeaf),
329
+ batchesOfPaddedPublicDataWrites: batchesOfPaddedPublicDataWrites.map(batch => batch.map(serializeLeaf)),
330
+ blockStateRef: blockStateReference(l2Block.header.state),
331
+ });
332
+ }
333
+
334
+ async rollback(): Promise<void> {
335
+ await this.call(WorldStateMessageType.ROLLBACK, void 0);
336
+ }
337
+
338
+ async updateArchive(header: Header, _includeUncommitted: boolean): Promise<void> {
339
+ await this.call(WorldStateMessageType.UPDATE_ARCHIVE, {
340
+ blockHash: header.hash().toBuffer(),
341
+ blockStateRef: blockStateReference(header.state),
342
+ });
343
+ }
344
+
345
+ updateLeaf<ID extends IndexedTreeId>(
346
+ _treeId: ID,
347
+ _leaf: NullifierLeafPreimage | Buffer,
348
+ _index: bigint,
349
+ ): Promise<void> {
350
+ return Promise.reject(new Error('Method not implemented'));
351
+ }
352
+
353
+ private call<T extends WorldStateMessageType>(
354
+ messageType: T,
355
+ body: WorldStateRequest[T],
356
+ ): Promise<WorldStateResponse[T]> {
357
+ return this.queue.put(async () => {
358
+ const request = new TypedMessage(messageType, new MessageHeader({ messageId: this.nextMessageId++ }), body);
359
+
360
+ const encodedRequest = this.encoder.encode(request);
361
+ const encodedResponse = await this.instance.call(encodedRequest);
362
+
363
+ const buf = Buffer.isBuffer(encodedResponse)
364
+ ? encodedResponse
365
+ : isAnyArrayBuffer(encodedResponse)
366
+ ? Buffer.from(encodedResponse)
367
+ : encodedResponse;
368
+
369
+ if (!Buffer.isBuffer(buf)) {
370
+ throw new TypeError(
371
+ 'Invalid encoded response: expected Buffer or ArrayBuffer, got ' +
372
+ (encodedResponse === null ? 'null' : typeof encodedResponse),
373
+ );
374
+ }
375
+
376
+ const decodedResponse = this.decoder.unpack(buf);
377
+ if (!TypedMessage.isTypedMessageLike(decodedResponse)) {
378
+ throw new TypeError(
379
+ 'Invalid response: expected TypedMessageLike, got ' +
380
+ (decodedResponse === null ? 'null' : typeof decodedResponse),
381
+ );
382
+ }
383
+
384
+ const response = TypedMessage.fromMessagePack<T, WorldStateResponse[T]>(decodedResponse);
385
+
386
+ if (response.header.requestId !== request.header.messageId) {
387
+ throw new Error(
388
+ 'Response ID does not match request: ' + response.header.requestId + ' != ' + request.header.messageId,
389
+ );
390
+ }
391
+
392
+ if (response.msgType !== messageType) {
393
+ throw new Error('Invalid response message type: ' + response.msgType + ' != ' + messageType);
394
+ }
395
+
396
+ return response.value;
397
+ });
398
+ }
399
+
400
+ public async stop(): Promise<void> {
401
+ await this.queue.end();
402
+ }
403
+
404
+ public delete(): Promise<void> {
405
+ return Promise.reject(new Error('Method not implemented'));
406
+ }
407
+
408
+ public fork(): Promise<MerkleTreeDb> {
409
+ return Promise.reject(new Error('Method not implemented'));
410
+ }
411
+ }
412
+
413
+ function hydrateLeaf<ID extends MerkleTreeId>(treeId: ID, leaf: Fr | Buffer) {
414
+ if (leaf instanceof Fr) {
415
+ return leaf;
416
+ } else if (treeId === MerkleTreeId.NULLIFIER_TREE) {
417
+ return NullifierLeaf.fromBuffer(leaf);
418
+ } else if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
419
+ return PublicDataTreeLeaf.fromBuffer(leaf);
420
+ } else {
421
+ return Fr.fromBuffer(leaf);
422
+ }
423
+ }
424
+
425
+ function serializeLeaf(leaf: Fr | NullifierLeaf | PublicDataTreeLeaf): SerializedLeafValue {
426
+ if (leaf instanceof Fr) {
427
+ return leaf.toBuffer();
428
+ } else if (leaf instanceof NullifierLeaf) {
429
+ return { value: leaf.nullifier.toBuffer() };
430
+ } else {
431
+ return { value: leaf.value.toBuffer(), slot: leaf.slot.toBuffer() };
432
+ }
433
+ }
434
+
435
+ function deserializeLeafValue(leaf: SerializedLeafValue): Fr | NullifierLeaf | PublicDataTreeLeaf {
436
+ if (Buffer.isBuffer(leaf)) {
437
+ return Fr.fromBuffer(leaf);
438
+ } else if ('slot' in leaf) {
439
+ return new PublicDataTreeLeaf(Fr.fromBuffer(leaf.slot), Fr.fromBuffer(leaf.value));
440
+ } else {
441
+ return new NullifierLeaf(Fr.fromBuffer(leaf.value));
442
+ }
443
+ }
444
+
445
+ function deserializeIndexedLeaf(leaf: SerializedIndexedLeaf): IndexedTreeLeafPreimage {
446
+ if ('slot' in leaf.value) {
447
+ return new PublicDataTreeLeafPreimage(
448
+ Fr.fromBuffer(leaf.value.slot),
449
+ Fr.fromBuffer(leaf.value.value),
450
+ Fr.fromBuffer(leaf.nextValue),
451
+ BigInt(leaf.nextIndex),
452
+ );
453
+ } else if ('value' in leaf.value) {
454
+ return new NullifierLeafPreimage(
455
+ Fr.fromBuffer(leaf.value.value),
456
+ Fr.fromBuffer(leaf.nextValue),
457
+ BigInt(leaf.nextIndex),
458
+ );
459
+ } else {
460
+ throw new Error('Invalid leaf type');
461
+ }
462
+ }
@@ -17,12 +17,12 @@ import { type AztecKVStore, type AztecSingleton } from '@aztec/kv-store';
17
17
  import { openTmpStore } from '@aztec/kv-store/utils';
18
18
  import { SHA256Trunc, StandardTree } from '@aztec/merkle-tree';
19
19
 
20
- import { type MerkleTrees } from '../world-state-db/index.js';
21
20
  import {
22
21
  MerkleTreeAdminOperationsFacade,
23
22
  MerkleTreeOperationsFacade,
24
23
  } from '../world-state-db/merkle_tree_operations_facade.js';
25
24
  import { MerkleTreeSnapshotOperationsFacade } from '../world-state-db/merkle_tree_snapshot_operations_facade.js';
25
+ import { type MerkleTrees } from '../world-state-db/merkle_trees.js';
26
26
  import { type WorldStateConfig } from './config.js';
27
27
  import {
28
28
  WorldStateRunningState,
@@ -77,4 +77,7 @@ export type MerkleTreeAdminDb = {
77
77
 
78
78
  /** Deletes this database. */
79
79
  delete(): Promise<void>;
80
+
81
+ /** Stops the database */
82
+ stop(): Promise<void>;
80
83
  };