@aztec/merkle-tree 0.1.0-alpha17 → 0.1.0-alpha21
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/.tsbuildinfo +1 -1
- package/dest/interfaces/indexed_tree.d.ts +1 -8
- package/dest/interfaces/indexed_tree.d.ts.map +1 -1
- package/dest/new_tree.js +1 -1
- package/dest/standard_indexed_tree/standard_indexed_tree.d.ts +37 -22
- package/dest/standard_indexed_tree/standard_indexed_tree.d.ts.map +1 -1
- package/dest/standard_indexed_tree/standard_indexed_tree.js +78 -87
- package/dest/standard_indexed_tree/test/standard_indexed_tree.test.d.ts.map +1 -0
- package/dest/standard_indexed_tree/test/standard_indexed_tree.test.js +337 -0
- package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.d.ts +23 -0
- package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.d.ts.map +1 -0
- package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.js +59 -0
- package/dest/standard_tree/standard_tree.d.ts +0 -10
- package/dest/standard_tree/standard_tree.d.ts.map +1 -1
- package/dest/standard_tree/standard_tree.js +3 -38
- package/dest/tree_base.d.ts +22 -9
- package/dest/tree_base.d.ts.map +1 -1
- package/dest/tree_base.js +48 -5
- package/package.json +3 -3
- package/src/interfaces/indexed_tree.ts +0 -10
- package/src/new_tree.ts +1 -1
- package/src/standard_indexed_tree/standard_indexed_tree.ts +86 -96
- package/src/standard_indexed_tree/{standard_indexed_tree.test.ts → test/standard_indexed_tree.test.ts} +35 -9
- package/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts +63 -0
- package/src/standard_tree/standard_tree.ts +2 -40
- package/src/tree_base.ts +55 -8
- package/dest/standard_indexed_tree/standard_indexed_tree.test.d.ts.map +0 -1
- package/dest/standard_indexed_tree/standard_indexed_tree.test.js +0 -316
- /package/dest/standard_indexed_tree/{standard_indexed_tree.test.d.ts → test/standard_indexed_tree.test.d.ts} +0 -0
package/dest/tree_base.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree_base.d.ts","sourceRoot":"","sources":["../src/tree_base.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"tree_base.d.ts","sourceRoot":"","sources":["../src/tree_base.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAYzD,eAAO,MAAM,UAAU,SAAU,MAAM;;;;CAStC,CAAC;AAEF,eAAO,MAAM,YAAY,QAAyF,CAAC;AAEnH;;GAEG;AACH,8BAAsB,QAAS,YAAW,UAAU;IAQhD,SAAS,CAAC,EAAE,EAAE,OAAO;IACrB,SAAS,CAAC,MAAM,EAAE,MAAM;IACxB,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,KAAK;IACb,SAAS,CAAC,IAAI,EAAE,MAAM;IAXxB,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IACpC,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,KAAK,CAAiC;gBAGlC,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACX,IAAI,GAAE,MAAW,EAC3B,IAAI,CAAC,EAAE,MAAM;IAiBf;;;;OAIG;IACI,OAAO,CAAC,kBAAkB,EAAE,OAAO,GAAG,MAAM;IAInD;;;;OAIG;IACI,YAAY,CAAC,kBAAkB,EAAE,OAAO;IAI/C;;;OAGG;IACI,OAAO,IAAI,MAAM;IAIxB;;;OAGG;IACI,QAAQ,IAAI,MAAM;IAIzB;;;;;;OAMG;IACU,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAalH;;;OAGG;IACU,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAapC;;;OAGG;IACI,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAKhC;;;;;OAKG;IACI,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAI5F;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;;;OAIG;cACa,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAkBvE;;;;;;;OAOG;YACW,qBAAqB;IAYnC;;;;OAIG;YACW,KAAK;IAInB;;;;OAIG;IAEU,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxC;;;OAGG;cACa,SAAS,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC;IAS9D;;;;;;;;;;;;;;OAcG;cACa,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA+B9D"}
|
package/dest/tree_base.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SiblingPath } from './sibling_path/sibling_path.js';
|
|
2
2
|
import { toBigIntLE, toBufferLE } from '@aztec/foundation/bigint-buffer';
|
|
3
3
|
const MAX_DEPTH = 254;
|
|
4
|
-
|
|
4
|
+
const indexToKeyHash = (name, level, index) => `${name}:${level}:${index}`;
|
|
5
5
|
const encodeMeta = (root, depth, size) => {
|
|
6
6
|
const data = Buffer.alloc(36);
|
|
7
7
|
root.copy(data);
|
|
@@ -45,7 +45,7 @@ export class TreeBase {
|
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Returns the root of the tree.
|
|
48
|
-
* @param includeUncommitted - If true, root incorporating
|
|
48
|
+
* @param includeUncommitted - If true, root incorporating uncommitted changes is returned.
|
|
49
49
|
* @returns The root of the tree.
|
|
50
50
|
*/
|
|
51
51
|
getRoot(includeUncommitted) {
|
|
@@ -53,7 +53,7 @@ export class TreeBase {
|
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
55
|
* Returns the number of leaves in the tree.
|
|
56
|
-
* @param includeUncommitted - If true, the returned number of leaves includes
|
|
56
|
+
* @param includeUncommitted - If true, the returned number of leaves includes uncommitted changes.
|
|
57
57
|
* @returns The number of leaves in the tree.
|
|
58
58
|
*/
|
|
59
59
|
getNumLeaves(includeUncommitted) {
|
|
@@ -195,7 +195,7 @@ export class TreeBase {
|
|
|
195
195
|
* Initializes the tree from the database.
|
|
196
196
|
*/
|
|
197
197
|
async initFromDb() {
|
|
198
|
-
// Implemented only by
|
|
198
|
+
// Implemented only by Indexed Tree to populate the leaf cache.
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
201
201
|
* Writes meta data to the provided batch.
|
|
@@ -210,5 +210,48 @@ export class TreeBase {
|
|
|
210
210
|
await this.db.put(this.name, data);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Appends the given leaves to the tree.
|
|
215
|
+
* @param leaves - The leaves to append.
|
|
216
|
+
* @returns Empty promise.
|
|
217
|
+
*
|
|
218
|
+
* @remarks The batch insertion algorithm works as follows:
|
|
219
|
+
* 1. Insert all the leaves,
|
|
220
|
+
* 2. start iterating over levels from the bottom up,
|
|
221
|
+
* 3. on each level iterate over all the affected nodes (i.e. nodes whose preimages have changed),
|
|
222
|
+
* 4. fetch the preimage, hash it and insert the updated value.
|
|
223
|
+
* @remarks This algorithm is optimal when it comes to the number of hashing operations. It might not be optimal when
|
|
224
|
+
* it comes to the number of database reads, but that should be irrelevant given that most of the time
|
|
225
|
+
* `getLatestValueAtIndex` will return a value from cache (because at least one of the 2 children was
|
|
226
|
+
* touched in previous iteration).
|
|
227
|
+
*/
|
|
228
|
+
async appendLeaves(leaves) {
|
|
229
|
+
const numLeaves = this.getNumLeaves(true);
|
|
230
|
+
if (numLeaves + BigInt(leaves.length) - 1n > this.maxIndex) {
|
|
231
|
+
throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`);
|
|
232
|
+
}
|
|
233
|
+
// 1. Insert all the leaves
|
|
234
|
+
let firstIndex = numLeaves;
|
|
235
|
+
let level = this.depth;
|
|
236
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
237
|
+
const cacheKey = indexToKeyHash(this.name, level, firstIndex + BigInt(i));
|
|
238
|
+
this.cache[cacheKey] = leaves[i];
|
|
239
|
+
}
|
|
240
|
+
let lastIndex = firstIndex + BigInt(leaves.length);
|
|
241
|
+
// 2. Iterate over all the levels from the bottom up
|
|
242
|
+
while (level > 0) {
|
|
243
|
+
firstIndex >>= 1n;
|
|
244
|
+
lastIndex >>= 1n;
|
|
245
|
+
// 3.Iterate over all the affected nodes at this level and update them
|
|
246
|
+
for (let index = firstIndex; index <= lastIndex; index++) {
|
|
247
|
+
const lhs = await this.getLatestValueAtIndex(level, index * 2n, true);
|
|
248
|
+
const rhs = await this.getLatestValueAtIndex(level, index * 2n + 1n, true);
|
|
249
|
+
const cacheKey = indexToKeyHash(this.name, level - 1, index);
|
|
250
|
+
this.cache[cacheKey] = this.hasher.compress(lhs, rhs);
|
|
251
|
+
}
|
|
252
|
+
level -= 1;
|
|
253
|
+
}
|
|
254
|
+
this.cachedSize = numLeaves + BigInt(leaves.length);
|
|
255
|
+
}
|
|
213
256
|
}
|
|
214
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
257
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJlZV9iYXNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3RyZWVfYmFzZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFHN0QsT0FBTyxFQUFFLFVBQVUsRUFBRSxVQUFVLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUV6RSxNQUFNLFNBQVMsR0FBRyxHQUFHLENBQUM7QUFFdEIsTUFBTSxjQUFjLEdBQUcsQ0FBQyxJQUFZLEVBQUUsS0FBYSxFQUFFLEtBQWEsRUFBRSxFQUFFLENBQUMsR0FBRyxJQUFJLElBQUksS0FBSyxJQUFJLEtBQUssRUFBRSxDQUFDO0FBQ25HLE1BQU0sVUFBVSxHQUFHLENBQUMsSUFBWSxFQUFFLEtBQWEsRUFBRSxJQUFZLEVBQUUsRUFBRTtJQUMvRCxNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzlCLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDaEIsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDOUIsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3JELENBQUMsQ0FBQztBQUNGLE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRyxDQUFDLElBQVksRUFBRSxFQUFFO0lBQ3pDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ2xDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDcEMsTUFBTSxJQUFJLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztJ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LEtBQW9DO1FBQzVELE1BQU0sSUFBSSxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQ2pGLElBQUksS0FBSyxFQUFFO1lBQ1QsS0FBSyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO1NBQzVCO2FBQU07WUFDTCxNQUFNLElBQUksQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7U0FDcEM7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7O09BY0c7SUFDTyxLQUFLLENBQUMsWUFBWSxDQUFDLE1BQWdCO1FBQzNDLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDMUMsSUFBSSxTQUFTLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRTtZQUMxRCxNQUFNLEtBQUssQ0FBQyw2Q0FBNkMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7U0FDM0U7UUFFRCwyQkFBMkI7UUFDM0IsSUFBSSxVQUFVLEdBQUcsU0FBUyxDQUFDO1FBQzNCLElBQUksS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUM7UUFDdkIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDdEMsTUFBTSxRQUFRLEdBQUcsY0FBYyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLFVBQVUsR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMxRSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztTQUNsQztRQUVELElBQUksU0FBUyxHQUFHLFVBQVUsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ25ELG9EQUFvRDtRQUNwRCxPQUFPLEtBQUssR0FBRyxDQUFDLEVBQUU7WUFDaEIsVUFBVSxLQUFLLEVBQUUsQ0FBQztZQUNsQixTQUFTLEtBQUssRUFBRSxDQUFDO1lBQ2pCLHNFQUFzRTtZQUN0RSxLQUFLLElBQUksS0FBSyxHQUFHLFVBQVUsRUFBRSxLQUFLLElBQUksU0FBUyxFQUFFLEtBQUssRUFBRSxFQUFFO2dCQUN4RCxNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxLQUFLLEVBQUUsS0FBSyxHQUFHLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQztnQkFDdEUsTUFBTSxHQUFHLEdBQUcsTUFBTSxJQUFJLENBQUMscUJBQXFCLENBQUMsS0FBSyxFQUFFLEtBQUssR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDO2dCQUMzRSxNQUFNLFFBQVEsR0FBRyxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxLQUFLLEdBQUcsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO2dCQUM3RCxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQzthQUN2RDtZQUVELEtBQUssSUFBSSxDQUFDLENBQUM7U0FDWjtRQUNELElBQUksQ0FBQyxVQUFVLEdBQUcsU0FBUyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDdEQsQ0FBQztDQUNGIn0=
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/merkle-tree",
|
|
3
|
-
"version": "0.1.0-
|
|
3
|
+
"version": "0.1.0-alpha21",
|
|
4
4
|
"exports": "./dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@aztec/circuits.js": "0.1.0-
|
|
8
|
-
"@aztec/foundation": "0.1.0-
|
|
7
|
+
"@aztec/circuits.js": "0.1.0-alpha21",
|
|
8
|
+
"@aztec/foundation": "0.1.0-alpha21",
|
|
9
9
|
"levelup": "^5.1.1",
|
|
10
10
|
"memdown": "^6.1.1",
|
|
11
11
|
"sha256": "^0.2.0",
|
|
@@ -51,24 +51,14 @@ export interface IndexedTree extends AppendOnlyTree {
|
|
|
51
51
|
*/
|
|
52
52
|
getLatestLeafDataCopy(index: number, includeUncommitted: boolean): LeafData | undefined;
|
|
53
53
|
|
|
54
|
-
/**
|
|
55
|
-
* Exposes the underlying tree's update leaf method.
|
|
56
|
-
* @param leaf - The hash to set at the leaf.
|
|
57
|
-
* @param index - The index of the element.
|
|
58
|
-
*/
|
|
59
|
-
// TODO: remove once the batch insertion functionality is moved to StandardIndexedTree from circuit_block_builder.ts
|
|
60
|
-
updateLeaf(leaf: LeafData, index: bigint): Promise<void>;
|
|
61
|
-
|
|
62
54
|
/**
|
|
63
55
|
* Batch insert multiple leaves into the tree.
|
|
64
56
|
* @param leaves - Leaves to insert into the tree.
|
|
65
|
-
* @param treeHeight - Height of the tree.
|
|
66
57
|
* @param subtreeHeight - Height of the subtree.
|
|
67
58
|
* @param includeUncommitted - If true, the uncommitted changes are included in the search.
|
|
68
59
|
*/
|
|
69
60
|
batchInsert<TreeHeight extends number, SubtreeHeight extends number, SubtreeSiblingPathHeight extends number>(
|
|
70
61
|
leaves: Buffer[],
|
|
71
|
-
treeHeight: TreeHeight,
|
|
72
62
|
subtreeHeight: SubtreeHeight,
|
|
73
63
|
includeUncommitted: boolean,
|
|
74
64
|
): Promise<
|
package/src/new_tree.ts
CHANGED
|
@@ -18,7 +18,7 @@ export async function newTree<T extends TreeBase>(
|
|
|
18
18
|
hasher: Hasher,
|
|
19
19
|
name: string,
|
|
20
20
|
depth: number,
|
|
21
|
-
prefilledSize =
|
|
21
|
+
prefilledSize = 1,
|
|
22
22
|
): Promise<T> {
|
|
23
23
|
const tree = new c(db, hasher, name, depth, 0n, undefined);
|
|
24
24
|
await tree.init(prefilledSize);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer';
|
|
2
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
3
|
-
import { Hasher } from '../hasher.js';
|
|
4
3
|
import { IndexedTree, LeafData } from '../interfaces/indexed_tree.js';
|
|
5
4
|
import { TreeBase } from '../tree_base.js';
|
|
6
5
|
import { SiblingPath } from '../index.js';
|
|
@@ -56,10 +55,6 @@ const encodeTreeValue = (leafData: LeafData) => {
|
|
|
56
55
|
return Buffer.concat([valueAsBuffer, indexAsBuffer, nextValueAsBuffer]);
|
|
57
56
|
};
|
|
58
57
|
|
|
59
|
-
const hashEncodedTreeValue = (leaf: LeafData, hasher: Hasher) => {
|
|
60
|
-
return hasher.compressInputs([leaf.value, leaf.nextIndex, leaf.nextValue].map(val => toBufferBE(val, 32)));
|
|
61
|
-
};
|
|
62
|
-
|
|
63
58
|
const decodeTreeValue = (buf: Buffer) => {
|
|
64
59
|
const value = toBigIntBE(buf.subarray(0, 32));
|
|
65
60
|
const nextIndex = toBigIntBE(buf.subarray(32, 64));
|
|
@@ -71,28 +66,21 @@ const decodeTreeValue = (buf: Buffer) => {
|
|
|
71
66
|
} as LeafData;
|
|
72
67
|
};
|
|
73
68
|
|
|
74
|
-
const initialLeaf: LeafData = {
|
|
75
|
-
value: 0n,
|
|
76
|
-
nextIndex: 0n,
|
|
77
|
-
nextValue: 0n,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
69
|
/**
|
|
81
70
|
* Indexed merkle tree.
|
|
82
71
|
*/
|
|
83
72
|
export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
protected leaves: LeafData[] = [];
|
|
74
|
+
protected cachedLeaves: { [key: number]: LeafData } = {};
|
|
86
75
|
|
|
87
76
|
/**
|
|
88
77
|
* Appends the given leaves to the tree.
|
|
89
|
-
* @param
|
|
78
|
+
* @param _leaves - The leaves to append.
|
|
90
79
|
* @returns Empty promise.
|
|
80
|
+
* @remarks Use batchInsert method instead.
|
|
91
81
|
*/
|
|
92
|
-
public
|
|
93
|
-
|
|
94
|
-
await this.appendLeaf(leaf);
|
|
95
|
-
}
|
|
82
|
+
public appendLeaves(_leaves: Buffer[]): Promise<void> {
|
|
83
|
+
throw new Error('Not implemented');
|
|
96
84
|
}
|
|
97
85
|
|
|
98
86
|
/**
|
|
@@ -183,48 +171,6 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
183
171
|
: undefined;
|
|
184
172
|
}
|
|
185
173
|
|
|
186
|
-
/**
|
|
187
|
-
* Appends the given leaf to the tree.
|
|
188
|
-
* @param leaf - The leaf to append.
|
|
189
|
-
* @returns Empty promise.
|
|
190
|
-
*/
|
|
191
|
-
private async appendLeaf(leaf: Buffer): Promise<void> {
|
|
192
|
-
const newValue = toBigIntBE(leaf);
|
|
193
|
-
|
|
194
|
-
// Special case when appending zero
|
|
195
|
-
if (newValue === 0n) {
|
|
196
|
-
const newSize = (this.cachedSize ?? this.size) + 1n;
|
|
197
|
-
if (newSize - 1n > this.maxIndex) {
|
|
198
|
-
throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`);
|
|
199
|
-
}
|
|
200
|
-
this.cachedSize = newSize;
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const indexOfPrevious = this.findIndexOfPreviousValue(newValue, true);
|
|
205
|
-
const previousLeafCopy = this.getLatestLeafDataCopy(indexOfPrevious.index, true);
|
|
206
|
-
|
|
207
|
-
if (previousLeafCopy === undefined) {
|
|
208
|
-
throw new Error(`Previous leaf not found!`);
|
|
209
|
-
}
|
|
210
|
-
const newLeaf = {
|
|
211
|
-
value: newValue,
|
|
212
|
-
nextIndex: previousLeafCopy.nextIndex,
|
|
213
|
-
nextValue: previousLeafCopy.nextValue,
|
|
214
|
-
} as LeafData;
|
|
215
|
-
if (indexOfPrevious.alreadyPresent) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
// insert a new leaf at the highest index and update the values of our previous leaf copy
|
|
219
|
-
const currentSize = this.getNumLeaves(true);
|
|
220
|
-
previousLeafCopy.nextIndex = BigInt(currentSize);
|
|
221
|
-
previousLeafCopy.nextValue = newLeaf.value;
|
|
222
|
-
this.cachedLeaves[Number(currentSize)] = newLeaf;
|
|
223
|
-
this.cachedLeaves[Number(indexOfPrevious.index)] = previousLeafCopy;
|
|
224
|
-
await this._updateLeaf(hashEncodedTreeValue(previousLeafCopy, this.hasher), BigInt(indexOfPrevious.index));
|
|
225
|
-
await this._updateLeaf(hashEncodedTreeValue(newLeaf, this.hasher), this.getNumLeaves(true));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
174
|
/**
|
|
229
175
|
* Finds the index of the minimum value in an array.
|
|
230
176
|
* @param values - The collection of values to be searched.
|
|
@@ -247,15 +193,39 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
247
193
|
* Initializes the tree.
|
|
248
194
|
* @param prefilledSize - A number of leaves that are prefilled with values.
|
|
249
195
|
* @returns Empty promise.
|
|
196
|
+
*
|
|
197
|
+
* @remarks Explanation of pre-filling:
|
|
198
|
+
* There needs to be an initial (0,0,0) leaf in the tree, so that when we insert the first 'proper' leaf, we can
|
|
199
|
+
* prove that any value greater than 0 doesn't exist in the tree yet. We prefill/pad the tree with "the number of
|
|
200
|
+
* leaves that are added by one block" so that the first 'proper' block can insert a full subtree.
|
|
201
|
+
*
|
|
202
|
+
* Without this padding, there would be a leaf (0,0,0) at leaf index 0, making it really difficult to insert e.g.
|
|
203
|
+
* 1024 leaves for the first block, because there's only neat space for 1023 leaves after 0. By padding with 1023
|
|
204
|
+
* more leaves, we can then insert the first block of 1024 leaves into indices 1024:2047.
|
|
250
205
|
*/
|
|
251
206
|
public async init(prefilledSize: number): Promise<void> {
|
|
252
|
-
|
|
253
|
-
|
|
207
|
+
if (prefilledSize < 1) {
|
|
208
|
+
throw new Error(`Prefilled size must be at least 1!`);
|
|
209
|
+
}
|
|
254
210
|
|
|
255
|
-
|
|
256
|
-
|
|
211
|
+
const leaves: LeafData[] = [];
|
|
212
|
+
for (let i = 0n; i < prefilledSize; i++) {
|
|
213
|
+
const newLeaf = {
|
|
214
|
+
value: toBigIntBE(Buffer.from([Number(i)])),
|
|
215
|
+
nextIndex: i + 1n,
|
|
216
|
+
nextValue: i + 1n,
|
|
217
|
+
};
|
|
218
|
+
leaves.push(newLeaf);
|
|
257
219
|
}
|
|
258
220
|
|
|
221
|
+
// Make the first leaf have 0 value
|
|
222
|
+
leaves[0].value = 0n;
|
|
223
|
+
|
|
224
|
+
// Make the last leaf point to the first leaf
|
|
225
|
+
leaves[prefilledSize - 1].nextIndex = 0n;
|
|
226
|
+
leaves[prefilledSize - 1].nextValue = 0n;
|
|
227
|
+
|
|
228
|
+
await this.encodeAndAppendLeaves(leaves, true);
|
|
259
229
|
await this.commit();
|
|
260
230
|
}
|
|
261
231
|
|
|
@@ -315,35 +285,19 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
315
285
|
* @param leaf - New contents of the leaf.
|
|
316
286
|
* @param index - Index of the leaf to be updated.
|
|
317
287
|
*/
|
|
318
|
-
|
|
319
|
-
private async _updateLeaf(leaf: Buffer, index: bigint) {
|
|
288
|
+
protected async updateLeaf(leaf: LeafData, index: bigint) {
|
|
320
289
|
if (index > this.maxIndex) {
|
|
321
290
|
throw Error(`Index out of bounds. Index ${index}, max index: ${this.maxIndex}.`);
|
|
322
291
|
}
|
|
323
|
-
|
|
292
|
+
|
|
293
|
+
const encodedLeaf = this.encodeLeaf(leaf, true);
|
|
294
|
+
await this.addLeafToCacheAndHashToRoot(encodedLeaf, index);
|
|
324
295
|
const numLeaves = this.getNumLeaves(true);
|
|
325
296
|
if (index >= numLeaves) {
|
|
326
297
|
this.cachedSize = index + 1n;
|
|
327
298
|
}
|
|
328
299
|
}
|
|
329
300
|
|
|
330
|
-
/**
|
|
331
|
-
* Exposes the underlying tree's update leaf method.
|
|
332
|
-
* @param leaf - The hash to set at the leaf.
|
|
333
|
-
* @param index - The index of the element.
|
|
334
|
-
*/
|
|
335
|
-
// TODO: remove once the batch insertion functionality is moved here from circuit_block_builder.ts
|
|
336
|
-
public async updateLeaf(leaf: LeafData, index: bigint): Promise<void> {
|
|
337
|
-
let encodedLeaf;
|
|
338
|
-
if (leaf.value == 0n) {
|
|
339
|
-
encodedLeaf = toBufferBE(0n, 32);
|
|
340
|
-
} else {
|
|
341
|
-
encodedLeaf = hashEncodedTreeValue(leaf, this.hasher);
|
|
342
|
-
}
|
|
343
|
-
this.cachedLeaves[Number(index)] = leaf;
|
|
344
|
-
await this._updateLeaf(encodedLeaf, index);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
301
|
/* eslint-disable jsdoc/require-description-complete-sentence */
|
|
348
302
|
/* The following doc block messes up with complete-sentence, so we just disable it */
|
|
349
303
|
|
|
@@ -366,8 +320,8 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
366
320
|
*
|
|
367
321
|
* Algorithm overview
|
|
368
322
|
*
|
|
369
|
-
* In general, if we want to batch insert items, we first to update their low nullifier to point to them,
|
|
370
|
-
* then batch insert all of the values
|
|
323
|
+
* In general, if we want to batch insert items, we first need to update their low nullifier to point to them,
|
|
324
|
+
* then batch insert all of the values at once in the final step.
|
|
371
325
|
* To update a low nullifier, we provide an insertion proof that the low nullifier currently exists to the
|
|
372
326
|
* circuit, then update the low nullifier.
|
|
373
327
|
* Updating this low nullifier will in turn change the root of the tree. Therefore future low nullifier insertion proofs
|
|
@@ -422,7 +376,7 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
422
376
|
* nextVal 2 10 15 0 3 5 - -
|
|
423
377
|
*
|
|
424
378
|
* Inserting 20: (happy path)
|
|
425
|
-
* 1. Find the low nullifier (15) - provide
|
|
379
|
+
* 1. Find the low nullifier (15) - provide inclusion proof
|
|
426
380
|
* 2. Update its pointers
|
|
427
381
|
* 3. Insert 20 into the pending subtree
|
|
428
382
|
*
|
|
@@ -454,7 +408,6 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
454
408
|
*
|
|
455
409
|
* TODO: this implementation will change once the zero value is changed from h(0,0,0). Changes incoming over the next sprint
|
|
456
410
|
* @param leaves - Values to insert into the tree.
|
|
457
|
-
* @param treeHeight - Height of the tree.
|
|
458
411
|
* @param subtreeHeight - Height of the subtree.
|
|
459
412
|
* @returns The data for the leaves to be updated when inserting the new ones.
|
|
460
413
|
*/
|
|
@@ -464,7 +417,6 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
464
417
|
SubtreeSiblingPathHeight extends number,
|
|
465
418
|
>(
|
|
466
419
|
leaves: Buffer[],
|
|
467
|
-
treeHeight: TreeHeight,
|
|
468
420
|
subtreeHeight: SubtreeHeight,
|
|
469
421
|
): Promise<
|
|
470
422
|
| [LowLeafWitnessData<TreeHeight>[], SiblingPath<SubtreeSiblingPathHeight>]
|
|
@@ -473,7 +425,7 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
473
425
|
// Keep track of touched low leaves
|
|
474
426
|
const touched = new Map<number, bigint[]>();
|
|
475
427
|
|
|
476
|
-
const emptyLowLeafWitness = getEmptyLowLeafWitness(
|
|
428
|
+
const emptyLowLeafWitness = getEmptyLowLeafWitness(this.getDepth() as TreeHeight);
|
|
477
429
|
// Accumulators
|
|
478
430
|
const lowLeavesWitnesses: LowLeafWitnessData<TreeHeight>[] = [];
|
|
479
431
|
const pendingInsertionSubtree: LeafData[] = [];
|
|
@@ -494,7 +446,7 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
494
446
|
|
|
495
447
|
const indexOfPrevious = this.findIndexOfPreviousValue(newValue, true);
|
|
496
448
|
|
|
497
|
-
// If a touched node has a value that is less
|
|
449
|
+
// If a touched node has a value that is less than the current value
|
|
498
450
|
const prevNodes = touched.get(indexOfPrevious.index);
|
|
499
451
|
if (prevNodes && prevNodes.some(v => v < newValue)) {
|
|
500
452
|
// check the pending low nullifiers for a low nullifier that works
|
|
@@ -561,7 +513,9 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
561
513
|
lowLeaf.nextValue = newValue;
|
|
562
514
|
lowLeaf.nextIndex = startInsertionIndex + BigInt(i);
|
|
563
515
|
|
|
564
|
-
|
|
516
|
+
const lowLeafIndex = indexOfPrevious.index;
|
|
517
|
+
this.cachedLeaves[lowLeafIndex] = lowLeaf;
|
|
518
|
+
await this.updateLeaf(lowLeaf, BigInt(lowLeafIndex));
|
|
565
519
|
}
|
|
566
520
|
}
|
|
567
521
|
|
|
@@ -571,10 +525,9 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
571
525
|
);
|
|
572
526
|
|
|
573
527
|
// Perform batch insertion of new pending values
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
528
|
+
// Note: In this case we set `hash0Leaf` param to false because batch insertion algorithm use forced null leaf
|
|
529
|
+
// inclusion. See {@link encodeLeaf} for a more through param explanation.
|
|
530
|
+
await this.encodeAndAppendLeaves(pendingInsertionSubtree, false);
|
|
578
531
|
return [lowLeavesWitnesses, newSubtreeSiblingPath];
|
|
579
532
|
}
|
|
580
533
|
|
|
@@ -588,4 +541,41 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
|
|
|
588
541
|
// Drop the first subtreeHeight items since we only care about the path to the subtree root
|
|
589
542
|
return fullSiblingPath.getSubtreeSiblingPath(subtreeHeight);
|
|
590
543
|
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Encodes leaves and appends them to a tree.
|
|
547
|
+
* @param leaves - Leaves to encode.
|
|
548
|
+
* @param hash0Leaf - Indicates whether 0 value leaf should be hashed. See {@link encodeLeaf}.
|
|
549
|
+
* @returns Empty promise
|
|
550
|
+
*/
|
|
551
|
+
private async encodeAndAppendLeaves(leaves: LeafData[], hash0Leaf: boolean): Promise<void> {
|
|
552
|
+
const startInsertionIndex = Number(this.getNumLeaves(true));
|
|
553
|
+
|
|
554
|
+
const serialisedLeaves = leaves.map((leaf, i) => {
|
|
555
|
+
this.cachedLeaves[startInsertionIndex + i] = leaf;
|
|
556
|
+
return this.encodeLeaf(leaf, hash0Leaf);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
await super.appendLeaves(serialisedLeaves);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Encode a leaf into a buffer.
|
|
564
|
+
* @param leaf - Leaf to encode.
|
|
565
|
+
* @param hash0Leaf - Indicates whether 0 value leaf should be hashed. Not hashing 0 value can represent a forced
|
|
566
|
+
* null leaf insertion. Detecting this case by checking for 0 value is safe as in the case of
|
|
567
|
+
* nullifier it is improbable that a valid nullifier would be 0.
|
|
568
|
+
* @returns Leaf encoded in a buffer.
|
|
569
|
+
*/
|
|
570
|
+
private encodeLeaf(leaf: LeafData, hash0Leaf: boolean): Buffer {
|
|
571
|
+
let encodedLeaf;
|
|
572
|
+
if (!hash0Leaf && leaf.value == 0n) {
|
|
573
|
+
encodedLeaf = toBufferBE(0n, 32);
|
|
574
|
+
} else {
|
|
575
|
+
encodedLeaf = this.hasher.compressInputs(
|
|
576
|
+
[leaf.value, leaf.nextIndex, leaf.nextValue].map(val => toBufferBE(val, 32)),
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return encodedLeaf;
|
|
580
|
+
}
|
|
591
581
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { default as levelup } from 'levelup';
|
|
2
|
-
import { Hasher, INITIAL_LEAF, MerkleTree, Pedersen, SiblingPath } from '
|
|
3
|
-
import {
|
|
4
|
-
import { treeTestSuite } from '../test/test_suite.js';
|
|
2
|
+
import { Hasher, INITIAL_LEAF, MerkleTree, Pedersen, SiblingPath } from '../../index.js';
|
|
3
|
+
import { treeTestSuite } from '../../test/test_suite.js';
|
|
5
4
|
|
|
6
|
-
import { createMemDown } from '
|
|
7
|
-
import { newTree } from '
|
|
8
|
-
import { loadTree } from '
|
|
5
|
+
import { createMemDown } from '../../test/utils/create_mem_down.js';
|
|
6
|
+
import { newTree } from '../../new_tree.js';
|
|
7
|
+
import { loadTree } from '../../load_tree.js';
|
|
9
8
|
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
|
|
10
9
|
import { IWasmModule } from '@aztec/foundation/wasm';
|
|
11
10
|
import { CircuitsWasm } from '@aztec/circuits.js';
|
|
11
|
+
import { StandardIndexedTreeWithAppend } from './standard_indexed_tree_with_append.js';
|
|
12
12
|
|
|
13
|
-
const createDb = async (levelUp: levelup.LevelUp, hasher: Hasher, name: string, depth: number) => {
|
|
14
|
-
return await newTree(
|
|
13
|
+
const createDb = async (levelUp: levelup.LevelUp, hasher: Hasher, name: string, depth: number, prefilledSize = 1) => {
|
|
14
|
+
return await newTree(StandardIndexedTreeWithAppend, levelUp, hasher, name, depth, prefilledSize);
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const createFromName = async (levelUp: levelup.LevelUp, hasher: Hasher, name: string) => {
|
|
18
|
-
return await loadTree(
|
|
18
|
+
return await loadTree(StandardIndexedTreeWithAppend, levelUp, hasher, name);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const createIndexedTreeLeaf = (value: number, nextIndex: number, nextValue: number) => {
|
|
@@ -447,4 +447,30 @@ describe('StandardIndexedTreeSpecific', () => {
|
|
|
447
447
|
expect(await tree.getSiblingPath(BigInt(i), false)).toEqual(await tree.getSiblingPath(BigInt(i), true));
|
|
448
448
|
}
|
|
449
449
|
});
|
|
450
|
+
|
|
451
|
+
// For varying orders of insertions assert the local batch insertion generator creates the correct proofs
|
|
452
|
+
it.each([
|
|
453
|
+
// These are arbitrary but it needs to be higher than the constant `INITIAL_NULLIFIER_TREE_SIZE` and `KERNEL_NEW_NULLIFIERS_LENGTH * 2`
|
|
454
|
+
[[1003, 1002, 1001, 1000, 0, 0, 0, 0]],
|
|
455
|
+
[[1003, 1004, 1005, 1006, 0, 0, 0, 0]],
|
|
456
|
+
[[1234, 1098, 0, 0, 99999, 1096, 1054, 0]],
|
|
457
|
+
[[1970, 1980, 1040, 0, 99999, 1880, 100001, 9000000]],
|
|
458
|
+
] as const)('performs nullifier tree batch insertion correctly', async nullifiers => {
|
|
459
|
+
const leaves = nullifiers.map(i => toBufferBE(BigInt(i), 32));
|
|
460
|
+
|
|
461
|
+
const TREE_HEIGHT = 16; // originally from NULLIFIER_TREE_HEIGHT
|
|
462
|
+
const INITIAL_TREE_SIZE = 8; // originally from INITIAL_NULLIFIER_TREE_SIZE
|
|
463
|
+
const SUBTREE_HEIGHT = 5; // originally from BaseRollupInputs.NULLIFIER_SUBTREE_HEIGHT
|
|
464
|
+
|
|
465
|
+
// Create a depth-3 indexed merkle tree
|
|
466
|
+
const appendTree = await createDb(levelup(createMemDown()), pedersen, 'test', TREE_HEIGHT, INITIAL_TREE_SIZE);
|
|
467
|
+
const insertTree = await createDb(levelup(createMemDown()), pedersen, 'test', TREE_HEIGHT, INITIAL_TREE_SIZE);
|
|
468
|
+
|
|
469
|
+
await appendTree.appendLeaves(leaves);
|
|
470
|
+
await insertTree.batchInsert(leaves, SUBTREE_HEIGHT);
|
|
471
|
+
|
|
472
|
+
const expectedRoot = appendTree.getRoot(true);
|
|
473
|
+
const actualRoot = insertTree.getRoot(true);
|
|
474
|
+
expect(actualRoot).toEqual(expectedRoot);
|
|
475
|
+
});
|
|
450
476
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
|
|
2
|
+
import { LeafData, StandardIndexedTree } from '../../index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A testing utility which is here to store the original implementation of StandardIndexedTree.appendLeaves method
|
|
6
|
+
* that was replaced by the more efficient batchInsert method. We keep the original implementation around as it useful
|
|
7
|
+
* for testing that the more complex batchInsert method works correctly.
|
|
8
|
+
*/
|
|
9
|
+
export class StandardIndexedTreeWithAppend extends StandardIndexedTree {
|
|
10
|
+
/**
|
|
11
|
+
* Appends the given leaves to the tree.
|
|
12
|
+
* @param leaves - The leaves to append.
|
|
13
|
+
* @returns Empty promise.
|
|
14
|
+
* @remarks This method is inefficient and is here mostly for testing. Use batchInsert instead.
|
|
15
|
+
*/
|
|
16
|
+
public async appendLeaves(leaves: Buffer[]): Promise<void> {
|
|
17
|
+
for (const leaf of leaves) {
|
|
18
|
+
await this.appendLeaf(leaf);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Appends the given leaf to the tree.
|
|
24
|
+
* @param leaf - The leaf to append.
|
|
25
|
+
* @returns Empty promise.
|
|
26
|
+
*/
|
|
27
|
+
private async appendLeaf(leaf: Buffer): Promise<void> {
|
|
28
|
+
const newValue = toBigIntBE(leaf);
|
|
29
|
+
|
|
30
|
+
// Special case when appending zero
|
|
31
|
+
if (newValue === 0n) {
|
|
32
|
+
const newSize = (this.cachedSize ?? this.size) + 1n;
|
|
33
|
+
if (newSize - 1n > this.maxIndex) {
|
|
34
|
+
throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`);
|
|
35
|
+
}
|
|
36
|
+
this.cachedSize = newSize;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const indexOfPrevious = this.findIndexOfPreviousValue(newValue, true);
|
|
41
|
+
const previousLeafCopy = this.getLatestLeafDataCopy(indexOfPrevious.index, true);
|
|
42
|
+
|
|
43
|
+
if (previousLeafCopy === undefined) {
|
|
44
|
+
throw new Error(`Previous leaf not found!`);
|
|
45
|
+
}
|
|
46
|
+
const newLeaf = {
|
|
47
|
+
value: newValue,
|
|
48
|
+
nextIndex: previousLeafCopy.nextIndex,
|
|
49
|
+
nextValue: previousLeafCopy.nextValue,
|
|
50
|
+
} as LeafData;
|
|
51
|
+
if (indexOfPrevious.alreadyPresent) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// insert a new leaf at the highest index and update the values of our previous leaf copy
|
|
55
|
+
const currentSize = this.getNumLeaves(true);
|
|
56
|
+
previousLeafCopy.nextIndex = BigInt(currentSize);
|
|
57
|
+
previousLeafCopy.nextValue = newLeaf.value;
|
|
58
|
+
this.cachedLeaves[Number(currentSize)] = newLeaf;
|
|
59
|
+
this.cachedLeaves[Number(indexOfPrevious.index)] = previousLeafCopy;
|
|
60
|
+
await this.updateLeaf(previousLeafCopy, BigInt(indexOfPrevious.index));
|
|
61
|
+
await this.updateLeaf(newLeaf, this.getNumLeaves(true));
|
|
62
|
+
}
|
|
63
|
+
}
|