@aztec/world-state 0.0.0-test.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dest/index.d.ts +5 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +4 -0
- package/dest/instrumentation/instrumentation.d.ts +22 -0
- package/dest/instrumentation/instrumentation.d.ts.map +1 -0
- package/dest/instrumentation/instrumentation.js +117 -0
- package/dest/native/fork_checkpoint.d.ts +10 -0
- package/dest/native/fork_checkpoint.d.ts.map +1 -0
- package/dest/native/fork_checkpoint.js +26 -0
- package/dest/native/index.d.ts +3 -0
- package/dest/native/index.d.ts.map +1 -0
- package/dest/native/index.js +2 -0
- package/dest/native/merkle_trees_facade.d.ts +42 -0
- package/dest/native/merkle_trees_facade.d.ts.map +1 -0
- package/dest/native/merkle_trees_facade.js +240 -0
- package/dest/native/message.d.ts +331 -0
- package/dest/native/message.d.ts.map +1 -0
- package/dest/native/message.js +192 -0
- package/dest/native/native_world_state.d.ts +57 -0
- package/dest/native/native_world_state.d.ts.map +1 -0
- package/dest/native/native_world_state.js +229 -0
- package/dest/native/native_world_state_instance.d.ts +33 -0
- package/dest/native/native_world_state_instance.d.ts.map +1 -0
- package/dest/native/native_world_state_instance.js +164 -0
- package/dest/native/world_state_ops_queue.d.ts +19 -0
- package/dest/native/world_state_ops_queue.d.ts.map +1 -0
- package/dest/native/world_state_ops_queue.js +146 -0
- package/dest/synchronizer/config.d.ts +23 -0
- package/dest/synchronizer/config.d.ts.map +1 -0
- package/dest/synchronizer/config.js +39 -0
- package/dest/synchronizer/factory.d.ts +12 -0
- package/dest/synchronizer/factory.d.ts.map +1 -0
- package/dest/synchronizer/factory.js +24 -0
- package/dest/synchronizer/index.d.ts +3 -0
- package/dest/synchronizer/index.d.ts.map +1 -0
- package/dest/synchronizer/index.js +2 -0
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +79 -0
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -0
- package/dest/synchronizer/server_world_state_synchronizer.js +277 -0
- package/dest/test/index.d.ts +2 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +1 -0
- package/dest/test/utils.d.ts +19 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +99 -0
- package/dest/testing.d.ts +10 -0
- package/dest/testing.d.ts.map +1 -0
- package/dest/testing.js +37 -0
- package/dest/world-state-db/index.d.ts +3 -0
- package/dest/world-state-db/index.d.ts.map +1 -0
- package/dest/world-state-db/index.js +1 -0
- package/dest/world-state-db/merkle_tree_db.d.ts +68 -0
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -0
- package/dest/world-state-db/merkle_tree_db.js +17 -0
- package/package.json +98 -0
- package/src/index.ts +4 -0
- package/src/instrumentation/instrumentation.ts +174 -0
- package/src/native/fork_checkpoint.ts +30 -0
- package/src/native/index.ts +2 -0
- package/src/native/merkle_trees_facade.ts +331 -0
- package/src/native/message.ts +541 -0
- package/src/native/native_world_state.ts +317 -0
- package/src/native/native_world_state_instance.ts +238 -0
- package/src/native/world_state_ops_queue.ts +190 -0
- package/src/synchronizer/config.ts +68 -0
- package/src/synchronizer/factory.ts +53 -0
- package/src/synchronizer/index.ts +2 -0
- package/src/synchronizer/server_world_state_synchronizer.ts +344 -0
- package/src/test/index.ts +1 -0
- package/src/test/utils.ts +153 -0
- package/src/testing.ts +60 -0
- package/src/world-state-db/index.ts +3 -0
- package/src/world-state-db/merkle_tree_db.ts +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# World State
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The primary functions of the world state package are to maintain the collection of Merkle Trees comprising the global state of the system and to offer an interface with which the trees can be queried.
|
|
6
|
+
|
|
7
|
+
### The Merkle Tree DB, a collection of Merkle Trees of varying types.
|
|
8
|
+
|
|
9
|
+
As of the time of writing the collection consisted of the following trees.
|
|
10
|
+
|
|
11
|
+
#### Standard 'Append Only' trees
|
|
12
|
+
|
|
13
|
+
- The Contract Tree. Every contract created within the system has a 'Function Tree', a tree of leaves generated from the functions on the contract. The root of the function tree is inserted as a leaf in the contracts tree.
|
|
14
|
+
- The Contract Tree Roots Tree. A tree whose leaves are the historical roots of the contract tree.
|
|
15
|
+
- The Note Hash Tree. A tree whose leaves are the note hashes of notes generated by the private contract function calls within the system.
|
|
16
|
+
- The Note Hash Tree Roots Tree. A tree whose leaves are the historical roots of the note hash tree.
|
|
17
|
+
|
|
18
|
+
#### Indexed trees
|
|
19
|
+
|
|
20
|
+
- The Nullifier Tree. A tree whose leaves contain the consumed values (commitments, tx hashes etc) of the system.
|
|
21
|
+
|
|
22
|
+
#### Sparse trees
|
|
23
|
+
|
|
24
|
+
- The Public Data Tree. A tree whose leaves are the current value of every item of public state in the system, addressed as `leaf_index = hash(contract_address, storage_slot_in_contract)`
|
|
25
|
+
|
|
26
|
+
### The Synchronizer
|
|
27
|
+
|
|
28
|
+
The synchronizer's role is to periodically poll for new block information and reconcile that information with the current state of the Merkle Trees.
|
|
29
|
+
|
|
30
|
+
Once a new block is received, the synchronizer checks the uncommitted root values of all of the trees against the roots published as part of the block. If they are all equal, the tree state is committed. If they are not equal, the tree states are rolled back to the last committed state before the published data is inserted and committed.
|
|
31
|
+
|
|
32
|
+
### The Merkle Tree Interface
|
|
33
|
+
|
|
34
|
+
The interface to the Merkle Tree DB offers a unified asynchronous API to the set of trees available. Reads from the Merkle Trees need to be marked as to whether they should include uncommitted state. For this reason, the MerkleTreeReadOperationsFacade exists to abstract this detail away from the end consumer.
|
|
35
|
+
|
|
36
|
+
# Building/Testing
|
|
37
|
+
|
|
38
|
+
Building the package is as simple as calling `yarn build` from the package root.
|
|
39
|
+
|
|
40
|
+
Running `yarn test` will execute the packages unit tests.
|
package/dest/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC"}
|
package/dest/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
2
|
+
import { WorldStateMessageType, type WorldStateStatusFull } from '../native/message.js';
|
|
3
|
+
export declare class WorldStateInstrumentation {
|
|
4
|
+
readonly telemetry: TelemetryClient;
|
|
5
|
+
private log;
|
|
6
|
+
private dbMapSize;
|
|
7
|
+
private treeSize;
|
|
8
|
+
private unfinalisedHeight;
|
|
9
|
+
private finalisedHeight;
|
|
10
|
+
private oldestBlock;
|
|
11
|
+
private dbNumItems;
|
|
12
|
+
private dbUsedSize;
|
|
13
|
+
private requestHistogram;
|
|
14
|
+
private criticalErrors;
|
|
15
|
+
constructor(telemetry: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
|
|
16
|
+
private updateTreeStats;
|
|
17
|
+
private updateTreeDBStats;
|
|
18
|
+
updateWorldStateMetrics(worldStateStatus: WorldStateStatusFull): void;
|
|
19
|
+
recordRoundTrip(timeUs: number, request: WorldStateMessageType): void;
|
|
20
|
+
incCriticalErrors(errorType: 'synch_pending_block' | 'finalize_block' | 'prune_pending_block' | 'prune_historical_block'): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/instrumentation/instrumentation.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,eAAe,EAGrB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAIL,qBAAqB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,sBAAsB,CAAC;AAa9B,qBAAa,yBAAyB;aAWR,SAAS,EAAE,eAAe;IAAE,OAAO,CAAC,GAAG;IAVnE,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,cAAc,CAAgB;gBAEV,SAAS,EAAE,eAAe,EAAU,GAAG,yCAA8C;IAiDjH,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,iBAAiB;IAWlB,uBAAuB,CAAC,gBAAgB,EAAE,oBAAoB;IAgC9D,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;IAQ9D,iBAAiB,CACtB,SAAS,EAAE,qBAAqB,GAAG,gBAAgB,GAAG,qBAAqB,GAAG,wBAAwB;CAMzG"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
3
|
+
import { Attributes, Metrics, ValueType } from '@aztec/telemetry-client';
|
|
4
|
+
import { WorldStateMessageType } from '../native/message.js';
|
|
5
|
+
const durationTrackDenylist = new Set([
|
|
6
|
+
WorldStateMessageType.GET_INITIAL_STATE_REFERENCE,
|
|
7
|
+
WorldStateMessageType.CLOSE,
|
|
8
|
+
// these aren't used anymore, should be removed from the API
|
|
9
|
+
WorldStateMessageType.COMMIT,
|
|
10
|
+
WorldStateMessageType.ROLLBACK
|
|
11
|
+
]);
|
|
12
|
+
export class WorldStateInstrumentation {
|
|
13
|
+
telemetry;
|
|
14
|
+
log;
|
|
15
|
+
dbMapSize;
|
|
16
|
+
treeSize;
|
|
17
|
+
unfinalisedHeight;
|
|
18
|
+
finalisedHeight;
|
|
19
|
+
oldestBlock;
|
|
20
|
+
dbNumItems;
|
|
21
|
+
dbUsedSize;
|
|
22
|
+
requestHistogram;
|
|
23
|
+
criticalErrors;
|
|
24
|
+
constructor(telemetry, log = createLogger('world-state:instrumentation')){
|
|
25
|
+
this.telemetry = telemetry;
|
|
26
|
+
this.log = log;
|
|
27
|
+
const meter = telemetry.getMeter('World State');
|
|
28
|
+
this.dbMapSize = meter.createGauge(Metrics.WORLD_STATE_DB_MAP_SIZE, {
|
|
29
|
+
description: `The current configured map size for each merkle tree`,
|
|
30
|
+
valueType: ValueType.INT
|
|
31
|
+
});
|
|
32
|
+
this.treeSize = meter.createGauge(Metrics.WORLD_STATE_TREE_SIZE, {
|
|
33
|
+
description: `The current number of leaves in each merkle tree`,
|
|
34
|
+
valueType: ValueType.INT
|
|
35
|
+
});
|
|
36
|
+
this.unfinalisedHeight = meter.createGauge(Metrics.WORLD_STATE_UNFINALISED_HEIGHT, {
|
|
37
|
+
description: `The unfinalised block height of each merkle tree`,
|
|
38
|
+
valueType: ValueType.INT
|
|
39
|
+
});
|
|
40
|
+
this.finalisedHeight = meter.createGauge(Metrics.WORLD_STATE_FINALISED_HEIGHT, {
|
|
41
|
+
description: `The finalised block height of each merkle tree`,
|
|
42
|
+
valueType: ValueType.INT
|
|
43
|
+
});
|
|
44
|
+
this.oldestBlock = meter.createGauge(Metrics.WORLD_STATE_OLDEST_BLOCK, {
|
|
45
|
+
description: `The oldest historical block of each merkle tree`,
|
|
46
|
+
valueType: ValueType.INT
|
|
47
|
+
});
|
|
48
|
+
this.dbUsedSize = meter.createGauge(Metrics.WORLD_STATE_DB_USED_SIZE, {
|
|
49
|
+
description: `The current used database size for each db of each merkle tree`,
|
|
50
|
+
valueType: ValueType.INT
|
|
51
|
+
});
|
|
52
|
+
this.dbNumItems = meter.createGauge(Metrics.WORLD_STATE_DB_NUM_ITEMS, {
|
|
53
|
+
description: `The current number of items in each database of each merkle tree`,
|
|
54
|
+
valueType: ValueType.INT
|
|
55
|
+
});
|
|
56
|
+
this.requestHistogram = meter.createHistogram(Metrics.WORLD_STATE_REQUEST_TIME, {
|
|
57
|
+
description: 'The round trip time of world state requests',
|
|
58
|
+
unit: 'us',
|
|
59
|
+
valueType: ValueType.INT
|
|
60
|
+
});
|
|
61
|
+
this.criticalErrors = meter.createUpDownCounter(Metrics.WORLD_STATE_CRITICAL_ERROR_COUNT, {
|
|
62
|
+
description: 'The number of critical errors in the world state',
|
|
63
|
+
valueType: ValueType.INT
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
updateTreeStats(treeDbStats, treeMeta, tree) {
|
|
67
|
+
this.dbMapSize.record(Number(treeDbStats.mapSize), {
|
|
68
|
+
[Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree]
|
|
69
|
+
});
|
|
70
|
+
this.treeSize.record(Number(treeMeta.size), {
|
|
71
|
+
[Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree]
|
|
72
|
+
});
|
|
73
|
+
this.unfinalisedHeight.record(Number(treeMeta.unfinalisedBlockHeight), {
|
|
74
|
+
[Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree]
|
|
75
|
+
});
|
|
76
|
+
this.finalisedHeight.record(Number(treeMeta.finalisedBlockHeight), {
|
|
77
|
+
[Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree]
|
|
78
|
+
});
|
|
79
|
+
this.oldestBlock.record(Number(treeMeta.oldestHistoricBlock), {
|
|
80
|
+
[Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree]
|
|
81
|
+
});
|
|
82
|
+
this.updateTreeDBStats(treeDbStats.blockIndicesDBStats, 'block_indices', tree);
|
|
83
|
+
this.updateTreeDBStats(treeDbStats.blocksDBStats, 'blocks', tree);
|
|
84
|
+
this.updateTreeDBStats(treeDbStats.leafIndicesDBStats, 'leaf_indices', tree);
|
|
85
|
+
this.updateTreeDBStats(treeDbStats.leafPreimagesDBStats, 'leaf_preimage', tree);
|
|
86
|
+
this.updateTreeDBStats(treeDbStats.nodesDBStats, 'nodes', tree);
|
|
87
|
+
}
|
|
88
|
+
updateTreeDBStats(dbStats, dbType, tree) {
|
|
89
|
+
this.dbNumItems.record(Number(dbStats.numDataItems), {
|
|
90
|
+
[Attributes.WS_DB_DATA_TYPE]: dbType,
|
|
91
|
+
[Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree]
|
|
92
|
+
});
|
|
93
|
+
this.dbUsedSize.record(Number(dbStats.totalUsedSize), {
|
|
94
|
+
[Attributes.WS_DB_DATA_TYPE]: dbType,
|
|
95
|
+
[Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
updateWorldStateMetrics(worldStateStatus) {
|
|
99
|
+
this.updateTreeStats(worldStateStatus.dbStats.archiveTreeStats, worldStateStatus.meta.archiveTreeMeta, MerkleTreeId.ARCHIVE);
|
|
100
|
+
this.updateTreeStats(worldStateStatus.dbStats.messageTreeStats, worldStateStatus.meta.messageTreeMeta, MerkleTreeId.L1_TO_L2_MESSAGE_TREE);
|
|
101
|
+
this.updateTreeStats(worldStateStatus.dbStats.noteHashTreeStats, worldStateStatus.meta.noteHashTreeMeta, MerkleTreeId.NOTE_HASH_TREE);
|
|
102
|
+
this.updateTreeStats(worldStateStatus.dbStats.nullifierTreeStats, worldStateStatus.meta.nullifierTreeMeta, MerkleTreeId.NULLIFIER_TREE);
|
|
103
|
+
this.updateTreeStats(worldStateStatus.dbStats.publicDataTreeStats, worldStateStatus.meta.publicDataTreeMeta, MerkleTreeId.PUBLIC_DATA_TREE);
|
|
104
|
+
}
|
|
105
|
+
recordRoundTrip(timeUs, request) {
|
|
106
|
+
if (!durationTrackDenylist.has(request)) {
|
|
107
|
+
this.requestHistogram.record(Math.ceil(timeUs), {
|
|
108
|
+
[Attributes.WORLD_STATE_REQUEST_TYPE]: WorldStateMessageType[request]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
incCriticalErrors(errorType) {
|
|
113
|
+
this.criticalErrors.add(1, {
|
|
114
|
+
[Attributes.ERROR_TYPE]: errorType
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MerkleTreeCheckpointOperations } from '@aztec/stdlib/interfaces/server';
|
|
2
|
+
export declare class ForkCheckpoint {
|
|
3
|
+
private readonly fork;
|
|
4
|
+
private completed;
|
|
5
|
+
private constructor();
|
|
6
|
+
static new(fork: MerkleTreeCheckpointOperations): Promise<ForkCheckpoint>;
|
|
7
|
+
commit(): Promise<void>;
|
|
8
|
+
revert(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=fork_checkpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fork_checkpoint.d.ts","sourceRoot":"","sources":["../../src/native/fork_checkpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AAEtF,qBAAa,cAAc;IAGL,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFzC,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO;WAEM,GAAG,CAAC,IAAI,EAAE,8BAA8B,GAAG,OAAO,CAAC,cAAc,CAAC;IAKzE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IASvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ9B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class ForkCheckpoint {
|
|
2
|
+
fork;
|
|
3
|
+
completed;
|
|
4
|
+
constructor(fork){
|
|
5
|
+
this.fork = fork;
|
|
6
|
+
this.completed = false;
|
|
7
|
+
}
|
|
8
|
+
static async new(fork) {
|
|
9
|
+
await fork.createCheckpoint();
|
|
10
|
+
return new ForkCheckpoint(fork);
|
|
11
|
+
}
|
|
12
|
+
async commit() {
|
|
13
|
+
if (this.completed) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await this.fork.commitCheckpoint();
|
|
17
|
+
this.completed = true;
|
|
18
|
+
}
|
|
19
|
+
async revert() {
|
|
20
|
+
if (this.completed) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await this.fork.revertCheckpoint();
|
|
24
|
+
this.completed = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
4
|
+
import { type IndexedTreeLeafPreimage, SiblingPath } from '@aztec/foundation/trees';
|
|
5
|
+
import type { BatchInsertionResult, IndexedTreeId, MerkleTreeLeafType, MerkleTreeReadOperations, MerkleTreeWriteOperations, SequentialInsertionResult, TreeInfo } from '@aztec/stdlib/interfaces/server';
|
|
6
|
+
import { MerkleTreeId, NullifierLeaf, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
7
|
+
import { type BlockHeader, StateReference } from '@aztec/stdlib/tx';
|
|
8
|
+
import { type SerializedLeafValue, type WorldStateRevision } from './message.js';
|
|
9
|
+
import type { NativeWorldStateInstance } from './native_world_state_instance.js';
|
|
10
|
+
export declare class MerkleTreesFacade implements MerkleTreeReadOperations {
|
|
11
|
+
protected readonly instance: NativeWorldStateInstance;
|
|
12
|
+
protected readonly initialHeader: BlockHeader;
|
|
13
|
+
protected readonly revision: WorldStateRevision;
|
|
14
|
+
constructor(instance: NativeWorldStateInstance, initialHeader: BlockHeader, revision: WorldStateRevision);
|
|
15
|
+
getInitialHeader(): BlockHeader;
|
|
16
|
+
findLeafIndices(treeId: MerkleTreeId, values: MerkleTreeLeafType<MerkleTreeId>[]): Promise<(bigint | undefined)[]>;
|
|
17
|
+
findLeafIndicesAfter(treeId: MerkleTreeId, leaves: MerkleTreeLeafType<MerkleTreeId>[], startIndex: bigint): Promise<(bigint | undefined)[]>;
|
|
18
|
+
getLeafPreimage(treeId: IndexedTreeId, leafIndex: bigint): Promise<IndexedTreeLeafPreimage | undefined>;
|
|
19
|
+
getLeafValue<ID extends MerkleTreeId>(treeId: ID, leafIndex: bigint): Promise<MerkleTreeLeafType<ID> | undefined>;
|
|
20
|
+
getPreviousValueIndex(treeId: IndexedTreeId, value: bigint): Promise<{
|
|
21
|
+
index: bigint;
|
|
22
|
+
alreadyPresent: boolean;
|
|
23
|
+
} | undefined>;
|
|
24
|
+
getSiblingPath<N extends number>(treeId: MerkleTreeId, leafIndex: bigint): Promise<SiblingPath<N>>;
|
|
25
|
+
getStateReference(): Promise<StateReference>;
|
|
26
|
+
getInitialStateReference(): Promise<StateReference>;
|
|
27
|
+
getTreeInfo(treeId: MerkleTreeId): Promise<TreeInfo>;
|
|
28
|
+
getBlockNumbersForLeafIndices<ID extends MerkleTreeId>(treeId: ID, leafIndices: bigint[]): Promise<(bigint | undefined)[]>;
|
|
29
|
+
}
|
|
30
|
+
export declare class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTreeWriteOperations {
|
|
31
|
+
constructor(instance: NativeWorldStateInstance, initialHeader: BlockHeader, revision: WorldStateRevision);
|
|
32
|
+
updateArchive(header: BlockHeader): Promise<void>;
|
|
33
|
+
appendLeaves<ID extends MerkleTreeId>(treeId: ID, leaves: MerkleTreeLeafType<ID>[]): Promise<void>;
|
|
34
|
+
batchInsert<TreeHeight extends number, SubtreeSiblingPathHeight extends number, ID extends IndexedTreeId>(treeId: ID, rawLeaves: Buffer[], subtreeHeight: number): Promise<BatchInsertionResult<TreeHeight, SubtreeSiblingPathHeight>>;
|
|
35
|
+
sequentialInsert<TreeHeight extends number, ID extends IndexedTreeId>(treeId: ID, rawLeaves: Buffer[]): Promise<SequentialInsertionResult<TreeHeight>>;
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
createCheckpoint(): Promise<void>;
|
|
38
|
+
commitCheckpoint(): Promise<void>;
|
|
39
|
+
revertCheckpoint(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export declare function serializeLeaf(leaf: Fr | NullifierLeaf | PublicDataTreeLeaf): SerializedLeafValue;
|
|
42
|
+
//# sourceMappingURL=merkle_trees_facade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merkle_trees_facade.d.ts","sourceRoot":"","sources":["../../src/native/merkle_trees_facade.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAE9C,OAAO,EAAE,KAAK,uBAAuB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,wBAAwB,EACxB,yBAAyB,EACzB,yBAAyB,EACzB,QAAQ,EACT,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,YAAY,EACZ,aAAa,EAEb,kBAAkB,EAEnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,WAAW,EAAyB,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAI3F,OAAO,EAEL,KAAK,mBAAmB,EAExB,KAAK,kBAAkB,EAGxB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAEjF,qBAAa,iBAAkB,YAAW,wBAAwB;IAE9D,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,wBAAwB;IACrD,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,WAAW;IAC7C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB;gBAF5B,QAAQ,EAAE,wBAAwB,EAClC,aAAa,EAAE,WAAW,EAC1B,QAAQ,EAAE,kBAAkB;IAGjD,gBAAgB,IAAI,WAAW;IAI/B,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,kBAAkB,CAAC,YAAY,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAI5G,oBAAoB,CACxB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,kBAAkB,CAAC,YAAY,CAAC,EAAE,EAC1C,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAiB5B,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC;IAUvG,YAAY,CAAC,EAAE,SAAS,YAAY,EACxC,MAAM,EAAE,EAAE,EACV,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAmBxC,qBAAqB,CACzB,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IAY5D,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAUlG,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC;IAe5C,wBAAwB,IAAI,OAAO,CAAC,cAAc,CAAC;IAanD,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC;IAcpD,6BAA6B,CAAC,EAAE,SAAS,YAAY,EACzD,MAAM,EAAE,EAAE,EACV,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;CASnC;AAED,qBAAa,qBAAsB,SAAQ,iBAAkB,YAAW,yBAAyB;gBACnF,QAAQ,EAAE,wBAAwB,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,kBAAkB;IAKlG,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQjD,YAAY,CAAC,EAAE,SAAS,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlG,WAAW,CAAC,UAAU,SAAS,MAAM,EAAE,wBAAwB,SAAS,MAAM,EAAE,EAAE,SAAS,aAAa,EAC5G,MAAM,EAAE,EAAE,EACV,SAAS,EAAE,MAAM,EAAE,EACnB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,oBAAoB,CAAC,UAAU,EAAE,wBAAwB,CAAC,CAAC;IA2BhE,gBAAgB,CAAC,UAAU,SAAS,MAAM,EAAE,EAAE,SAAS,aAAa,EACxE,MAAM,EAAE,EAAE,EACV,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,yBAAyB,CAAC,UAAU,CAAC,CAAC;IAsBpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/C;AAcD,wBAAgB,aAAa,CAAC,IAAI,EAAE,EAAE,GAAG,aAAa,GAAG,kBAAkB,GAAG,mBAAmB,CAQhG"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
+
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
3
|
+
import { SiblingPath } from '@aztec/foundation/trees';
|
|
4
|
+
import { MerkleTreeId, NullifierLeaf, NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
|
|
5
|
+
import { PartialStateReference, StateReference } from '@aztec/stdlib/tx';
|
|
6
|
+
import assert from 'assert';
|
|
7
|
+
import { WorldStateMessageType, blockStateReference, treeStateReferenceToSnapshot } from './message.js';
|
|
8
|
+
export class MerkleTreesFacade {
|
|
9
|
+
instance;
|
|
10
|
+
initialHeader;
|
|
11
|
+
revision;
|
|
12
|
+
constructor(instance, initialHeader, revision){
|
|
13
|
+
this.instance = instance;
|
|
14
|
+
this.initialHeader = initialHeader;
|
|
15
|
+
this.revision = revision;
|
|
16
|
+
}
|
|
17
|
+
getInitialHeader() {
|
|
18
|
+
return this.initialHeader;
|
|
19
|
+
}
|
|
20
|
+
findLeafIndices(treeId, values) {
|
|
21
|
+
return this.findLeafIndicesAfter(treeId, values, 0n);
|
|
22
|
+
}
|
|
23
|
+
async findLeafIndicesAfter(treeId, leaves, startIndex) {
|
|
24
|
+
const response = await this.instance.call(WorldStateMessageType.FIND_LEAF_INDICES, {
|
|
25
|
+
leaves: leaves.map((leaf)=>serializeLeaf(hydrateLeaf(treeId, leaf))),
|
|
26
|
+
revision: this.revision,
|
|
27
|
+
treeId,
|
|
28
|
+
startIndex
|
|
29
|
+
});
|
|
30
|
+
return response.indices.map((index)=>{
|
|
31
|
+
if (typeof index === 'number' || typeof index === 'bigint') {
|
|
32
|
+
return BigInt(index);
|
|
33
|
+
} else {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async getLeafPreimage(treeId, leafIndex) {
|
|
39
|
+
const resp = await this.instance.call(WorldStateMessageType.GET_LEAF_PREIMAGE, {
|
|
40
|
+
leafIndex,
|
|
41
|
+
revision: this.revision,
|
|
42
|
+
treeId
|
|
43
|
+
});
|
|
44
|
+
return resp ? deserializeIndexedLeaf(resp) : undefined;
|
|
45
|
+
}
|
|
46
|
+
async getLeafValue(treeId, leafIndex) {
|
|
47
|
+
const resp = await this.instance.call(WorldStateMessageType.GET_LEAF_VALUE, {
|
|
48
|
+
leafIndex,
|
|
49
|
+
revision: this.revision,
|
|
50
|
+
treeId
|
|
51
|
+
});
|
|
52
|
+
if (!resp) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const leaf = deserializeLeafValue(resp);
|
|
56
|
+
if (leaf instanceof Fr) {
|
|
57
|
+
return leaf;
|
|
58
|
+
} else {
|
|
59
|
+
return leaf.toBuffer();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async getPreviousValueIndex(treeId, value) {
|
|
63
|
+
const resp = await this.instance.call(WorldStateMessageType.FIND_LOW_LEAF, {
|
|
64
|
+
key: new Fr(value),
|
|
65
|
+
revision: this.revision,
|
|
66
|
+
treeId
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
alreadyPresent: resp.alreadyPresent,
|
|
70
|
+
index: BigInt(resp.index)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async getSiblingPath(treeId, leafIndex) {
|
|
74
|
+
const siblingPath = await this.instance.call(WorldStateMessageType.GET_SIBLING_PATH, {
|
|
75
|
+
leafIndex,
|
|
76
|
+
revision: this.revision,
|
|
77
|
+
treeId
|
|
78
|
+
});
|
|
79
|
+
return new SiblingPath(siblingPath.length, siblingPath);
|
|
80
|
+
}
|
|
81
|
+
async getStateReference() {
|
|
82
|
+
const resp = await this.instance.call(WorldStateMessageType.GET_STATE_REFERENCE, {
|
|
83
|
+
revision: this.revision
|
|
84
|
+
});
|
|
85
|
+
return new StateReference(treeStateReferenceToSnapshot(resp.state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]), new PartialStateReference(treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NOTE_HASH_TREE]), treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NULLIFIER_TREE]), treeStateReferenceToSnapshot(resp.state[MerkleTreeId.PUBLIC_DATA_TREE])));
|
|
86
|
+
}
|
|
87
|
+
async getInitialStateReference() {
|
|
88
|
+
const resp = await this.instance.call(WorldStateMessageType.GET_INITIAL_STATE_REFERENCE, {
|
|
89
|
+
canonical: true
|
|
90
|
+
});
|
|
91
|
+
return new StateReference(treeStateReferenceToSnapshot(resp.state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]), new PartialStateReference(treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NOTE_HASH_TREE]), treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NULLIFIER_TREE]), treeStateReferenceToSnapshot(resp.state[MerkleTreeId.PUBLIC_DATA_TREE])));
|
|
92
|
+
}
|
|
93
|
+
async getTreeInfo(treeId) {
|
|
94
|
+
const resp = await this.instance.call(WorldStateMessageType.GET_TREE_INFO, {
|
|
95
|
+
treeId: treeId,
|
|
96
|
+
revision: this.revision
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
depth: resp.depth,
|
|
100
|
+
root: resp.root,
|
|
101
|
+
size: BigInt(resp.size),
|
|
102
|
+
treeId
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async getBlockNumbersForLeafIndices(treeId, leafIndices) {
|
|
106
|
+
const response = await this.instance.call(WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES, {
|
|
107
|
+
treeId,
|
|
108
|
+
revision: this.revision,
|
|
109
|
+
leafIndices
|
|
110
|
+
});
|
|
111
|
+
return response.blockNumbers.map((x)=>x === undefined || x === null ? undefined : BigInt(x));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export class MerkleTreesForkFacade extends MerkleTreesFacade {
|
|
115
|
+
constructor(instance, initialHeader, revision){
|
|
116
|
+
assert.notEqual(revision.forkId, 0, 'Fork ID must be set');
|
|
117
|
+
assert.equal(revision.includeUncommitted, true, 'Fork must include uncommitted data');
|
|
118
|
+
super(instance, initialHeader, revision);
|
|
119
|
+
}
|
|
120
|
+
async updateArchive(header) {
|
|
121
|
+
await this.instance.call(WorldStateMessageType.UPDATE_ARCHIVE, {
|
|
122
|
+
forkId: this.revision.forkId,
|
|
123
|
+
blockHeaderHash: (await header.hash()).toBuffer(),
|
|
124
|
+
blockStateRef: blockStateReference(header.state)
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async appendLeaves(treeId, leaves) {
|
|
128
|
+
await this.instance.call(WorldStateMessageType.APPEND_LEAVES, {
|
|
129
|
+
leaves: leaves.map((leaf)=>leaf),
|
|
130
|
+
forkId: this.revision.forkId,
|
|
131
|
+
treeId
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async batchInsert(treeId, rawLeaves, subtreeHeight) {
|
|
135
|
+
const leaves = rawLeaves.map((leaf)=>hydrateLeaf(treeId, leaf)).map(serializeLeaf);
|
|
136
|
+
const resp = await this.instance.call(WorldStateMessageType.BATCH_INSERT, {
|
|
137
|
+
leaves,
|
|
138
|
+
treeId,
|
|
139
|
+
forkId: this.revision.forkId,
|
|
140
|
+
subtreeDepth: subtreeHeight
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
newSubtreeSiblingPath: new SiblingPath(resp.subtree_path.length, resp.subtree_path),
|
|
144
|
+
sortedNewLeaves: resp.sorted_leaves.map(([leaf])=>leaf).map(deserializeLeafValue).map(serializeToBuffer),
|
|
145
|
+
sortedNewLeavesIndexes: resp.sorted_leaves.map(([, index])=>index),
|
|
146
|
+
lowLeavesWitnessData: resp.low_leaf_witness_data.map((data)=>({
|
|
147
|
+
index: BigInt(data.index),
|
|
148
|
+
leafPreimage: deserializeIndexedLeaf(data.leaf),
|
|
149
|
+
siblingPath: new SiblingPath(data.path.length, data.path)
|
|
150
|
+
}))
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async sequentialInsert(treeId, rawLeaves) {
|
|
154
|
+
const leaves = rawLeaves.map((leaf)=>hydrateLeaf(treeId, leaf)).map(serializeLeaf);
|
|
155
|
+
const resp = await this.instance.call(WorldStateMessageType.SEQUENTIAL_INSERT, {
|
|
156
|
+
leaves,
|
|
157
|
+
treeId,
|
|
158
|
+
forkId: this.revision.forkId
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
lowLeavesWitnessData: resp.low_leaf_witness_data.map((data)=>({
|
|
162
|
+
index: BigInt(data.index),
|
|
163
|
+
leafPreimage: deserializeIndexedLeaf(data.leaf),
|
|
164
|
+
siblingPath: new SiblingPath(data.path.length, data.path)
|
|
165
|
+
})),
|
|
166
|
+
insertionWitnessData: resp.insertion_witness_data.map((data)=>({
|
|
167
|
+
index: BigInt(data.index),
|
|
168
|
+
leafPreimage: deserializeIndexedLeaf(data.leaf),
|
|
169
|
+
siblingPath: new SiblingPath(data.path.length, data.path)
|
|
170
|
+
}))
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async close() {
|
|
174
|
+
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
175
|
+
await this.instance.call(WorldStateMessageType.DELETE_FORK, {
|
|
176
|
+
forkId: this.revision.forkId
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async createCheckpoint() {
|
|
180
|
+
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
181
|
+
await this.instance.call(WorldStateMessageType.CREATE_CHECKPOINT, {
|
|
182
|
+
forkId: this.revision.forkId
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async commitCheckpoint() {
|
|
186
|
+
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
187
|
+
await this.instance.call(WorldStateMessageType.COMMIT_CHECKPOINT, {
|
|
188
|
+
forkId: this.revision.forkId
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async revertCheckpoint() {
|
|
192
|
+
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
193
|
+
await this.instance.call(WorldStateMessageType.REVERT_CHECKPOINT, {
|
|
194
|
+
forkId: this.revision.forkId
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function hydrateLeaf(treeId, leaf) {
|
|
199
|
+
if (leaf instanceof Fr) {
|
|
200
|
+
return leaf;
|
|
201
|
+
} else if (treeId === MerkleTreeId.NULLIFIER_TREE) {
|
|
202
|
+
return NullifierLeaf.fromBuffer(leaf);
|
|
203
|
+
} else if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
|
|
204
|
+
return PublicDataTreeLeaf.fromBuffer(leaf);
|
|
205
|
+
} else {
|
|
206
|
+
return Fr.fromBuffer(leaf);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export function serializeLeaf(leaf) {
|
|
210
|
+
if (leaf instanceof Fr) {
|
|
211
|
+
return leaf.toBuffer();
|
|
212
|
+
} else if (leaf instanceof NullifierLeaf) {
|
|
213
|
+
return {
|
|
214
|
+
value: leaf.nullifier.toBuffer()
|
|
215
|
+
};
|
|
216
|
+
} else {
|
|
217
|
+
return {
|
|
218
|
+
value: leaf.value.toBuffer(),
|
|
219
|
+
slot: leaf.slot.toBuffer()
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function deserializeLeafValue(leaf) {
|
|
224
|
+
if (Buffer.isBuffer(leaf)) {
|
|
225
|
+
return Fr.fromBuffer(leaf);
|
|
226
|
+
} else if ('slot' in leaf) {
|
|
227
|
+
return new PublicDataTreeLeaf(Fr.fromBuffer(leaf.slot), Fr.fromBuffer(leaf.value));
|
|
228
|
+
} else {
|
|
229
|
+
return new NullifierLeaf(Fr.fromBuffer(leaf.value));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function deserializeIndexedLeaf(leaf) {
|
|
233
|
+
if ('slot' in leaf.value) {
|
|
234
|
+
return new PublicDataTreeLeafPreimage(Fr.fromBuffer(leaf.value.slot), Fr.fromBuffer(leaf.value.value), Fr.fromBuffer(leaf.nextValue), BigInt(leaf.nextIndex));
|
|
235
|
+
} else if ('value' in leaf.value) {
|
|
236
|
+
return new NullifierLeafPreimage(Fr.fromBuffer(leaf.value.value), Fr.fromBuffer(leaf.nextValue), BigInt(leaf.nextIndex));
|
|
237
|
+
} else {
|
|
238
|
+
throw new Error('Invalid leaf type');
|
|
239
|
+
}
|
|
240
|
+
}
|