From 1b8d40ea4e11a236269b5daf78635768fc04ed97 Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+cheethas@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:09:40 -0700 Subject: [PATCH] feat: zero leaf indexed tree (#285) * feat: circuit now uses 0 hashes * feat: nullifier tree uses 0 as the empty hash * chore(bb): bump * chore(bb): update branch * fix: update 0 insertion * force build circuits * fix hashConstructor test + update snapshot * remove log * clean: remove force rebuild * fix: address nits * fix: include uncommitted comments * docs: uncommitted comments --------- Co-authored-by: cheethas Co-authored-by: cheethas Co-authored-by: spypsy --- circuits/cpp/barretenberg | 2 +- .../base/native_base_rollup_circuit.cpp | 23 ++--- .../base/nullifier_tree_testing_harness.cpp | 56 ++---------- .../base/nullifier_tree_testing_harness.hpp | 3 - .../src/aztec3/circuits/rollup/base/utils.cpp | 4 +- .../src/abis/__snapshots__/abis.test.ts.snap | 58 ++++++------- .../circuits.js/src/abis/abis.test.ts | 3 +- .../src/indexed_tree/indexed_tree.test.ts | 87 ++++++++++++++----- .../src/indexed_tree/indexed_tree.ts | 22 ++++- .../src/standard_tree/standard_tree.ts | 10 +++ 10 files changed, 137 insertions(+), 131 deletions(-) diff --git a/circuits/cpp/barretenberg b/circuits/cpp/barretenberg index 6b4eaa9e547..e884272e088 160000 --- a/circuits/cpp/barretenberg +++ b/circuits/cpp/barretenberg @@ -1 +1 @@ -Subproject commit 6b4eaa9e5474de9af1e893b94e1b47c28a5e83ec +Subproject commit e884272e088a072f7ba36f6bac7ae8a253dc7484 diff --git a/circuits/cpp/src/aztec3/circuits/rollup/base/native_base_rollup_circuit.cpp b/circuits/cpp/src/aztec3/circuits/rollup/base/native_base_rollup_circuit.cpp index 6b69b24ae32..917a9f738ba 100644 --- a/circuits/cpp/src/aztec3/circuits/rollup/base/native_base_rollup_circuit.cpp +++ b/circuits/cpp/src/aztec3/circuits/rollup/base/native_base_rollup_circuit.cpp @@ -23,16 +23,7 @@ namespace aztec3::circuits::rollup::native_base_rollup { const NT::fr EMPTY_COMMITMENTS_SUBTREE_ROOT = MerkleTree(PRIVATE_DATA_SUBTREE_DEPTH).root(); const NT::fr EMPTY_CONTRACTS_SUBTREE_ROOT = MerkleTree(CONTRACT_SUBTREE_DEPTH).root(); - -// Note: this is temporary until I work out how to encode a large fr in a constant -NT::fr calculate_empty_nullifier_subtree_root() -{ - MerkleTree empty_nullifier_tree = MerkleTree(NULLIFIER_SUBTREE_DEPTH); - for (size_t i = 0; i < KERNEL_NEW_NULLIFIERS_LENGTH * 2; i++) { - empty_nullifier_tree.update_element(i, NullifierLeaf{ .value = 0, .nextIndex = 0, .nextValue = 0 }.hash()); - } - return empty_nullifier_tree.root(); -} +const NT::fr EMPTY_NULLIFIER_SUBTREE_ROOT = MerkleTree(NULLIFIER_SUBTREE_DEPTH).root(); // TODO: can we aggregate proofs if we do not have a working circuit impl @@ -242,9 +233,11 @@ NT::fr create_nullifier_subtree(std::array const& values, fr const& value) return false; } -// TODO: test -fr NullifierMemoryTreeTestingHarness::append_value(fr const& value) -{ - // If the value is 0, then we force insert the value to increase the size of the tree - // TODO: this is a hack - if (value == 0) { - nullifier_leaf new_leaf = { .value = 0, .nextIndex = 0, .nextValue = 0 }; - auto new_leaf_hash = new_leaf.hash(); - leaves_.push_back(new_leaf); - size_t new_leaf_index = leaves_.size() - 1; - auto root = update_element(new_leaf_index, new_leaf_hash); - return root; - } - - // Find the leaf with the value closest and less than `value` - size_t current; - bool is_already_present; - std::tie(current, is_already_present) = find_closest_leaf(leaves_, value); - - nullifier_leaf new_leaf = { .value = value, - .nextIndex = leaves_[current].nextIndex, - .nextValue = leaves_[current].nextValue }; - if (!is_already_present) { - // Update the current leaf to point it to the new leaf - leaves_[current].nextIndex = leaves_.size(); - leaves_[current].nextValue = value; - - // Insert the new leaf with (nextIndex, nextValue) of the current leaf - leaves_.push_back(new_leaf); - } - - // Update the old leaf in the tree - auto old_leaf_hash = leaves_[current].hash(); - size_t old_leaf_index = current; - auto root = update_element(old_leaf_index, old_leaf_hash); - - // Insert the new leaf in the tree - auto new_leaf_hash = new_leaf.hash(); - - size_t new_leaf_index = is_already_present ? old_leaf_index : leaves_.size() - 1; - root = update_element(new_leaf_index, new_leaf_hash); - - return root; -} - // handle synthetic membership assertions std::tuple, std::vector>, std::vector> NullifierMemoryTreeTestingHarness::circuit_prep_batch_insert(std::vector const& values) @@ -124,7 +79,6 @@ NullifierMemoryTreeTestingHarness::circuit_prep_batch_insert(std::vector con } // If there is a lower value in the tree, we need to check the current low nullifiers for one that can be used if (has_less_than) { - for (size_t j = 0; j < pending_insertion_tree.size(); ++j) { // Skip checking empty values if (pending_insertion_tree[j].value == 0) { @@ -147,7 +101,7 @@ NullifierMemoryTreeTestingHarness::circuit_prep_batch_insert(std::vector con } } - // empty low nullifier + // add empty low nullifier sibling_paths.push_back(empty_sp); low_nullifier_indexes.push_back(empty_index); low_nullifiers.push_back(empty_leaf); @@ -160,7 +114,7 @@ NullifierMemoryTreeTestingHarness::circuit_prep_batch_insert(std::vector con prev_nodes->second.push_back(new_value); } - nullifier_leaf low_nullifier = leaves_[current]; + nullifier_leaf low_nullifier = leaves_[current].unwrap(); std::vector sibling_path = this->get_sibling_path(current); sibling_paths.push_back(sibling_path); @@ -185,7 +139,7 @@ NullifierMemoryTreeTestingHarness::circuit_prep_batch_insert(std::vector con void NullifierMemoryTreeTestingHarness::update_element_in_place(size_t index, nullifier_leaf leaf) { // Find the leaf with the value closest and less than `value` - this->leaves_[index] = leaf; + this->leaves_[index].set(leaf); update_element(index, leaf.hash()); } @@ -197,8 +151,8 @@ std::pair NullifierMemoryTreeTestingHarness::find_lower( // TODO: handle is already present case if (!is_already_present) { - return std::make_pair(leaves_[current], current); + return std::make_pair(leaves_[current].unwrap(), current); } else { - return std::make_pair(leaves_[current], current); + return std::make_pair(leaves_[current].unwrap(), current); } } \ No newline at end of file diff --git a/circuits/cpp/src/aztec3/circuits/rollup/base/nullifier_tree_testing_harness.hpp b/circuits/cpp/src/aztec3/circuits/rollup/base/nullifier_tree_testing_harness.hpp index 3ded991dd36..f960a87921d 100644 --- a/circuits/cpp/src/aztec3/circuits/rollup/base/nullifier_tree_testing_harness.hpp +++ b/circuits/cpp/src/aztec3/circuits/rollup/base/nullifier_tree_testing_harness.hpp @@ -28,9 +28,6 @@ class NullifierMemoryTreeTestingHarness : public proof_system::plonk::stdlib::me // Get the value immediately lower than the given value std::pair find_lower(fr const& value); - // Append a value to the tree, even zeros - fr append_value(fr const& value); - // Utilities to inspect tree fr total_size() const { return total_size_; } fr depth() const { return depth_; } diff --git a/circuits/cpp/src/aztec3/circuits/rollup/base/utils.cpp b/circuits/cpp/src/aztec3/circuits/rollup/base/utils.cpp index acc11739495..9c7a059a333 100644 --- a/circuits/cpp/src/aztec3/circuits/rollup/base/utils.cpp +++ b/circuits/cpp/src/aztec3/circuits/rollup/base/utils.cpp @@ -68,7 +68,7 @@ NullifierMemoryTreeTestingHarness get_initial_nullifier_tree(std::vector ini { NullifierMemoryTreeTestingHarness nullifier_tree = NullifierMemoryTreeTestingHarness(NULLIFIER_TREE_HEIGHT); for (size_t i = 0; i < initial_values.size(); ++i) { - nullifier_tree.append_value(initial_values[i]); + nullifier_tree.update_element(initial_values[i]); } return nullifier_tree; } @@ -158,7 +158,7 @@ generate_nullifier_tree_testing_values(BaseRollupInputs rollupInputs, new_nullifiers_kernel_2[i - KERNEL_NEW_NULLIFIERS_LENGTH] = insertion_val; } insertion_values.push_back(insertion_val); - reference_tree.append_value(insertion_val); + reference_tree.update_element(insertion_val); } // Get the hash paths etc from the insertion values diff --git a/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap b/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap index 5ec7c15ebb8..02c843658d3 100644 --- a/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap +++ b/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap @@ -102,38 +102,38 @@ Fr { exports[`abis wasm bindings hash constructor info 1`] = ` { "data": [ - 33, - 79, - 48, - 128, - 228, + 36, + 203, + 121, + 198, + 238, + 246, + 39, + 11, + 209, + 251, + 234, + 135, + 70, 0, + 68, + 226, + 184, + 31, 77, - 227, - 224, - 187, - 234, - 166, + 126, + 81, + 123, + 23, + 198, 162, - 215, - 250, - 187, - 135, - 156, - 67, - 135, - 234, - 67, - 223, - 239, - 172, - 228, - 95, - 105, - 107, - 101, - 207, - 241, + 185, + 199, + 254, + 183, + 90, + 167, + 9, ], "type": "Buffer", } diff --git a/yarn-project/circuits.js/src/abis/abis.test.ts b/yarn-project/circuits.js/src/abis/abis.test.ts index 7498466746c..d4dd9812009 100644 --- a/yarn-project/circuits.js/src/abis/abis.test.ts +++ b/yarn-project/circuits.js/src/abis/abis.test.ts @@ -68,7 +68,8 @@ describe('abis wasm bindings', () => { it('hash constructor info', async () => { const functionData = new FunctionData(Buffer.alloc(4), true, true); - const args = [new Fr(0n), new Fr(1n)]; + // args needs to have a FIXED length of 8, due to a circuit constant `aztec3::ARGS_SIZE`. + const args = [new Fr(0n), new Fr(1n), new Fr(0n), new Fr(1n), new Fr(0n), new Fr(1n), new Fr(0n), new Fr(1n)]; const vkHash = Buffer.alloc(32); const res = await hashConstructor(wasm, functionData, args, vkHash); expect(res).toMatchSnapshot(); diff --git a/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.test.ts b/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.test.ts index ee3b387c67b..eddccb5ed25 100644 --- a/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.test.ts +++ b/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.test.ts @@ -54,11 +54,22 @@ describe('IndexedMerkleTreeSpecific', () => { * nextVal 0 0 0 0 0 0 0 0. */ - const zeroTreeLeafHash = pedersen.compressInputs(createIndexedTreeLeaf(0, 0, 0)); + const zeroTreeLeafHash = toBufferBE(0n, 32); + const initialLeafHash = pedersen.compressInputs(createIndexedTreeLeaf(0, 0, 0)); const level1ZeroHash = pedersen.compress(zeroTreeLeafHash, zeroTreeLeafHash); const level2ZeroHash = pedersen.compress(level1ZeroHash, level1ZeroHash); - let root = pedersen.compress(level2ZeroHash, level2ZeroHash); + + let index0Hash = initialLeafHash; + // Each element is named by the level followed by the index on that level. E.g. e10 -> level 1, index 0, e21 -> level 2, index 1 + let e10 = pedersen.compress(index0Hash, zeroTreeLeafHash); + let e20 = pedersen.compress(e10, level1ZeroHash); + + const initialE20 = e20; // Kept for calculating committed state later + const initialE10 = e10; + + let root = pedersen.compress(e20, level2ZeroHash); const initialRoot = root; + const emptySiblingPath = new SiblingPath([zeroTreeLeafHash, level1ZeroHash, level2ZeroHash]); expect(tree.getRoot(true)).toEqual(root); @@ -78,10 +89,10 @@ describe('IndexedMerkleTreeSpecific', () => { * nextIdx 1 0 0 0 0 0 0 0 * nextVal 30 0 0 0 0 0 0 0. */ - let index0Hash = pedersen.compressInputs(createIndexedTreeLeaf(0, 1, 30)); + index0Hash = pedersen.compressInputs(createIndexedTreeLeaf(0, 1, 30)); let index1Hash = pedersen.compressInputs(createIndexedTreeLeaf(30, 0, 0)); - let e10 = pedersen.compress(index0Hash, index1Hash); - let e20 = pedersen.compress(e10, level1ZeroHash); + e10 = pedersen.compress(index0Hash, index1Hash); + e20 = pedersen.compress(e10, level1ZeroHash); root = pedersen.compress(e20, level2ZeroHash); await tree.appendLeaves([toBufferBE(30n, 32)]); @@ -91,7 +102,8 @@ describe('IndexedMerkleTreeSpecific', () => { expect(await tree.getSiblingPath(1n, true)).toEqual(new SiblingPath([index0Hash, level1ZeroHash, level2ZeroHash])); // ensure the committed state is correct - await verifyCommittedState(tree, initialRoot, 1n, emptySiblingPath); + const initialSiblingPath = new SiblingPath([initialLeafHash, level1ZeroHash, level2ZeroHash]); + await verifyCommittedState(tree, initialRoot, 1n, initialSiblingPath); /** * Add new value 10: @@ -116,7 +128,7 @@ describe('IndexedMerkleTreeSpecific', () => { expect(await tree.getSiblingPath(2n, true)).toEqual(new SiblingPath([zeroTreeLeafHash, e10, level2ZeroHash])); // ensure the committed state is correct - await verifyCommittedState(tree, initialRoot, 2n, emptySiblingPath); + await verifyCommittedState(tree, initialRoot, 2n, new SiblingPath([zeroTreeLeafHash, initialE10, level2ZeroHash])); /** * Add new value 20: @@ -141,7 +153,7 @@ describe('IndexedMerkleTreeSpecific', () => { expect(await tree.getSiblingPath(3n, true)).toEqual(new SiblingPath([index2Hash, e10, level2ZeroHash])); // ensure the committed state is correct - await verifyCommittedState(tree, initialRoot, 3n, emptySiblingPath); + await verifyCommittedState(tree, initialRoot, 3n, new SiblingPath([zeroTreeLeafHash, initialE10, level2ZeroHash])); /** * Add new value 50: @@ -166,7 +178,7 @@ describe('IndexedMerkleTreeSpecific', () => { expect(tree.getNumLeaves(true)).toEqual(5n); // ensure the committed state is correct - await verifyCommittedState(tree, initialRoot, 4n, emptySiblingPath); + await verifyCommittedState(tree, initialRoot, 4n, new SiblingPath([zeroTreeLeafHash, level1ZeroHash, initialE20])); // check all uncommitted hash paths expect(await tree.getSiblingPath(0n, true)).toEqual(new SiblingPath([index1Hash, e11, e21])); @@ -179,9 +191,19 @@ describe('IndexedMerkleTreeSpecific', () => { expect(await tree.getSiblingPath(7n, true)).toEqual(new SiblingPath([zeroTreeLeafHash, e12, e20])); // check all committed hash paths - for (let i = 0; i < 8; i++) { - expect(await tree.getSiblingPath(BigInt(i), false)).toEqual(emptySiblingPath); - } + expect(await tree.getSiblingPath(0n, false)).toEqual(emptySiblingPath); + expect(await tree.getSiblingPath(1n, false)).toEqual(initialSiblingPath); + expect(await tree.getSiblingPath(2n, false)).toEqual( + new SiblingPath([zeroTreeLeafHash, initialE10, level2ZeroHash]), + ); + expect(await tree.getSiblingPath(3n, false)).toEqual( + new SiblingPath([zeroTreeLeafHash, initialE10, level2ZeroHash]), + ); + const e2SiblingPath = new SiblingPath([zeroTreeLeafHash, level1ZeroHash, initialE20]); + expect(await tree.getSiblingPath(4n, false)).toEqual(e2SiblingPath); + expect(await tree.getSiblingPath(5n, false)).toEqual(e2SiblingPath); + expect(await tree.getSiblingPath(6n, false)).toEqual(e2SiblingPath); + expect(await tree.getSiblingPath(7n, false)).toEqual(e2SiblingPath); await tree.commit(); // check all committed hash paths equal uncommitted hash paths @@ -205,12 +227,23 @@ describe('IndexedMerkleTreeSpecific', () => { * nextVal 0 0 0 0 0 0 0 0. */ - const zeroTreeLeafHash = pedersen.compressInputs(createIndexedTreeLeaf(0, 0, 0)); + const zeroTreeLeafHash = toBufferBE(0n, 32); + const initialLeafHash = pedersen.compressInputs(createIndexedTreeLeaf(0, 0, 0)); const level1ZeroHash = pedersen.compress(zeroTreeLeafHash, zeroTreeLeafHash); const level2ZeroHash = pedersen.compress(level1ZeroHash, level1ZeroHash); - let root = pedersen.compress(level2ZeroHash, level2ZeroHash); + let index0Hash = initialLeafHash; + + let e10 = pedersen.compress(index0Hash, zeroTreeLeafHash); + let e20 = pedersen.compress(e10, level1ZeroHash); + + const inite10 = e10; + const inite20 = e20; + + let root = pedersen.compress(e20, level2ZeroHash); const initialRoot = root; + const emptySiblingPath = new SiblingPath([zeroTreeLeafHash, level1ZeroHash, level2ZeroHash]); + const initialSiblingPath = new SiblingPath([initialLeafHash, level1ZeroHash, level2ZeroHash]); expect(tree.getRoot(true)).toEqual(root); expect(tree.getNumLeaves(true)).toEqual(1n); @@ -229,10 +262,10 @@ describe('IndexedMerkleTreeSpecific', () => { * nextIdx 1 0 0 0 0 0 0 0 * nextVal 30 0 0 0 0 0 0 0. */ - let index0Hash = pedersen.compressInputs(createIndexedTreeLeaf(0, 1, 30)); + index0Hash = pedersen.compressInputs(createIndexedTreeLeaf(0, 1, 30)); let index1Hash = pedersen.compressInputs(createIndexedTreeLeaf(30, 0, 0)); - let e10 = pedersen.compress(index0Hash, index1Hash); - let e20 = pedersen.compress(e10, level1ZeroHash); + e10 = pedersen.compress(index0Hash, index1Hash); + e20 = pedersen.compress(e10, level1ZeroHash); root = pedersen.compress(e20, level2ZeroHash); await tree.appendLeaves([toBufferBE(30n, 32)]); @@ -242,7 +275,7 @@ describe('IndexedMerkleTreeSpecific', () => { expect(await tree.getSiblingPath(1n, true)).toEqual(new SiblingPath([index0Hash, level1ZeroHash, level2ZeroHash])); // ensure the committed state is correct - await verifyCommittedState(tree, initialRoot, 1n, emptySiblingPath); + await verifyCommittedState(tree, initialRoot, 1n, initialSiblingPath); /** * Add new value 10: @@ -267,7 +300,7 @@ describe('IndexedMerkleTreeSpecific', () => { expect(await tree.getSiblingPath(2n, true)).toEqual(new SiblingPath([zeroTreeLeafHash, e10, level2ZeroHash])); // ensure the committed state is correct - await verifyCommittedState(tree, initialRoot, 2n, emptySiblingPath); + await verifyCommittedState(tree, initialRoot, 2n, new SiblingPath([zeroTreeLeafHash, inite10, level2ZeroHash])); /** * Add new value 20: @@ -292,7 +325,7 @@ describe('IndexedMerkleTreeSpecific', () => { expect(await tree.getSiblingPath(3n, true)).toEqual(new SiblingPath([index2Hash, e10, level2ZeroHash])); // ensure the committed state is correct - await verifyCommittedState(tree, initialRoot, 3n, emptySiblingPath); + await verifyCommittedState(tree, initialRoot, 3n, new SiblingPath([zeroTreeLeafHash, inite10, level2ZeroHash])); // Add 2 empty values const emptyLeaves = [toBufferBE(0n, 32), toBufferBE(0n, 32)]; @@ -325,7 +358,7 @@ describe('IndexedMerkleTreeSpecific', () => { expect(tree.getNumLeaves(true)).toEqual(7n); // ensure the committed state is correct - await verifyCommittedState(tree, initialRoot, 6n, emptySiblingPath); + await verifyCommittedState(tree, initialRoot, 6n, new SiblingPath([zeroTreeLeafHash, level1ZeroHash, inite20])); // // check all uncommitted hash paths expect(await tree.getSiblingPath(0n, true)).toEqual(new SiblingPath([index1Hash, e11, e21])); @@ -338,9 +371,15 @@ describe('IndexedMerkleTreeSpecific', () => { expect(await tree.getSiblingPath(7n, true)).toEqual(new SiblingPath([index6Hash, level1ZeroHash, e20])); // check all committed hash paths - for (let i = 0; i < 8; i++) { - expect(await tree.getSiblingPath(BigInt(i), false)).toEqual(emptySiblingPath); - } + expect(await tree.getSiblingPath(0n, false)).toEqual(emptySiblingPath); + expect(await tree.getSiblingPath(1n, false)).toEqual(initialSiblingPath); + expect(await tree.getSiblingPath(2n, false)).toEqual(new SiblingPath([zeroTreeLeafHash, inite10, level2ZeroHash])); + expect(await tree.getSiblingPath(3n, false)).toEqual(new SiblingPath([zeroTreeLeafHash, inite10, level2ZeroHash])); + const e2SiblingPath = new SiblingPath([zeroTreeLeafHash, level1ZeroHash, inite20]); + expect(await tree.getSiblingPath(4n, false)).toEqual(e2SiblingPath); + expect(await tree.getSiblingPath(5n, false)).toEqual(e2SiblingPath); + expect(await tree.getSiblingPath(6n, false)).toEqual(e2SiblingPath); + expect(await tree.getSiblingPath(7n, false)).toEqual(e2SiblingPath); await tree.commit(); // check all committed hash paths equal uncommitted hash paths diff --git a/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts b/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts index 485e5e7d594..4a43f3e5259 100644 --- a/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts +++ b/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts @@ -80,7 +80,7 @@ export class IndexedTree implements MerkleTree { depth: number, prefilledSize = 0, ): Promise { - const underlying = await StandardMerkleTree.new(db, hasher, name, depth, hashEncodedTreeValue(initialLeaf, hasher)); + const underlying = await StandardMerkleTree.new(db, hasher, name, depth); const tree = new IndexedTree(underlying, hasher, db); await tree.init(prefilledSize); return tree; @@ -94,7 +94,7 @@ export class IndexedTree implements MerkleTree { * @returns The newly created tree. */ static async fromName(db: LevelUp, hasher: Hasher, name: string): Promise { - const underlying = await StandardMerkleTree.fromName(db, hasher, name, hashEncodedTreeValue(initialLeaf, hasher)); + const underlying = await StandardMerkleTree.fromName(db, hasher, name); const tree = new IndexedTree(underlying, hasher, db); await tree.initFromDb(); return tree; @@ -110,6 +110,7 @@ export class IndexedTree implements MerkleTree { /** * Returns the root of the tree. + * @param includeUncommitted Include uncommitted changes in the root computation. * @returns The root of the tree. */ public getRoot(includeUncommitted: boolean): Buffer { @@ -126,6 +127,7 @@ export class IndexedTree implements MerkleTree { /** * Returns the number of leaves in the tree. + * @param includeUncommitted include uncommitted leaves in the computation. * @returns The number of leaves in the tree. */ public getNumLeaves(includeUncommitted: boolean): bigint { @@ -164,6 +166,7 @@ export class IndexedTree implements MerkleTree { /** * Returns a sibling path for the element at the given index. * @param index - The index of the element. + * @param includeUncommitted include uncommitted leaves in the computation. * @returns A sibling path for the element at the given index. * Note: The sibling path is an array of sibling hashes, with the lowest hash (leaf hash) first, and the highest hash last. */ @@ -177,8 +180,13 @@ export class IndexedTree implements MerkleTree { * @param index - The index of the element */ public async updateLeaf(leaf: LeafData, index: bigint): Promise { + let encodedLeaf; + if (leaf.value == 0n) { + encodedLeaf = toBufferBE(0n, 32); + } else { + encodedLeaf = hashEncodedTreeValue(leaf, this.hasher); + } this.cachedLeaves[Number(index)] = leaf; - const encodedLeaf = hashEncodedTreeValue(leaf, this.hasher); await this.underlying.updateLeaf(encodedLeaf, index); } @@ -233,6 +241,7 @@ export class IndexedTree implements MerkleTree { /** * Finds the index of the largest leaf whose value is less than or equal to the provided value. * @param newValue - The new value to be inserted into the tree. + * @param includeUncommitted include uncommitted leaves in the computation. * @returns Tuple containing the leaf index and a flag to say if the value is a duplicate. */ public findIndexOfPreviousValue(newValue: bigint, includeUncommitted: boolean) { @@ -350,6 +359,7 @@ export class IndexedTree implements MerkleTree { /** * Gets the latest LeafData copy. * @param index - Index of the leaf of which to obtain the LeafData copy. + * @param includeUncommitted include uncommitted leaves in the computation. * @returns A copy of the leaf data at the given index or undefined if the leaf was not found. */ public getLatestLeafDataCopy(index: number, includeUncommitted: boolean): LeafData | undefined { @@ -363,6 +373,12 @@ export class IndexedTree implements MerkleTree { : undefined; } + /** + * Gets the value of the leaf at the given index. + * @param index - Index of the leaf of which to obtain the value. + * @param includeUncommitted include uncommitted leaves in the computation. + * @returns The value of the leaf at the given index or undefined if the leaf is empty. + */ public getLeafValue(index: bigint, includeUncommitted: boolean): Promise { const leaf = this.getLatestLeafDataCopy(Number(index), includeUncommitted); if (!leaf) return Promise.resolve(undefined); diff --git a/yarn-project/merkle-tree/src/standard_tree/standard_tree.ts b/yarn-project/merkle-tree/src/standard_tree/standard_tree.ts index eb89aa3a31c..4dd339e0642 100644 --- a/yarn-project/merkle-tree/src/standard_tree/standard_tree.ts +++ b/yarn-project/merkle-tree/src/standard_tree/standard_tree.ts @@ -112,6 +112,7 @@ export class StandardMerkleTree implements MerkleTree { /** * Returns the root of the tree. + * @param includeUncommitted - Whether to include uncommitted leaves in the computation. * @returns The root of the tree. */ public getRoot(includeUncommitted = false): Buffer { @@ -120,6 +121,7 @@ export class StandardMerkleTree implements MerkleTree { /** * Returns the number of leaves in the tree. + * @param includeUncommitted - Whether to include uncommitted leaves in the computation. * @returns The number of leaves in the tree. */ public getNumLeaves(includeUncommitted = false) { @@ -145,6 +147,7 @@ export class StandardMerkleTree implements MerkleTree { /** * Returns a sibling path for the element at the given index. * @param index - The index of the element. + * @param includeUncommitted - Whether to include uncommitted leaves in the computation. * @returns A sibling path for the element at the given index. * Note: The sibling path is an array of sibling hashes, with the lowest hash (leaf hash) first, and the highest hash last. */ @@ -221,6 +224,12 @@ export class StandardMerkleTree implements MerkleTree { return Promise.resolve(); } + /** + * Returns the value of the leaf at the given index. + * @param index the index of the leaf + * @param includeUncommitted include uncommitted leaves in the computation. + * @returns The value of the leaf at the given index, undefined if empty. + */ public getLeafValue(index: bigint, includeUncommitted = false): Promise { return this.getLatestValueAtIndex(this.depth, index, includeUncommitted); } @@ -260,6 +269,7 @@ export class StandardMerkleTree implements MerkleTree { * Returns the latest value at the given index. * @param level - The level of the tree. * @param index - The index of the element. + * @param includeUncommitted - Whether to include uncommitted leaves in the computation. * @returns The latest value at the given index. * Note: If the value is not in the cache, it will be fetched from the database. */