From 0eb17297433b6251b3a2e083a6ad57dd1560a3c5 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Tue, 21 May 2024 13:43:46 +0300 Subject: [PATCH 01/27] Initial working prototype --- core/lib/mini_merkle_tree/src/lib.rs | 125 +++++++++++++++++++------ core/lib/mini_merkle_tree/src/tests.rs | 53 +++++++++++ 2 files changed, 151 insertions(+), 27 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index f4f66d8fe61b..2979d61b7b0c 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -5,6 +5,7 @@ #![warn(clippy::all, clippy::pedantic)] #![allow(clippy::must_use_candidate, clippy::similar_names)] +use std::collections::VecDeque; use std::iter; use once_cell::sync::Lazy; @@ -27,8 +28,10 @@ const MAX_TREE_DEPTH: usize = 32; #[derive(Debug, Clone)] pub struct MiniMerkleTree { hasher: H, - hashes: Box<[H256]>, + hashes: VecDeque, binary_tree_size: usize, + head_index: usize, + left_cache: Vec, } impl MiniMerkleTree @@ -67,7 +70,7 @@ where leaves: impl Iterator, min_tree_size: Option, ) -> Self { - let hashes: Box<[H256]> = leaves.map(|bytes| hasher.hash_bytes(&bytes)).collect(); + let hashes: VecDeque<_> = leaves.map(|bytes| hasher.hash_bytes(&bytes)).collect(); let mut binary_tree_size = hashes.len().next_power_of_two(); if let Some(min_tree_size) = min_tree_size { assert!( @@ -76,8 +79,9 @@ where ); binary_tree_size = min_tree_size.max(binary_tree_size); } + let depth = tree_depth_by_size(binary_tree_size); assert!( - tree_depth_by_size(binary_tree_size) <= MAX_TREE_DEPTH, + depth <= MAX_TREE_DEPTH, "Tree contains more than {} items; this is not supported", 1 << MAX_TREE_DEPTH ); @@ -86,66 +90,133 @@ where hasher, hashes, binary_tree_size, + head_index: 0, + left_cache: vec![H256::default(); depth], } } /// Returns the root hash of this tree. /// # Panics /// Will panic if the constant below is invalid. - pub fn merkle_root(self) -> H256 { + pub fn merkle_root(&self) -> H256 { if self.hashes.is_empty() { let depth = tree_depth_by_size(self.binary_tree_size); self.hasher.empty_subtree_hash(depth) } else { - self.compute_merkle_root_and_path(0, None) + self.compute_merkle_root_and_path((0, 0), None, None) } } /// Returns the root hash and the Merkle proof for a leaf with the specified 0-based `index`. - pub fn merkle_root_and_path(self, index: usize) -> (H256, Vec) { - let mut merkle_path = vec![]; - let root_hash = self.compute_merkle_root_and_path(index, Some(&mut merkle_path)); - (root_hash, merkle_path) + pub fn merkle_root_and_path(&self, index: usize) -> (H256, Vec) { + let (mut left_path, mut right_path) = (vec![], vec![]); + let root_hash = self.compute_merkle_root_and_path( + (index, index), + Some((&mut left_path, &mut right_path)), + None, + ); + (root_hash, right_path) + } + + /// Adds a new leaf to the tree (replaces leftmost empty leaf). + /// If the tree is full, its size is doubled. + /// Note: empty leaves != zero leaves. + pub fn push(&mut self, leaf: [u8; LEAF_SIZE]) { + let leaf_hash = self.hasher.hash_bytes(&leaf); + self.hashes.push_back(leaf_hash); + if self.head_index + self.hashes.len() > self.binary_tree_size { + self.binary_tree_size *= 2; + self.left_cache.push(H256::default()); + } + } + + /// Caches the rightmost `count` leaves. + /// Does not affect the root hash, but makes it impossible to get the paths to the cached leaves. + /// + /// # Panics + /// Panics if `count` is greater than the number of non-cached leaves in the tree. + pub fn cache(&mut self, count: usize) { + assert!(self.hashes.len() >= count, "not enough leaves to pop"); + let depth = tree_depth_by_size(self.binary_tree_size); + let mut new_cache = vec![H256::default(); depth]; + self.compute_merkle_root_and_path((0, count - 1), None, Some(&mut new_cache)); + self.hashes.drain(0..count); + self.head_index += count; + self.left_cache = new_cache; } fn compute_merkle_root_and_path( - self, - mut index: usize, - mut merkle_path: Option<&mut Vec>, + &self, + (mut left, mut right): (usize, usize), + mut merkle_paths: Option<(&mut Vec, &mut Vec)>, + mut new_cache: Option<&mut Vec>, ) -> H256 { - assert!(index < self.hashes.len(), "invalid tree leaf index"); + // TODO: left is always 0 + + assert!(left < self.hashes.len(), "invalid tree leaf index"); + assert!(right < self.hashes.len(), "invalid tree leaf index"); let depth = tree_depth_by_size(self.binary_tree_size); - if let Some(merkle_path) = merkle_path.as_deref_mut() { - merkle_path.reserve(depth); + if let Some((left_path, right_path)) = &mut merkle_paths { + left_path.reserve(depth); + right_path.reserve(depth); } - let mut hashes = self.hashes; + let mut hashes = self.hashes.clone(); let mut level_len = hashes.len(); + let mut head_index = self.head_index; + for level in 0..depth { let empty_hash_at_level = self.hasher.empty_subtree_hash(level); - if let Some(merkle_path) = merkle_path.as_deref_mut() { - let adjacent_idx = index ^ 1; - let adjacent_hash = if adjacent_idx < level_len { - hashes[adjacent_idx] - } else { + let sibling_hash = |index: usize| { + if index == 0 && head_index % 2 == 1 { + self.left_cache[level] + } else if index == level_len - 1 && (head_index + index) % 2 == 0 { empty_hash_at_level + } else { + let sibling = ((head_index + index) ^ 1) - head_index; + hashes[sibling] + } + }; + + if let Some((left_path, right_path)) = &mut merkle_paths { + left_path.push(sibling_hash(left)); + right_path.push(sibling_hash(right)); + } + + let shift = head_index % 2; + + if let Some(new_cache) = new_cache.as_deref_mut() { + new_cache[level] = if (shift + right) % 2 == 0 { + hashes[right] + } else if right == 0 { + self.left_cache[level] + } else { + hashes[right - 1] }; - merkle_path.push(adjacent_hash); } - for i in 0..(level_len / 2) { - hashes[i] = self.hasher.compress(&hashes[2 * i], &hashes[2 * i + 1]); + if shift == 1 { + hashes[0] = self.hasher.compress(&self.left_cache[level], &hashes[0]); } - if level_len % 2 == 1 { + + for i in shift..((level_len + shift) / 2) { + hashes[i] = self + .hasher + .compress(&hashes[2 * i - shift], &hashes[2 * i + 1 - shift]); + } + + if (level_len + shift) % 2 == 1 { hashes[level_len / 2] = self .hasher .compress(&hashes[level_len - 1], &empty_hash_at_level); } - index /= 2; - level_len = level_len / 2 + level_len % 2; + left = (left + shift) / 2; // TODO: it's always 0 anyway + right = (right + shift) / 2; + level_len = level_len / 2 + ((head_index % 2) | (level_len % 2)); + head_index /= 2; } hashes[0] } diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index c534c87523cd..d7d6e7387b38 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -28,6 +28,8 @@ fn hash_of_empty_tree_with_single_item() { println!("checking tree with {len} items"); let tree = MiniMerkleTree::new(iter::once([0_u8; 88]), Some(len)); assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); + // tree.pop(1); + // assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); } } @@ -44,6 +46,8 @@ fn hash_of_large_empty_tree_with_multiple_items() { let tree = MiniMerkleTree::new(leaves, None); let depth = tree_depth_by_size(tree_size); assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); + // tree.pop(len / 2); + // assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); } } @@ -217,3 +221,52 @@ fn merkle_proofs_are_valid_in_very_small_trees() { } } } + +#[test] +fn dynamic_merkle_tree_growth() { + let mut tree = MiniMerkleTree::new(iter::empty(), None); + assert_eq!(tree.binary_tree_size, 1); + + for len in 1..=8_usize { + tree.push([0; 88]); + assert_eq!(tree.binary_tree_size, len.next_power_of_two()); + } + + // Shouldn't shink after popping + tree.cache(6); + assert_eq!(tree.binary_tree_size, 8); +} + +#[test] +fn merkle_tree_popping() { + let leaves = (1_u8..=50).map(|byte| [byte; 88]); + let mut tree = MiniMerkleTree::new(leaves.clone(), None); + + let expected_root_hash: H256 = + "0x2da23c4270b612710106f3e02e9db9fa42663751869f48d952fa7a0eaaa92475" + .parse() + .unwrap(); + + let expected_path = [ + "0x39f19437665159060317aab8b417352df18779f50b68a6bf6bc9c94dff8c98ca", + "0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0", + "0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111", + "0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa", + "0x6edd774c0492cb4c825e4684330fd1c3259866606d47241ebf2a29af0190b5b1", + "0x29694afc5d76ad6ee48e9382b1cf724c503c5742aa905700e290845c56d1b488", + ] + .map(|s| s.parse::().unwrap()); + + for i in 0..50 { + let (root_hash, path) = tree.merkle_root_and_path(49 - i); + assert_eq!(root_hash, expected_root_hash); + assert_eq!(path, expected_path); + tree.cache(1); + } +} + +// TODO: +// - test for `push` and `pop` with different parities of arrays +// - test for getting & proving intervals +// - test for extremes (1, 2 nodes, caching everything, etc.) +// - empty tree From 558422486074adc3543616de501f090bec6e3055 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Tue, 21 May 2024 15:39:14 +0300 Subject: [PATCH 02/27] Add more tests --- core/lib/mini_merkle_tree/benches/tree.rs | 6 +-- core/lib/mini_merkle_tree/src/lib.rs | 46 +++++++++----------- core/lib/mini_merkle_tree/src/tests.rs | 52 ++++++++++++++++++----- 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/core/lib/mini_merkle_tree/benches/tree.rs b/core/lib/mini_merkle_tree/benches/tree.rs index 8ea4128ac34d..c59c0dc28306 100644 --- a/core/lib/mini_merkle_tree/benches/tree.rs +++ b/core/lib/mini_merkle_tree/benches/tree.rs @@ -10,11 +10,7 @@ const TREE_SIZES: &[usize] = &[32, 64, 128, 256, 512, 1_024]; fn compute_merkle_root(bencher: &mut Bencher<'_>, tree_size: usize) { let leaves = (0..tree_size).map(|i| [i as u8; 88]); let tree = MiniMerkleTree::new(leaves, None); - bencher.iter_batched( - || tree.clone(), - MiniMerkleTree::merkle_root, - BatchSize::SmallInput, - ); + bencher.iter_batched(|| &tree, MiniMerkleTree::merkle_root, BatchSize::SmallInput); } fn compute_merkle_path(bencher: &mut Bencher<'_>, tree_size: usize) { diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 2979d61b7b0c..866d7ed619bf 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -103,18 +103,15 @@ where let depth = tree_depth_by_size(self.binary_tree_size); self.hasher.empty_subtree_hash(depth) } else { - self.compute_merkle_root_and_path((0, 0), None, None) + self.compute_merkle_root_and_path(0, None, None) } } /// Returns the root hash and the Merkle proof for a leaf with the specified 0-based `index`. pub fn merkle_root_and_path(&self, index: usize) -> (H256, Vec) { let (mut left_path, mut right_path) = (vec![], vec![]); - let root_hash = self.compute_merkle_root_and_path( - (index, index), - Some((&mut left_path, &mut right_path)), - None, - ); + let root_hash = + self.compute_merkle_root_and_path(index, Some((&mut left_path, &mut right_path)), None); (root_hash, right_path) } @@ -134,12 +131,13 @@ where /// Does not affect the root hash, but makes it impossible to get the paths to the cached leaves. /// /// # Panics + /// /// Panics if `count` is greater than the number of non-cached leaves in the tree. pub fn cache(&mut self, count: usize) { - assert!(self.hashes.len() >= count, "not enough leaves to pop"); + assert!(self.hashes.len() >= count, "not enough leaves to cache"); let depth = tree_depth_by_size(self.binary_tree_size); let mut new_cache = vec![H256::default(); depth]; - self.compute_merkle_root_and_path((0, count - 1), None, Some(&mut new_cache)); + self.compute_merkle_root_and_path(count - 1, None, Some(&mut new_cache)); self.hashes.drain(0..count); self.head_index += count; self.left_cache = new_cache; @@ -147,14 +145,11 @@ where fn compute_merkle_root_and_path( &self, - (mut left, mut right): (usize, usize), + mut right_index: usize, mut merkle_paths: Option<(&mut Vec, &mut Vec)>, mut new_cache: Option<&mut Vec>, ) -> H256 { - // TODO: left is always 0 - - assert!(left < self.hashes.len(), "invalid tree leaf index"); - assert!(right < self.hashes.len(), "invalid tree leaf index"); + assert!(right_index < self.hashes.len(), "invalid tree leaf index"); let depth = tree_depth_by_size(self.binary_tree_size); if let Some((left_path, right_path)) = &mut merkle_paths { @@ -181,40 +176,39 @@ where }; if let Some((left_path, right_path)) = &mut merkle_paths { - left_path.push(sibling_hash(left)); - right_path.push(sibling_hash(right)); + left_path.push(sibling_hash(0)); + right_path.push(sibling_hash(right_index)); } - let shift = head_index % 2; + let parity = head_index % 2; if let Some(new_cache) = new_cache.as_deref_mut() { - new_cache[level] = if (shift + right) % 2 == 0 { - hashes[right] - } else if right == 0 { + new_cache[level] = if (parity + right_index) % 2 == 0 { + hashes[right_index] + } else if right_index == 0 { self.left_cache[level] } else { - hashes[right - 1] + hashes[right_index - 1] }; } - if shift == 1 { + if parity == 1 { hashes[0] = self.hasher.compress(&self.left_cache[level], &hashes[0]); } - for i in shift..((level_len + shift) / 2) { + for i in parity..((level_len + parity) / 2) { hashes[i] = self .hasher - .compress(&hashes[2 * i - shift], &hashes[2 * i + 1 - shift]); + .compress(&hashes[2 * i - parity], &hashes[2 * i + 1 - parity]); } - if (level_len + shift) % 2 == 1 { + if (level_len + parity) % 2 == 1 { hashes[level_len / 2] = self .hasher .compress(&hashes[level_len - 1], &empty_hash_at_level); } - left = (left + shift) / 2; // TODO: it's always 0 anyway - right = (right + shift) / 2; + right_index = (right_index + parity) / 2; level_len = level_len / 2 + ((head_index % 2) | (level_len % 2)); head_index /= 2; } diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index d7d6e7387b38..9357f807c349 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -28,8 +28,6 @@ fn hash_of_empty_tree_with_single_item() { println!("checking tree with {len} items"); let tree = MiniMerkleTree::new(iter::once([0_u8; 88]), Some(len)); assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); - // tree.pop(1); - // assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); } } @@ -46,8 +44,6 @@ fn hash_of_large_empty_tree_with_multiple_items() { let tree = MiniMerkleTree::new(leaves, None); let depth = tree_depth_by_size(tree_size); assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); - // tree.pop(len / 2); - // assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); } } @@ -226,20 +222,25 @@ fn merkle_proofs_are_valid_in_very_small_trees() { fn dynamic_merkle_tree_growth() { let mut tree = MiniMerkleTree::new(iter::empty(), None); assert_eq!(tree.binary_tree_size, 1); + assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(1)); for len in 1..=8_usize { tree.push([0; 88]); assert_eq!(tree.binary_tree_size, len.next_power_of_two()); + + let depth = tree_depth_by_size(tree.binary_tree_size); + assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); } - // Shouldn't shink after popping + // Shouldn't shink after caching tree.cache(6); assert_eq!(tree.binary_tree_size, 8); + assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(3)); } #[test] -fn merkle_tree_popping() { - let leaves = (1_u8..=50).map(|byte| [byte; 88]); +fn caching_leaves() { + let leaves = (1..=50).map(|byte| [byte; 88]); let mut tree = MiniMerkleTree::new(leaves.clone(), None); let expected_root_hash: H256 = @@ -263,10 +264,41 @@ fn merkle_tree_popping() { assert_eq!(path, expected_path); tree.cache(1); } + + let mut tree = MiniMerkleTree::new(leaves, None); + for i in 0..10 { + let (root_hash, path) = tree.merkle_root_and_path(49 - i * 5); + assert_eq!(root_hash, expected_root_hash); + assert_eq!(path, expected_path); + tree.cache(5); + } +} + +#[test] +#[allow(clippy::cast_possible_truncation)] // truncation is intentional +fn pushing_new_leaves() { + let mut tree = MiniMerkleTree::new(iter::empty(), None); + + let expected_roots = [ + "0x6f7a80e6ee852bd309ee9153c6157535092aa706f5c6e51ff199a4be012be1fd", + "0xda895440272a4c4a0b950753c77fd08db0ce57e21c98b75d154c341cbe5f31ac", + "0x74e62d47c142e2a5b0f2c71ea0f8bcca8d767f0edf7ec7b9134371f5bfef7b8a", + "0xe44bb0f3915370e8f432de0830c52d5dc7bbf1a46a21cccb462cefaf3f4cce4d", + "0x88443c3b1b9206955625b5722c06bca3207d39f6044780af885d5f09f6e615a1", + ] + .map(|s| s.parse::().unwrap()); + + for (i, expected_root) in expected_roots.iter().enumerate() { + let number = i as u8 + 1; + tree.push([number; 88]); + tree.push([number; 88]); + tree.push([number; 88]); + assert_eq!(tree.merkle_root(), *expected_root); + + tree.cache(2); + assert_eq!(tree.merkle_root(), *expected_root); + } } // TODO: -// - test for `push` and `pop` with different parities of arrays // - test for getting & proving intervals -// - test for extremes (1, 2 nodes, caching everything, etc.) -// - empty tree From e5c8a525d2d8ecb359dc5228d8c3e7ae42c04393 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 22 May 2024 02:05:42 +0300 Subject: [PATCH 03/27] More tests --- core/lib/mini_merkle_tree/src/lib.rs | 17 ++++++- core/lib/mini_merkle_tree/src/tests.rs | 69 +++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 866d7ed619bf..3732fc8bba9f 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -115,6 +115,21 @@ where (root_hash, right_path) } + /// Returns the root hash and the Merkle proof for an interval of leafs. + /// The interval is [0, `length`), where `0` is the leftmost uncached leaf. + pub fn merkle_root_and_paths_for_interval( + &self, + length: usize, + ) -> (H256, Vec, Vec) { + let (mut left_path, mut right_path) = (vec![], vec![]); + let root_hash = self.compute_merkle_root_and_path( + length - 1, + Some((&mut left_path, &mut right_path)), + None, + ); + (root_hash, left_path, right_path) + } + /// Adds a new leaf to the tree (replaces leftmost empty leaf). /// If the tree is full, its size is doubled. /// Note: empty leaves != zero leaves. @@ -209,7 +224,7 @@ where } right_index = (right_index + parity) / 2; - level_len = level_len / 2 + ((head_index % 2) | (level_len % 2)); + level_len = level_len / 2 + (parity | (level_len % 2)); head_index /= 2; } hashes[0] diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 9357f807c349..8259d40005b4 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -156,24 +156,77 @@ fn verify_merkle_proof( assert_eq!(hash, merkle_root); } +fn verify_interval_merkle_proof( + items: &[[u8; 88]], + mut index: usize, + left_path: &[H256], + right_path: &[H256], + merkle_root: H256, +) { + assert_eq!(left_path.len(), right_path.len()); + + let hasher = KeccakHasher; + let mut hashes: Vec<_> = items.iter().map(|item| hasher.hash_bytes(item)).collect(); + let mut level_len = hashes.len(); + + for (left_item, right_item) in left_path.iter().zip(right_path.iter()) { + let parity = index % 2; + if parity == 1 { + hashes[0] = hasher.compress(left_item, &hashes[0]); + } + + for i in parity..((level_len + parity) / 2) { + hashes[i] = hasher.compress(&hashes[2 * i - parity], &hashes[2 * i + 1 - parity]); + } + + if (level_len + parity) % 2 == 1 { + hashes[level_len / 2] = hasher.compress(&hashes[level_len - 1], right_item); + } + + level_len = level_len / 2 + (parity | (level_len % 2)); + index /= 2; + } + + assert_eq!(hashes[0], merkle_root); +} + #[test] fn merkle_proofs_are_valid_in_small_tree() { let leaves = (1_u8..=50).map(|byte| [byte; 88]); let tree = MiniMerkleTree::new(leaves.clone(), None); for (i, item) in leaves.enumerate() { - let (merkle_root, path) = tree.clone().merkle_root_and_path(i); + let (merkle_root, path) = tree.merkle_root_and_path(i); verify_merkle_proof(&item, i, 50, &path, merkle_root); } } +#[test] +fn merkle_proofs_are_valid_for_intervals() { + let mut leaves: Vec<_> = (1_u8..=50).map(|byte| [byte; 88]).collect(); + let mut tree = MiniMerkleTree::new(leaves.clone().into_iter(), None); + + for i in 1..=50 { + let (merkle_root, left_path, right_path) = tree.merkle_root_and_paths_for_interval(i); + verify_interval_merkle_proof(&leaves[..i], 0, &left_path, &right_path, merkle_root); + } + + tree.cache(25); + leaves.drain(..25); + + for i in 1..=25 { + let (merkle_root, left_path, right_path) = tree.merkle_root_and_paths_for_interval(i); + verify_interval_merkle_proof(&leaves[..i], 25, &left_path, &right_path, merkle_root); + } +} + #[test] fn merkle_proofs_are_valid_in_larger_tree() { let leaves = (1_u8..=255).map(|byte| [byte; 88]); let tree = MiniMerkleTree::new(leaves.clone(), Some(512)); for (i, item) in leaves.enumerate() { - let (merkle_root, path) = tree.clone().merkle_root_and_path(i); + let (merkle_root, path) = tree.merkle_root_and_path(i); verify_merkle_proof(&item, i, 512, &path, merkle_root); } } @@ -185,14 +238,14 @@ fn merkle_proofs_are_valid_in_very_large_tree() { let tree = MiniMerkleTree::new(leaves.clone(), None); for (i, item) in leaves.clone().enumerate().step_by(61) { - let (merkle_root, path) = tree.clone().merkle_root_and_path(i); + let (merkle_root, path) = tree.merkle_root_and_path(i); verify_merkle_proof(&item, i, 1 << 14, &path, merkle_root); } let tree_with_min_size = MiniMerkleTree::new(leaves.clone(), Some(512)); - assert_eq!(tree_with_min_size.clone().merkle_root(), tree.merkle_root()); + assert_eq!(tree_with_min_size.merkle_root(), tree.merkle_root()); for (i, item) in leaves.enumerate().step_by(61) { - let (merkle_root, path) = tree_with_min_size.clone().merkle_root_and_path(i); + let (merkle_root, path) = tree_with_min_size.merkle_root_and_path(i); verify_merkle_proof(&item, i, 1 << 14, &path, merkle_root); } } @@ -205,14 +258,14 @@ fn merkle_proofs_are_valid_in_very_small_trees() { let tree = MiniMerkleTree::new(leaves.clone(), None); let item_count = usize::from(item_count).next_power_of_two(); for (i, item) in leaves.clone().enumerate() { - let (merkle_root, path) = tree.clone().merkle_root_and_path(i); + let (merkle_root, path) = tree.merkle_root_and_path(i); verify_merkle_proof(&item, i, item_count, &path, merkle_root); } let tree_with_min_size = MiniMerkleTree::new(leaves.clone(), Some(512)); - assert_ne!(tree_with_min_size.clone().merkle_root(), tree.merkle_root()); + assert_ne!(tree_with_min_size.merkle_root(), tree.merkle_root()); for (i, item) in leaves.enumerate() { - let (merkle_root, path) = tree_with_min_size.clone().merkle_root_and_path(i); + let (merkle_root, path) = tree_with_min_size.merkle_root_and_path(i); verify_merkle_proof(&item, i, 512, &path, merkle_root); } } From db11b6c358875962f8989081f94c489d3344165d Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 22 May 2024 02:47:59 +0300 Subject: [PATCH 04/27] Make the loop a bit more readable --- core/lib/mini_merkle_tree/src/lib.rs | 36 ++++++++++++-------------- core/lib/mini_merkle_tree/src/tests.rs | 28 +++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 3732fc8bba9f..357293361839 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -5,8 +5,7 @@ #![warn(clippy::all, clippy::pedantic)] #![allow(clippy::must_use_candidate, clippy::similar_names)] -use std::collections::VecDeque; -use std::iter; +use std::{collections::VecDeque, iter}; use once_cell::sync::Lazy; @@ -195,10 +194,8 @@ where right_path.push(sibling_hash(right_index)); } - let parity = head_index % 2; - if let Some(new_cache) = new_cache.as_deref_mut() { - new_cache[level] = if (parity + right_index) % 2 == 0 { + new_cache[level] = if (head_index + right_index) % 2 == 0 { hashes[right_index] } else if right_index == 0 { self.left_cache[level] @@ -207,24 +204,25 @@ where }; } - if parity == 1 { - hashes[0] = self.hasher.compress(&self.left_cache[level], &hashes[0]); - } - - for i in parity..((level_len + parity) / 2) { - hashes[i] = self - .hasher - .compress(&hashes[2 * i - parity], &hashes[2 * i + 1 - parity]); - } + let parity = head_index % 2; + let next_level_len = level_len / 2 + (parity | (level_len % 2)); - if (level_len + parity) % 2 == 1 { - hashes[level_len / 2] = self - .hasher - .compress(&hashes[level_len - 1], &empty_hash_at_level); + for i in 0..next_level_len { + let lhs = if i == 0 && parity == 1 { + self.left_cache[level] + } else { + hashes[2 * i - parity] + }; + let rhs = if i == next_level_len - 1 && (level_len - parity) % 2 == 1 { + empty_hash_at_level + } else { + hashes[2 * i + 1 - parity] + }; + hashes[i] = self.hasher.compress(&lhs, &rhs); } right_index = (right_index + parity) / 2; - level_len = level_len / 2 + (parity | (level_len % 2)); + level_len = next_level_len; head_index /= 2; } hashes[0] diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 8259d40005b4..418785bfd0a7 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -171,19 +171,23 @@ fn verify_interval_merkle_proof( for (left_item, right_item) in left_path.iter().zip(right_path.iter()) { let parity = index % 2; - if parity == 1 { - hashes[0] = hasher.compress(left_item, &hashes[0]); + let next_level_len = level_len / 2 + (parity | (level_len % 2)); + + for i in 0..next_level_len { + let lhs = if i == 0 && parity == 1 { + left_item + } else { + &hashes[2 * i - parity] + }; + let rhs = if i == next_level_len - 1 && (level_len - parity) % 2 == 1 { + right_item + } else { + &hashes[2 * i + 1 - parity] + }; + hashes[i] = hasher.compress(lhs, rhs); } - for i in parity..((level_len + parity) / 2) { - hashes[i] = hasher.compress(&hashes[2 * i - parity], &hashes[2 * i + 1 - parity]); - } - - if (level_len + parity) % 2 == 1 { - hashes[level_len / 2] = hasher.compress(&hashes[level_len - 1], right_item); - } - - level_len = level_len / 2 + (parity | (level_len % 2)); + level_len = next_level_len; index /= 2; } @@ -275,7 +279,7 @@ fn merkle_proofs_are_valid_in_very_small_trees() { fn dynamic_merkle_tree_growth() { let mut tree = MiniMerkleTree::new(iter::empty(), None); assert_eq!(tree.binary_tree_size, 1); - assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(1)); + assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(0)); for len in 1..=8_usize { tree.push([0; 88]); From e3e19bc1c1f2dc64d55705379b77018adaecd67a Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 22 May 2024 15:57:10 +0300 Subject: [PATCH 05/27] Fix an edgecase bug --- core/lib/mini_merkle_tree/src/lib.rs | 12 ++++++++++-- core/lib/mini_merkle_tree/src/tests.rs | 18 +++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 357293361839..325914487ca4 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -90,7 +90,7 @@ where hashes, binary_tree_size, head_index: 0, - left_cache: vec![H256::default(); depth], + left_cache: vec![H256::default(); depth + 1], } } @@ -150,10 +150,11 @@ where pub fn cache(&mut self, count: usize) { assert!(self.hashes.len() >= count, "not enough leaves to cache"); let depth = tree_depth_by_size(self.binary_tree_size); - let mut new_cache = vec![H256::default(); depth]; + let mut new_cache = vec![H256::default(); depth + 1]; self.compute_merkle_root_and_path(count - 1, None, Some(&mut new_cache)); self.hashes.drain(0..count); self.head_index += count; + debug_assert!(self.left_cache.len() == new_cache.len()); self.left_cache = new_cache; } @@ -225,6 +226,13 @@ where level_len = next_level_len; head_index /= 2; } + + if let Some(new_cache) = new_cache { + // It is important to cache the root as well, in case + // we just cached all elements and will grow on the next push. + new_cache[depth] = hashes[0]; + } + hashes[0] } } diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 418785bfd0a7..d85f3742354d 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -158,7 +158,7 @@ fn verify_merkle_proof( fn verify_interval_merkle_proof( items: &[[u8; 88]], - mut index: usize, + mut head_index: usize, left_path: &[H256], right_path: &[H256], merkle_root: H256, @@ -170,7 +170,7 @@ fn verify_interval_merkle_proof( let mut level_len = hashes.len(); for (left_item, right_item) in left_path.iter().zip(right_path.iter()) { - let parity = index % 2; + let parity = head_index % 2; let next_level_len = level_len / 2 + (parity | (level_len % 2)); for i in 0..next_level_len { @@ -188,7 +188,7 @@ fn verify_interval_merkle_proof( } level_len = next_level_len; - index /= 2; + head_index /= 2; } assert_eq!(hashes[0], merkle_root); @@ -357,5 +357,13 @@ fn pushing_new_leaves() { } } -// TODO: -// - test for getting & proving intervals +#[test] +fn cache_all_and_grow() { + let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), None); + tree.cache(4); + tree.push([1; 88]); + let expected_root = "0xfa4c924185122254742622b10b68df8de89d33f685ee579f37a50c552b0d245d" + .parse() + .unwrap(); + assert_eq!(tree.merkle_root(), expected_root); +} From 66d82b3cde2f9cc6a507968ce3c7afe667b57b6b Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 22 May 2024 17:52:49 +0300 Subject: [PATCH 06/27] Fix 1 more bug and add comments --- core/lib/mini_merkle_tree/src/lib.rs | 36 +++++++++++++++++++++----- core/lib/mini_merkle_tree/src/tests.rs | 13 ++++++++++ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 325914487ca4..d14c0fce5fed 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -19,11 +19,19 @@ use zksync_crypto::hasher::{keccak::KeccakHasher, Hasher}; /// we unlikely to ever hit. const MAX_TREE_DEPTH: usize = 32; -/// In-memory Merkle tree of bounded depth (no more than 10). +/// In-memory Merkle tree of bounded depth (no more than 32). /// /// The tree is left-leaning, meaning that during its initialization, the size of a tree /// can be specified larger than the number of provided leaves. In this case, the remaining leaves /// will be considered to equal `[0_u8; LEAF_SIZE]`. +/// +/// The tree has dynamic size, meaning that it can grow by a factor of 2 when the number of leaves +/// exceeds the current tree size. It does not shrink. +/// +/// The tree is optimized for the case when the queries are performed on the rightmost leaves +/// and the leftmost leaves are cached. Caching enables the merkle roots and paths to be computed +/// in `O(n)` time, where `n` is the number of uncached leaves (in contrast to the total number of +/// leaves). Cache itself only takes up `O(depth)` space. #[derive(Debug, Clone)] pub struct MiniMerkleTree { hasher: H, @@ -64,6 +72,7 @@ where /// Panics if any of the following conditions applies: /// /// - `min_tree_size` (if supplied) is not a power of 2. + /// - The number of leaves is greater than `2^32`. pub fn with_hasher( hasher: H, leaves: impl Iterator, @@ -94,19 +103,27 @@ where } } + /// Returns `true` if the tree is empty. + pub fn is_empty(&self) -> bool { + self.head_index == 0 && self.hashes.is_empty() + } + /// Returns the root hash of this tree. - /// # Panics - /// Will panic if the constant below is invalid. pub fn merkle_root(&self) -> H256 { if self.hashes.is_empty() { let depth = tree_depth_by_size(self.binary_tree_size); - self.hasher.empty_subtree_hash(depth) + if self.head_index == 0 { + self.hasher.empty_subtree_hash(depth) + } else { + self.left_cache[depth] + } } else { self.compute_merkle_root_and_path(0, None, None) } } /// Returns the root hash and the Merkle proof for a leaf with the specified 0-based `index`. + /// `index` is relative to the leftmost uncached leaf. pub fn merkle_root_and_path(&self, index: usize) -> (H256, Vec) { let (mut left_path, mut right_path) = (vec![], vec![]); let root_hash = @@ -114,7 +131,7 @@ where (root_hash, right_path) } - /// Returns the root hash and the Merkle proof for an interval of leafs. + /// Returns the root hash and the Merkle proofs for an interval of leafs. /// The interval is [0, `length`), where `0` is the leftmost uncached leaf. pub fn merkle_root_and_paths_for_interval( &self, @@ -143,9 +160,7 @@ where /// Caches the rightmost `count` leaves. /// Does not affect the root hash, but makes it impossible to get the paths to the cached leaves. - /// /// # Panics - /// /// Panics if `count` is greater than the number of non-cached leaves in the tree. pub fn cache(&mut self, count: usize) { assert!(self.hashes.len() >= count, "not enough leaves to cache"); @@ -185,6 +200,7 @@ where } else if index == level_len - 1 && (head_index + index) % 2 == 0 { empty_hash_at_level } else { + // `index` is relative to `head_index` let sibling = ((head_index + index) ^ 1) - head_index; hashes[sibling] } @@ -196,6 +212,8 @@ where } if let Some(new_cache) = new_cache.as_deref_mut() { + // We cache the rightmost left child on the current level + // within the given interval. new_cache[level] = if (head_index + right_index) % 2 == 0 { hashes[right_index] } else if right_index == 0 { @@ -206,15 +224,19 @@ where } let parity = head_index % 2; + // If our queue starts from the right child or ends with the left child, + // we need to round the length up. let next_level_len = level_len / 2 + (parity | (level_len % 2)); for i in 0..next_level_len { let lhs = if i == 0 && parity == 1 { + // If the leftmost element is a right child, we need to use cache. self.left_cache[level] } else { hashes[2 * i - parity] }; let rhs = if i == next_level_len - 1 && (level_len - parity) % 2 == 1 { + // If the rightmost element is a left child, we need to use the empty hash. empty_hash_at_level } else { hashes[2 * i + 1 - parity] diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index d85f3742354d..bea81c0c37fe 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -367,3 +367,16 @@ fn cache_all_and_grow() { .unwrap(); assert_eq!(tree.merkle_root(), expected_root); } + +#[test] +fn cache_all_and_check_root() { + let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), None); + let root = tree.merkle_root(); + tree.cache(4); + assert_eq!(tree.merkle_root(), root); + + let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), Some(8)); + let root = tree.merkle_root(); + tree.cache(4); + assert_eq!(tree.merkle_root(), root); +} From 639e241b14792fbbaebca2609baaf3971688dece Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Fri, 24 May 2024 19:33:46 +0300 Subject: [PATCH 07/27] Make it a bit more consistent --- core/lib/mini_merkle_tree/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index d14c0fce5fed..28581fa02e00 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -164,8 +164,7 @@ where /// Panics if `count` is greater than the number of non-cached leaves in the tree. pub fn cache(&mut self, count: usize) { assert!(self.hashes.len() >= count, "not enough leaves to cache"); - let depth = tree_depth_by_size(self.binary_tree_size); - let mut new_cache = vec![H256::default(); depth + 1]; + let mut new_cache = vec![]; self.compute_merkle_root_and_path(count - 1, None, Some(&mut new_cache)); self.hashes.drain(0..count); self.head_index += count; @@ -186,6 +185,9 @@ where left_path.reserve(depth); right_path.reserve(depth); } + if let Some(new_cache) = new_cache.as_deref_mut() { + new_cache.reserve(depth + 1); + } let mut hashes = self.hashes.clone(); let mut level_len = hashes.len(); @@ -214,13 +216,14 @@ where if let Some(new_cache) = new_cache.as_deref_mut() { // We cache the rightmost left child on the current level // within the given interval. - new_cache[level] = if (head_index + right_index) % 2 == 0 { + let cache = if (head_index + right_index) % 2 == 0 { hashes[right_index] } else if right_index == 0 { self.left_cache[level] } else { hashes[right_index - 1] }; + new_cache.push(cache); } let parity = head_index % 2; @@ -252,7 +255,7 @@ where if let Some(new_cache) = new_cache { // It is important to cache the root as well, in case // we just cached all elements and will grow on the next push. - new_cache[depth] = hashes[0]; + new_cache.push(hashes[0]); } hashes[0] From 8c470d88e763cef8026535e6704d29cf3dd97f9e Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Fri, 24 May 2024 19:38:40 +0300 Subject: [PATCH 08/27] Remove useless cache operations --- core/lib/mini_merkle_tree/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 28581fa02e00..6a2c2627b01f 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -99,7 +99,7 @@ where hashes, binary_tree_size, head_index: 0, - left_cache: vec![H256::default(); depth + 1], + left_cache: vec![], } } @@ -154,7 +154,6 @@ where self.hashes.push_back(leaf_hash); if self.head_index + self.hashes.len() > self.binary_tree_size { self.binary_tree_size *= 2; - self.left_cache.push(H256::default()); } } @@ -168,7 +167,6 @@ where self.compute_merkle_root_and_path(count - 1, None, Some(&mut new_cache)); self.hashes.drain(0..count); self.head_index += count; - debug_assert!(self.left_cache.len() == new_cache.len()); self.left_cache = new_cache; } From 4a558da203dcd3264629ec484ebe985c565d436f Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Tue, 28 May 2024 13:21:48 +0300 Subject: [PATCH 09/27] Spelling fix --- checks-config/era.dic | 1 + core/lib/mini_merkle_tree/src/tests.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/checks-config/era.dic b/checks-config/era.dic index 063c129b3e66..9bc961c62b27 100644 --- a/checks-config/era.dic +++ b/checks-config/era.dic @@ -969,3 +969,4 @@ preloaded e2e upcasting foundryup +uncached diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index bea81c0c37fe..576020e2e02b 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -289,7 +289,7 @@ fn dynamic_merkle_tree_growth() { assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(depth)); } - // Shouldn't shink after caching + // Shouldn't shrink after caching tree.cache(6); assert_eq!(tree.binary_tree_size, 8); assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(3)); From 1404ecebbad4f01218fc072039e71ddbbe8d6d34 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Tue, 28 May 2024 22:38:37 +0300 Subject: [PATCH 10/27] Use right_path as cache --- core/lib/mini_merkle_tree/src/lib.rs | 53 +++++++++------------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 6a2c2627b01f..85fb6c73bc03 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -87,9 +87,8 @@ where ); binary_tree_size = min_tree_size.max(binary_tree_size); } - let depth = tree_depth_by_size(binary_tree_size); assert!( - depth <= MAX_TREE_DEPTH, + tree_depth_by_size(binary_tree_size) <= MAX_TREE_DEPTH, "Tree contains more than {} items; this is not supported", 1 << MAX_TREE_DEPTH ); @@ -125,9 +124,8 @@ where /// Returns the root hash and the Merkle proof for a leaf with the specified 0-based `index`. /// `index` is relative to the leftmost uncached leaf. pub fn merkle_root_and_path(&self, index: usize) -> (H256, Vec) { - let (mut left_path, mut right_path) = (vec![], vec![]); - let root_hash = - self.compute_merkle_root_and_path(index, Some((&mut left_path, &mut right_path)), None); + let mut right_path = vec![]; + let root_hash = self.compute_merkle_root_and_path(index, None, Some(&mut right_path)); (root_hash, right_path) } @@ -140,8 +138,8 @@ where let (mut left_path, mut right_path) = (vec![], vec![]); let root_hash = self.compute_merkle_root_and_path( length - 1, - Some((&mut left_path, &mut right_path)), - None, + Some(&mut left_path), + Some(&mut right_path), ); (root_hash, left_path, right_path) } @@ -164,27 +162,27 @@ where pub fn cache(&mut self, count: usize) { assert!(self.hashes.len() >= count, "not enough leaves to cache"); let mut new_cache = vec![]; - self.compute_merkle_root_and_path(count - 1, None, Some(&mut new_cache)); + let root = self.compute_merkle_root_and_path(count, None, Some(&mut new_cache)); self.hashes.drain(0..count); self.head_index += count; + new_cache.push(root); self.left_cache = new_cache; } fn compute_merkle_root_and_path( &self, mut right_index: usize, - mut merkle_paths: Option<(&mut Vec, &mut Vec)>, - mut new_cache: Option<&mut Vec>, + mut left_path: Option<&mut Vec>, + mut right_path: Option<&mut Vec>, ) -> H256 { - assert!(right_index < self.hashes.len(), "invalid tree leaf index"); + // assert!(right_index < self.hashes.len(), "invalid tree leaf index"); let depth = tree_depth_by_size(self.binary_tree_size); - if let Some((left_path, right_path)) = &mut merkle_paths { + if let Some(left_path) = left_path.as_deref_mut() { left_path.reserve(depth); - right_path.reserve(depth); } - if let Some(new_cache) = new_cache.as_deref_mut() { - new_cache.reserve(depth + 1); + if let Some(right_path) = right_path.as_deref_mut() { + right_path.reserve(depth); } let mut hashes = self.hashes.clone(); @@ -202,26 +200,15 @@ where } else { // `index` is relative to `head_index` let sibling = ((head_index + index) ^ 1) - head_index; - hashes[sibling] + hashes.get(sibling).copied().unwrap_or_default() } }; - if let Some((left_path, right_path)) = &mut merkle_paths { + if let Some(left_path) = left_path.as_deref_mut() { left_path.push(sibling_hash(0)); - right_path.push(sibling_hash(right_index)); } - - if let Some(new_cache) = new_cache.as_deref_mut() { - // We cache the rightmost left child on the current level - // within the given interval. - let cache = if (head_index + right_index) % 2 == 0 { - hashes[right_index] - } else if right_index == 0 { - self.left_cache[level] - } else { - hashes[right_index - 1] - }; - new_cache.push(cache); + if let Some(right_path) = right_path.as_deref_mut() { + right_path.push(sibling_hash(right_index)); } let parity = head_index % 2; @@ -250,12 +237,6 @@ where head_index /= 2; } - if let Some(new_cache) = new_cache { - // It is important to cache the root as well, in case - // we just cached all elements and will grow on the next push. - new_cache.push(hashes[0]); - } - hashes[0] } } From eaa0f8caf75adcbc69465a5341102ae4f9d03328 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 29 May 2024 12:05:34 +0300 Subject: [PATCH 11/27] Simplify further --- core/lib/mini_merkle_tree/src/lib.rs | 61 ++++++++++------------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 85fb6c73bc03..089174ff5b0c 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -163,7 +163,7 @@ where assert!(self.hashes.len() >= count, "not enough leaves to cache"); let mut new_cache = vec![]; let root = self.compute_merkle_root_and_path(count, None, Some(&mut new_cache)); - self.hashes.drain(0..count); + self.hashes.drain(..count); self.head_index += count; new_cache.push(root); self.left_cache = new_cache; @@ -175,8 +175,6 @@ where mut left_path: Option<&mut Vec>, mut right_path: Option<&mut Vec>, ) -> H256 { - // assert!(right_index < self.hashes.len(), "invalid tree leaf index"); - let depth = tree_depth_by_size(self.binary_tree_size); if let Some(left_path) = left_path.as_deref_mut() { left_path.reserve(depth); @@ -186,54 +184,37 @@ where } let mut hashes = self.hashes.clone(); - let mut level_len = hashes.len(); let mut head_index = self.head_index; for level in 0..depth { let empty_hash_at_level = self.hasher.empty_subtree_hash(level); - let sibling_hash = |index: usize| { - if index == 0 && head_index % 2 == 1 { - self.left_cache[level] - } else if index == level_len - 1 && (head_index + index) % 2 == 0 { - empty_hash_at_level - } else { - // `index` is relative to `head_index` - let sibling = ((head_index + index) ^ 1) - head_index; - hashes.get(sibling).copied().unwrap_or_default() - } - }; - - if let Some(left_path) = left_path.as_deref_mut() { - left_path.push(sibling_hash(0)); + if head_index % 2 == 1 { + hashes.push_front(self.left_cache[level]); } - if let Some(right_path) = right_path.as_deref_mut() { - right_path.push(sibling_hash(right_index)); + if hashes.len() % 2 == 1 { + hashes.push_back(empty_hash_at_level); } - let parity = head_index % 2; - // If our queue starts from the right child or ends with the left child, - // we need to round the length up. - let next_level_len = level_len / 2 + (parity | (level_len % 2)); + let push_sibling_hash = |path: Option<&mut Vec>, index: usize| { + // `index` is relative to `head_index` + if let Some(path) = path { + let sibling = ((head_index + index) ^ 1) - head_index + head_index % 2; + let hash = hashes.get(sibling).copied().unwrap_or_default(); + path.push(hash); + } + }; + + push_sibling_hash(left_path.as_deref_mut(), 0); + push_sibling_hash(right_path.as_deref_mut(), right_index); - for i in 0..next_level_len { - let lhs = if i == 0 && parity == 1 { - // If the leftmost element is a right child, we need to use cache. - self.left_cache[level] - } else { - hashes[2 * i - parity] - }; - let rhs = if i == next_level_len - 1 && (level_len - parity) % 2 == 1 { - // If the rightmost element is a left child, we need to use the empty hash. - empty_hash_at_level - } else { - hashes[2 * i + 1 - parity] - }; - hashes[i] = self.hasher.compress(&lhs, &rhs); + let level_len = hashes.len() / 2; + for i in 0..level_len { + hashes[i] = self.hasher.compress(&hashes[2 * i], &hashes[2 * i + 1]); } - right_index = (right_index + parity) / 2; - level_len = next_level_len; + hashes.drain(level_len..); + right_index = (right_index + head_index % 2) / 2; head_index /= 2; } From c968afff551a59151707d7ca0c1ae2250675f220 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 29 May 2024 12:21:39 +0300 Subject: [PATCH 12/27] Renaming stuff --- core/lib/mini_merkle_tree/src/lib.rs | 41 ++++++++++++-------------- core/lib/mini_merkle_tree/src/tests.rs | 18 +++++------ 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 089174ff5b0c..6f39ad626f89 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -37,7 +37,7 @@ pub struct MiniMerkleTree { hasher: H, hashes: VecDeque, binary_tree_size: usize, - head_index: usize, + start_index: usize, left_cache: Vec, } @@ -97,21 +97,21 @@ where hasher, hashes, binary_tree_size, - head_index: 0, + start_index: 0, left_cache: vec![], } } /// Returns `true` if the tree is empty. pub fn is_empty(&self) -> bool { - self.head_index == 0 && self.hashes.is_empty() + self.start_index == 0 && self.hashes.is_empty() } /// Returns the root hash of this tree. pub fn merkle_root(&self) -> H256 { if self.hashes.is_empty() { let depth = tree_depth_by_size(self.binary_tree_size); - if self.head_index == 0 { + if self.start_index == 0 { self.hasher.empty_subtree_hash(depth) } else { self.left_cache[depth] @@ -124,17 +124,14 @@ where /// Returns the root hash and the Merkle proof for a leaf with the specified 0-based `index`. /// `index` is relative to the leftmost uncached leaf. pub fn merkle_root_and_path(&self, index: usize) -> (H256, Vec) { - let mut right_path = vec![]; - let root_hash = self.compute_merkle_root_and_path(index, None, Some(&mut right_path)); - (root_hash, right_path) + let mut end_path = vec![]; + let root_hash = self.compute_merkle_root_and_path(index, None, Some(&mut end_path)); + (root_hash, end_path) } /// Returns the root hash and the Merkle proofs for an interval of leafs. /// The interval is [0, `length`), where `0` is the leftmost uncached leaf. - pub fn merkle_root_and_paths_for_interval( - &self, - length: usize, - ) -> (H256, Vec, Vec) { + pub fn merkle_root_and_paths_for_range(&self, length: usize) -> (H256, Vec, Vec) { let (mut left_path, mut right_path) = (vec![], vec![]); let root_hash = self.compute_merkle_root_and_path( length - 1, @@ -150,7 +147,7 @@ where pub fn push(&mut self, leaf: [u8; LEAF_SIZE]) { let leaf_hash = self.hasher.hash_bytes(&leaf); self.hashes.push_back(leaf_hash); - if self.head_index + self.hashes.len() > self.binary_tree_size { + if self.start_index + self.hashes.len() > self.binary_tree_size { self.binary_tree_size *= 2; } } @@ -164,27 +161,27 @@ where let mut new_cache = vec![]; let root = self.compute_merkle_root_and_path(count, None, Some(&mut new_cache)); self.hashes.drain(..count); - self.head_index += count; + self.start_index += count; new_cache.push(root); self.left_cache = new_cache; } fn compute_merkle_root_and_path( &self, - mut right_index: usize, - mut left_path: Option<&mut Vec>, - mut right_path: Option<&mut Vec>, + mut end_index: usize, + mut start_path: Option<&mut Vec>, + mut end_path: Option<&mut Vec>, ) -> H256 { let depth = tree_depth_by_size(self.binary_tree_size); - if let Some(left_path) = left_path.as_deref_mut() { + if let Some(left_path) = start_path.as_deref_mut() { left_path.reserve(depth); } - if let Some(right_path) = right_path.as_deref_mut() { + if let Some(right_path) = end_path.as_deref_mut() { right_path.reserve(depth); } let mut hashes = self.hashes.clone(); - let mut head_index = self.head_index; + let mut head_index = self.start_index; for level in 0..depth { let empty_hash_at_level = self.hasher.empty_subtree_hash(level); @@ -205,8 +202,8 @@ where } }; - push_sibling_hash(left_path.as_deref_mut(), 0); - push_sibling_hash(right_path.as_deref_mut(), right_index); + push_sibling_hash(start_path.as_deref_mut(), 0); + push_sibling_hash(end_path.as_deref_mut(), end_index); let level_len = hashes.len() / 2; for i in 0..level_len { @@ -214,7 +211,7 @@ where } hashes.drain(level_len..); - right_index = (right_index + head_index % 2) / 2; + end_index = (end_index + head_index % 2) / 2; head_index /= 2; } diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 576020e2e02b..bebc60c38309 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -156,20 +156,20 @@ fn verify_merkle_proof( assert_eq!(hash, merkle_root); } -fn verify_interval_merkle_proof( +fn verify_range_merkle_proof( items: &[[u8; 88]], mut head_index: usize, - left_path: &[H256], - right_path: &[H256], + start_path: &[H256], + end_path: &[H256], merkle_root: H256, ) { - assert_eq!(left_path.len(), right_path.len()); + assert_eq!(start_path.len(), end_path.len()); let hasher = KeccakHasher; let mut hashes: Vec<_> = items.iter().map(|item| hasher.hash_bytes(item)).collect(); let mut level_len = hashes.len(); - for (left_item, right_item) in left_path.iter().zip(right_path.iter()) { + for (left_item, right_item) in start_path.iter().zip(end_path.iter()) { let parity = head_index % 2; let next_level_len = level_len / 2 + (parity | (level_len % 2)); @@ -211,16 +211,16 @@ fn merkle_proofs_are_valid_for_intervals() { let mut tree = MiniMerkleTree::new(leaves.clone().into_iter(), None); for i in 1..=50 { - let (merkle_root, left_path, right_path) = tree.merkle_root_and_paths_for_interval(i); - verify_interval_merkle_proof(&leaves[..i], 0, &left_path, &right_path, merkle_root); + let (merkle_root, start_path, end_path) = tree.merkle_root_and_paths_for_range(i); + verify_range_merkle_proof(&leaves[..i], 0, &start_path, &end_path, merkle_root); } tree.cache(25); leaves.drain(..25); for i in 1..=25 { - let (merkle_root, left_path, right_path) = tree.merkle_root_and_paths_for_interval(i); - verify_interval_merkle_proof(&leaves[..i], 25, &left_path, &right_path, merkle_root); + let (merkle_root, start_path, end_path) = tree.merkle_root_and_paths_for_range(i); + verify_range_merkle_proof(&leaves[..i], 25, &start_path, &end_path, merkle_root); } } From 07bb18b94636ee49b68b323d6b02f84dbbf703da Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 29 May 2024 12:29:01 +0300 Subject: [PATCH 13/27] Clean up tests --- core/lib/mini_merkle_tree/src/tests.rs | 33 +++++++++++--------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index bebc60c38309..3f5d38339859 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -1,6 +1,7 @@ //! Tests for `MiniMerkleTree`. use super::*; +use std::collections::VecDeque; #[test] fn tree_depth_is_computed_correctly() { @@ -158,7 +159,7 @@ fn verify_merkle_proof( fn verify_range_merkle_proof( items: &[[u8; 88]], - mut head_index: usize, + mut start_index: usize, start_path: &[H256], end_path: &[H256], merkle_root: H256, @@ -166,29 +167,23 @@ fn verify_range_merkle_proof( assert_eq!(start_path.len(), end_path.len()); let hasher = KeccakHasher; - let mut hashes: Vec<_> = items.iter().map(|item| hasher.hash_bytes(item)).collect(); - let mut level_len = hashes.len(); + let mut hashes: VecDeque<_> = items.iter().map(|item| hasher.hash_bytes(item)).collect(); - for (left_item, right_item) in start_path.iter().zip(end_path.iter()) { - let parity = head_index % 2; - let next_level_len = level_len / 2 + (parity | (level_len % 2)); + for (start_item, end_item) in start_path.iter().zip(end_path.iter()) { + if start_index % 2 == 1 { + hashes.push_front(*start_item); + } + if hashes.len() % 2 == 1 { + hashes.push_back(*end_item); + } + let next_level_len = hashes.len() / 2; for i in 0..next_level_len { - let lhs = if i == 0 && parity == 1 { - left_item - } else { - &hashes[2 * i - parity] - }; - let rhs = if i == next_level_len - 1 && (level_len - parity) % 2 == 1 { - right_item - } else { - &hashes[2 * i + 1 - parity] - }; - hashes[i] = hasher.compress(lhs, rhs); + hashes[i] = hasher.compress(&hashes[2 * i], &hashes[2 * i + 1]); } - level_len = next_level_len; - head_index /= 2; + hashes.drain(next_level_len..); + start_index /= 2; } assert_eq!(hashes[0], merkle_root); From a73b2f218335e614fd8627247dd330ea9f470686 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 29 May 2024 12:30:34 +0300 Subject: [PATCH 14/27] More renaming --- core/lib/mini_merkle_tree/src/lib.rs | 2 +- core/lib/mini_merkle_tree/src/tests.rs | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 6f39ad626f89..e458ad70c741 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -156,7 +156,7 @@ where /// Does not affect the root hash, but makes it impossible to get the paths to the cached leaves. /// # Panics /// Panics if `count` is greater than the number of non-cached leaves in the tree. - pub fn cache(&mut self, count: usize) { + pub fn trim_start(&mut self, count: usize) { assert!(self.hashes.len() >= count, "not enough leaves to cache"); let mut new_cache = vec![]; let root = self.compute_merkle_root_and_path(count, None, Some(&mut new_cache)); diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 3f5d38339859..62d2c8c5ec28 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -1,8 +1,9 @@ //! Tests for `MiniMerkleTree`. -use super::*; use std::collections::VecDeque; +use super::*; + #[test] fn tree_depth_is_computed_correctly() { const TREE_SIZES_AND_DEPTHS: &[(usize, usize)] = &[ @@ -210,7 +211,7 @@ fn merkle_proofs_are_valid_for_intervals() { verify_range_merkle_proof(&leaves[..i], 0, &start_path, &end_path, merkle_root); } - tree.cache(25); + tree.trim_start(25); leaves.drain(..25); for i in 1..=25 { @@ -285,7 +286,7 @@ fn dynamic_merkle_tree_growth() { } // Shouldn't shrink after caching - tree.cache(6); + tree.trim_start(6); assert_eq!(tree.binary_tree_size, 8); assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(3)); } @@ -314,7 +315,7 @@ fn caching_leaves() { let (root_hash, path) = tree.merkle_root_and_path(49 - i); assert_eq!(root_hash, expected_root_hash); assert_eq!(path, expected_path); - tree.cache(1); + tree.trim_start(1); } let mut tree = MiniMerkleTree::new(leaves, None); @@ -322,7 +323,7 @@ fn caching_leaves() { let (root_hash, path) = tree.merkle_root_and_path(49 - i * 5); assert_eq!(root_hash, expected_root_hash); assert_eq!(path, expected_path); - tree.cache(5); + tree.trim_start(5); } } @@ -347,15 +348,15 @@ fn pushing_new_leaves() { tree.push([number; 88]); assert_eq!(tree.merkle_root(), *expected_root); - tree.cache(2); + tree.trim_start(2); assert_eq!(tree.merkle_root(), *expected_root); } } #[test] -fn cache_all_and_grow() { +fn trim_all_and_grow() { let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), None); - tree.cache(4); + tree.trim_start(4); tree.push([1; 88]); let expected_root = "0xfa4c924185122254742622b10b68df8de89d33f685ee579f37a50c552b0d245d" .parse() @@ -364,14 +365,14 @@ fn cache_all_and_grow() { } #[test] -fn cache_all_and_check_root() { +fn trim_all_and_check_root() { let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), None); let root = tree.merkle_root(); - tree.cache(4); + tree.trim_start(4); assert_eq!(tree.merkle_root(), root); let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), Some(8)); let root = tree.merkle_root(); - tree.cache(4); + tree.trim_start(4); assert_eq!(tree.merkle_root(), root); } From 51bdbc8f92cde62b5bb88fdb85462aac815b4bd3 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 29 May 2024 12:46:05 +0300 Subject: [PATCH 15/27] Comments --- core/lib/mini_merkle_tree/src/lib.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index e458ad70c741..8b8d508bbbc1 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -29,16 +29,17 @@ const MAX_TREE_DEPTH: usize = 32; /// exceeds the current tree size. It does not shrink. /// /// The tree is optimized for the case when the queries are performed on the rightmost leaves -/// and the leftmost leaves are cached. Caching enables the merkle roots and paths to be computed -/// in `O(n)` time, where `n` is the number of uncached leaves (in contrast to the total number of -/// leaves). Cache itself only takes up `O(depth)` space. +/// and the leftmost leaves are cached (trimmed). Caching enables the merkle roots and paths to be computed +/// in `O(max(n, depth))` time, where `n` is the number of uncached leaves (in contrast to the total number of +/// leaves). Cache itself only takes up `O(depth)` space. However, caching prevents the retrieval of paths to the +/// cached leaves. #[derive(Debug, Clone)] pub struct MiniMerkleTree { hasher: H, hashes: VecDeque, binary_tree_size: usize, start_index: usize, - left_cache: Vec, + cache: Vec, } impl MiniMerkleTree @@ -98,7 +99,7 @@ where hashes, binary_tree_size, start_index: 0, - left_cache: vec![], + cache: vec![], } } @@ -114,7 +115,7 @@ where if self.start_index == 0 { self.hasher.empty_subtree_hash(depth) } else { - self.left_cache[depth] + self.cache[depth] } } else { self.compute_merkle_root_and_path(0, None, None) @@ -159,11 +160,14 @@ where pub fn trim_start(&mut self, count: usize) { assert!(self.hashes.len() >= count, "not enough leaves to cache"); let mut new_cache = vec![]; + // Cache is a subset of the path to the first untrimmed leaf. let root = self.compute_merkle_root_and_path(count, None, Some(&mut new_cache)); self.hashes.drain(..count); self.start_index += count; + // It is important to add the root in case we just trimmed all leaves *and* + // the tree will grow on the next push. new_cache.push(root); - self.left_cache = new_cache; + self.cache = new_cache; } fn compute_merkle_root_and_path( @@ -187,7 +191,7 @@ where let empty_hash_at_level = self.hasher.empty_subtree_hash(level); if head_index % 2 == 1 { - hashes.push_front(self.left_cache[level]); + hashes.push_front(self.cache[level]); } if hashes.len() % 2 == 1 { hashes.push_back(empty_hash_at_level); From 2b269cbd7fdbf1d9a4cc5876c3b7d8e6fdca2990 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 29 May 2024 12:58:07 +0300 Subject: [PATCH 16/27] More renaming --- checks-config/era.dic | 1 + core/lib/mini_merkle_tree/src/lib.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/checks-config/era.dic b/checks-config/era.dic index 9bc961c62b27..1da348514bb4 100644 --- a/checks-config/era.dic +++ b/checks-config/era.dic @@ -970,3 +970,4 @@ e2e upcasting foundryup uncached +untrimmed diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 8b8d508bbbc1..2194d68754c7 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -130,8 +130,8 @@ where (root_hash, end_path) } - /// Returns the root hash and the Merkle proofs for an interval of leafs. - /// The interval is [0, `length`), where `0` is the leftmost uncached leaf. + /// Returns the root hash and the Merkle proofs for a range of leafs. + /// The range is 0..length, where `0` is the leftmost untrimmed leaf. pub fn merkle_root_and_paths_for_range(&self, length: usize) -> (H256, Vec, Vec) { let (mut left_path, mut right_path) = (vec![], vec![]); let root_hash = self.compute_merkle_root_and_path( @@ -185,12 +185,12 @@ where } let mut hashes = self.hashes.clone(); - let mut head_index = self.start_index; + let mut start_index = self.start_index; for level in 0..depth { let empty_hash_at_level = self.hasher.empty_subtree_hash(level); - if head_index % 2 == 1 { + if start_index % 2 == 1 { hashes.push_front(self.cache[level]); } if hashes.len() % 2 == 1 { @@ -200,7 +200,7 @@ where let push_sibling_hash = |path: Option<&mut Vec>, index: usize| { // `index` is relative to `head_index` if let Some(path) = path { - let sibling = ((head_index + index) ^ 1) - head_index + head_index % 2; + let sibling = ((start_index + index) ^ 1) - start_index + start_index % 2; let hash = hashes.get(sibling).copied().unwrap_or_default(); path.push(hash); } @@ -215,8 +215,8 @@ where } hashes.drain(level_len..); - end_index = (end_index + head_index % 2) / 2; - head_index /= 2; + end_index = (end_index + start_index % 2) / 2; + start_index /= 2; } hashes[0] From bc2a3551de701bf67ac7a8822257c880a3287ea0 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 29 May 2024 13:05:10 +0300 Subject: [PATCH 17/27] Add option to add raw hashes --- core/lib/mini_merkle_tree/src/lib.rs | 40 +++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 2194d68754c7..ed603dbba512 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -66,7 +66,8 @@ where H: HashEmptySubtree, { /// Creates a new Merkle tree from the supplied leaves. If `min_tree_size` is supplied and is larger than the - /// number of the supplied leaves, the leaves are padded to `min_tree_size` with `[0_u8; LEAF_SIZE]` entries. + /// number of the supplied leaves, the leaves are padded to `min_tree_size` with `[0_u8; LEAF_SIZE]` entries, + /// but are deemed empty. /// /// # Panics /// @@ -79,7 +80,26 @@ where leaves: impl Iterator, min_tree_size: Option, ) -> Self { - let hashes: VecDeque<_> = leaves.map(|bytes| hasher.hash_bytes(&bytes)).collect(); + let hashes: Vec<_> = leaves.map(|bytes| hasher.hash_bytes(&bytes)).collect(); + Self::from_hashes(hasher, hashes.into_iter(), min_tree_size) + } + + /// Creates a new Merkle tree from the supplied raw hashes. If `min_tree_size` is supplied and is larger than the + /// number of the supplied leaves, the leaves are padded to `min_tree_size` with zero-hash entries, + /// but are deemed empty. + /// + /// # Panics + /// + /// Panics if any of the following conditions applies: + /// + /// - `min_tree_size` (if supplied) is not a power of 2. + /// - The number of leaves is greater than `2^32`. + pub fn from_hashes( + hasher: H, + hashes: impl Iterator, + min_tree_size: Option, + ) -> Self { + let hashes: VecDeque<_> = hashes.collect(); let mut binary_tree_size = hashes.len().next_power_of_two(); if let Some(min_tree_size) = min_tree_size { assert!( @@ -142,17 +162,24 @@ where (root_hash, left_path, right_path) } - /// Adds a new leaf to the tree (replaces leftmost empty leaf). + /// Adds a raw hash to the tree (replaces leftmost empty leaf). /// If the tree is full, its size is doubled. /// Note: empty leaves != zero leaves. - pub fn push(&mut self, leaf: [u8; LEAF_SIZE]) { - let leaf_hash = self.hasher.hash_bytes(&leaf); + pub fn push_hash(&mut self, leaf_hash: H256) { self.hashes.push_back(leaf_hash); if self.start_index + self.hashes.len() > self.binary_tree_size { self.binary_tree_size *= 2; } } + /// Adds a new leaf to the tree (replaces leftmost empty leaf). + /// If the tree is full, its size is doubled. + /// Note: empty leaves != zero leaves. + pub fn push(&mut self, leaf: [u8; LEAF_SIZE]) { + let leaf_hash = self.hasher.hash_bytes(&leaf); + self.push_hash(leaf_hash); + } + /// Caches the rightmost `count` leaves. /// Does not affect the root hash, but makes it impossible to get the paths to the cached leaves. /// # Panics @@ -189,7 +216,6 @@ where for level in 0..depth { let empty_hash_at_level = self.hasher.empty_subtree_hash(level); - if start_index % 2 == 1 { hashes.push_front(self.cache[level]); } @@ -201,6 +227,8 @@ where // `index` is relative to `head_index` if let Some(path) = path { let sibling = ((start_index + index) ^ 1) - start_index + start_index % 2; + // When trimming all existing leaves, + // the first untrimmed leaf and its sibling might not exist. let hash = hashes.get(sibling).copied().unwrap_or_default(); path.push(hash); } From 53ff585dfcf18cffff32996f0f9f2d184ddf52dc Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Thu, 30 May 2024 20:30:27 +0300 Subject: [PATCH 18/27] Simplify further --- core/lib/mini_merkle_tree/src/lib.rs | 58 ++++++++++---------------- core/lib/mini_merkle_tree/src/tests.rs | 2 +- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index ed603dbba512..51b8dae38eac 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -108,8 +108,10 @@ where ); binary_tree_size = min_tree_size.max(binary_tree_size); } + + let depth = tree_depth_by_size(binary_tree_size); assert!( - tree_depth_by_size(binary_tree_size) <= MAX_TREE_DEPTH, + depth <= MAX_TREE_DEPTH, "Tree contains more than {} items; this is not supported", 1 << MAX_TREE_DEPTH ); @@ -119,7 +121,7 @@ where hashes, binary_tree_size, start_index: 0, - cache: vec![], + cache: vec![H256::default(); depth + 1], } } @@ -138,7 +140,7 @@ where self.cache[depth] } } else { - self.compute_merkle_root_and_path(0, None, None) + self.compute_merkle_root_and_path(0, None) } } @@ -146,20 +148,17 @@ where /// `index` is relative to the leftmost uncached leaf. pub fn merkle_root_and_path(&self, index: usize) -> (H256, Vec) { let mut end_path = vec![]; - let root_hash = self.compute_merkle_root_and_path(index, None, Some(&mut end_path)); + let root_hash = self.compute_merkle_root_and_path(index, Some(&mut end_path)); (root_hash, end_path) } /// Returns the root hash and the Merkle proofs for a range of leafs. /// The range is 0..length, where `0` is the leftmost untrimmed leaf. pub fn merkle_root_and_paths_for_range(&self, length: usize) -> (H256, Vec, Vec) { - let (mut left_path, mut right_path) = (vec![], vec![]); - let root_hash = self.compute_merkle_root_and_path( - length - 1, - Some(&mut left_path), - Some(&mut right_path), - ); - (root_hash, left_path, right_path) + let mut right_path = vec![]; + let root_hash = self.compute_merkle_root_and_path(length - 1, Some(&mut right_path)); + let depth = tree_depth_by_size(self.binary_tree_size); + (root_hash, self.cache[..depth].to_vec(), right_path) } /// Adds a raw hash to the tree (replaces leftmost empty leaf). @@ -188,7 +187,7 @@ where assert!(self.hashes.len() >= count, "not enough leaves to cache"); let mut new_cache = vec![]; // Cache is a subset of the path to the first untrimmed leaf. - let root = self.compute_merkle_root_and_path(count, None, Some(&mut new_cache)); + let root = self.compute_merkle_root_and_path(count, Some(&mut new_cache)); self.hashes.drain(..count); self.start_index += count; // It is important to add the root in case we just trimmed all leaves *and* @@ -200,51 +199,38 @@ where fn compute_merkle_root_and_path( &self, mut end_index: usize, - mut start_path: Option<&mut Vec>, mut end_path: Option<&mut Vec>, ) -> H256 { let depth = tree_depth_by_size(self.binary_tree_size); - if let Some(left_path) = start_path.as_deref_mut() { - left_path.reserve(depth); - } if let Some(right_path) = end_path.as_deref_mut() { right_path.reserve(depth); } let mut hashes = self.hashes.clone(); - let mut start_index = self.start_index; + let mut absolute_start_index = self.start_index; for level in 0..depth { - let empty_hash_at_level = self.hasher.empty_subtree_hash(level); - if start_index % 2 == 1 { + if absolute_start_index % 2 == 1 { hashes.push_front(self.cache[level]); + end_index += 1; } if hashes.len() % 2 == 1 { + let empty_hash_at_level = self.hasher.empty_subtree_hash(level); hashes.push_back(empty_hash_at_level); } - - let push_sibling_hash = |path: Option<&mut Vec>, index: usize| { - // `index` is relative to `head_index` - if let Some(path) = path { - let sibling = ((start_index + index) ^ 1) - start_index + start_index % 2; - // When trimming all existing leaves, - // the first untrimmed leaf and its sibling might not exist. - let hash = hashes.get(sibling).copied().unwrap_or_default(); - path.push(hash); - } - }; - - push_sibling_hash(start_path.as_deref_mut(), 0); - push_sibling_hash(end_path.as_deref_mut(), end_index); + if let Some(path) = end_path.as_deref_mut() { + let hash = hashes.get(end_index ^ 1).copied().unwrap_or_default(); + path.push(hash); + } let level_len = hashes.len() / 2; for i in 0..level_len { hashes[i] = self.hasher.compress(&hashes[2 * i], &hashes[2 * i + 1]); } - hashes.drain(level_len..); - end_index = (end_index + start_index % 2) / 2; - start_index /= 2; + hashes.truncate(level_len); + end_index /= 2; + absolute_start_index /= 2; } hashes[0] diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 62d2c8c5ec28..def0e7e6d277 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -183,7 +183,7 @@ fn verify_range_merkle_proof( hashes[i] = hasher.compress(&hashes[2 * i], &hashes[2 * i + 1]); } - hashes.drain(next_level_len..); + hashes.truncate(next_level_len); start_index /= 2; } From 474cd070786d1d513b9c9bd3eb364f2f8fd8e8fd Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Fri, 31 May 2024 14:24:21 +0300 Subject: [PATCH 19/27] Only add root hash to cache when necessary --- core/lib/mini_merkle_tree/src/lib.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 51b8dae38eac..f95217607a3b 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -121,7 +121,7 @@ where hashes, binary_tree_size, start_index: 0, - cache: vec![H256::default(); depth + 1], + cache: vec![H256::default(); depth], } } @@ -135,13 +135,12 @@ where if self.hashes.is_empty() { let depth = tree_depth_by_size(self.binary_tree_size); if self.start_index == 0 { - self.hasher.empty_subtree_hash(depth) - } else { - self.cache[depth] + return self.hasher.empty_subtree_hash(depth); + } else if self.start_index == self.binary_tree_size { + return self.cache[depth]; } - } else { - self.compute_merkle_root_and_path(0, None) } + self.compute_merkle_root_and_path(0, None) } /// Returns the root hash and the Merkle proof for a leaf with the specified 0-based `index`. @@ -190,9 +189,9 @@ where let root = self.compute_merkle_root_and_path(count, Some(&mut new_cache)); self.hashes.drain(..count); self.start_index += count; - // It is important to add the root in case we just trimmed all leaves *and* - // the tree will grow on the next push. - new_cache.push(root); + if self.start_index == self.binary_tree_size { + new_cache.push(root); + } self.cache = new_cache; } @@ -209,14 +208,17 @@ where let mut hashes = self.hashes.clone(); let mut absolute_start_index = self.start_index; + if hashes.is_empty() { + hashes.push_back(self.hasher.empty_subtree_hash(0)); + } + for level in 0..depth { if absolute_start_index % 2 == 1 { hashes.push_front(self.cache[level]); end_index += 1; } if hashes.len() % 2 == 1 { - let empty_hash_at_level = self.hasher.empty_subtree_hash(level); - hashes.push_back(empty_hash_at_level); + hashes.push_back(self.hasher.empty_subtree_hash(level)); } if let Some(path) = end_path.as_deref_mut() { let hash = hashes.get(end_index ^ 1).copied().unwrap_or_default(); From 54441955ccd3eea7d0c7219c6f911e0b2c8ed492 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Fri, 31 May 2024 16:05:33 +0300 Subject: [PATCH 20/27] Make cache and paths partial --- core/lib/mini_merkle_tree/src/lib.rs | 49 ++++++++++++++++++-------- core/lib/mini_merkle_tree/src/tests.rs | 10 +++--- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index f95217607a3b..d416ffb2839c 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -39,7 +39,7 @@ pub struct MiniMerkleTree { hashes: VecDeque, binary_tree_size: usize, start_index: usize, - cache: Vec, + cache: Vec>, } impl MiniMerkleTree @@ -121,7 +121,7 @@ where hashes, binary_tree_size, start_index: 0, - cache: vec![H256::default(); depth], + cache: vec![None; depth], } } @@ -137,27 +137,35 @@ where if self.start_index == 0 { return self.hasher.empty_subtree_hash(depth); } else if self.start_index == self.binary_tree_size { - return self.cache[depth]; + return self.cache[depth].unwrap(); } } - self.compute_merkle_root_and_path(0, None) + self.compute_merkle_root_and_path(0, None, None) } /// Returns the root hash and the Merkle proof for a leaf with the specified 0-based `index`. /// `index` is relative to the leftmost uncached leaf. pub fn merkle_root_and_path(&self, index: usize) -> (H256, Vec) { + assert!(index < self.hashes.len(), "leaf index out of bounds"); let mut end_path = vec![]; - let root_hash = self.compute_merkle_root_and_path(index, Some(&mut end_path)); - (root_hash, end_path) + let root_hash = self.compute_merkle_root_and_path(index, Some(&mut end_path), None); + ( + root_hash, + end_path.into_iter().map(Option::unwrap).collect(), + ) } /// Returns the root hash and the Merkle proofs for a range of leafs. /// The range is 0..length, where `0` is the leftmost untrimmed leaf. - pub fn merkle_root_and_paths_for_range(&self, length: usize) -> (H256, Vec, Vec) { + pub fn merkle_root_and_paths_for_range( + &self, + length: usize, + ) -> (H256, Vec>, Vec>) { + assert!(length <= self.hashes.len(), "not enough leaves in the tree"); let mut right_path = vec![]; - let root_hash = self.compute_merkle_root_and_path(length - 1, Some(&mut right_path)); - let depth = tree_depth_by_size(self.binary_tree_size); - (root_hash, self.cache[..depth].to_vec(), right_path) + let root_hash = + self.compute_merkle_root_and_path(length - 1, Some(&mut right_path), Some(Side::Right)); + (root_hash, self.cache.clone(), right_path) } /// Adds a raw hash to the tree (replaces leftmost empty leaf). @@ -186,11 +194,11 @@ where assert!(self.hashes.len() >= count, "not enough leaves to cache"); let mut new_cache = vec![]; // Cache is a subset of the path to the first untrimmed leaf. - let root = self.compute_merkle_root_and_path(count, Some(&mut new_cache)); + let root = self.compute_merkle_root_and_path(count, Some(&mut new_cache), Some(Side::Left)); self.hashes.drain(..count); self.start_index += count; if self.start_index == self.binary_tree_size { - new_cache.push(root); + new_cache.push(Some(root)); } self.cache = new_cache; } @@ -198,7 +206,8 @@ where fn compute_merkle_root_and_path( &self, mut end_index: usize, - mut end_path: Option<&mut Vec>, + mut end_path: Option<&mut Vec>>, + side: Option, ) -> H256 { let depth = tree_depth_by_size(self.binary_tree_size); if let Some(right_path) = end_path.as_deref_mut() { @@ -214,14 +223,18 @@ where for level in 0..depth { if absolute_start_index % 2 == 1 { - hashes.push_front(self.cache[level]); + hashes.push_front(self.cache[level].unwrap()); end_index += 1; } if hashes.len() % 2 == 1 { hashes.push_back(self.hasher.empty_subtree_hash(level)); } if let Some(path) = end_path.as_deref_mut() { - let hash = hashes.get(end_index ^ 1).copied().unwrap_or_default(); + let hash = match side { + Some(Side::Left) if end_index % 2 == 0 => None, + Some(Side::Right) if end_index % 2 == 1 => None, + _ => hashes.get(end_index ^ 1).copied(), + }; path.push(hash); } @@ -244,6 +257,12 @@ fn tree_depth_by_size(tree_size: usize) -> usize { tree_size.trailing_zeros() as usize } +#[derive(Debug, Clone, Copy)] +enum Side { + Left, + Right, +} + /// Hashing of empty binary Merkle trees. pub trait HashEmptySubtree: 'static + Send + Sync + Hasher diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index def0e7e6d277..5ff2d43ac616 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -161,8 +161,8 @@ fn verify_merkle_proof( fn verify_range_merkle_proof( items: &[[u8; 88]], mut start_index: usize, - start_path: &[H256], - end_path: &[H256], + start_path: &[Option], + end_path: &[Option], merkle_root: H256, ) { assert_eq!(start_path.len(), end_path.len()); @@ -172,10 +172,10 @@ fn verify_range_merkle_proof( for (start_item, end_item) in start_path.iter().zip(end_path.iter()) { if start_index % 2 == 1 { - hashes.push_front(*start_item); + hashes.push_front(start_item.unwrap()); } if hashes.len() % 2 == 1 { - hashes.push_back(*end_item); + hashes.push_back(end_item.unwrap()); } let next_level_len = hashes.len() / 2; @@ -202,7 +202,7 @@ fn merkle_proofs_are_valid_in_small_tree() { } #[test] -fn merkle_proofs_are_valid_for_intervals() { +fn merkle_proofs_are_valid_for_ranges() { let mut leaves: Vec<_> = (1_u8..=50).map(|byte| [byte; 88]).collect(); let mut tree = MiniMerkleTree::new(leaves.clone().into_iter(), None); From 6ff63dc2af79d10c922d3a9753cb6d0e04f6b90d Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Fri, 31 May 2024 17:00:15 +0300 Subject: [PATCH 21/27] Fix edgecase & add comments --- core/lib/mini_merkle_tree/src/lib.rs | 18 +++++++++++++++--- core/lib/mini_merkle_tree/src/tests.rs | 13 +++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index d416ffb2839c..b831e42059a6 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -131,13 +131,15 @@ where } /// Returns the root hash of this tree. + /// # Panics + /// Should never panic, unless there is a bug. pub fn merkle_root(&self) -> H256 { if self.hashes.is_empty() { let depth = tree_depth_by_size(self.binary_tree_size); if self.start_index == 0 { return self.hasher.empty_subtree_hash(depth); } else if self.start_index == self.binary_tree_size { - return self.cache[depth].unwrap(); + return self.cache[depth].expect("cache is invalid"); } } self.compute_merkle_root_and_path(0, None, None) @@ -145,6 +147,8 @@ where /// Returns the root hash and the Merkle proof for a leaf with the specified 0-based `index`. /// `index` is relative to the leftmost uncached leaf. + /// # Panics + /// Panics if `index` is >= than the number of leaves in the tree. pub fn merkle_root_and_path(&self, index: usize) -> (H256, Vec) { assert!(index < self.hashes.len(), "leaf index out of bounds"); let mut end_path = vec![]; @@ -157,10 +161,13 @@ where /// Returns the root hash and the Merkle proofs for a range of leafs. /// The range is 0..length, where `0` is the leftmost untrimmed leaf. + /// # Panics + /// Panics if `length` is 0 or greater than the number of leaves in the tree. pub fn merkle_root_and_paths_for_range( &self, length: usize, ) -> (H256, Vec>, Vec>) { + assert!(length > 0, "range must not be empty"); assert!(length <= self.hashes.len(), "not enough leaves in the tree"); let mut right_path = vec![]; let root_hash = @@ -175,6 +182,9 @@ where self.hashes.push_back(leaf_hash); if self.start_index + self.hashes.len() > self.binary_tree_size { self.binary_tree_size *= 2; + if self.cache.len() < tree_depth_by_size(self.binary_tree_size) { + self.cache.push(Some(self.merkle_root())); + } } } @@ -193,11 +203,13 @@ where pub fn trim_start(&mut self, count: usize) { assert!(self.hashes.len() >= count, "not enough leaves to cache"); let mut new_cache = vec![]; - // Cache is a subset of the path to the first untrimmed leaf. + // Cache is a left subset of the path to the first untrimmed leaf. let root = self.compute_merkle_root_and_path(count, Some(&mut new_cache), Some(Side::Left)); self.hashes.drain(..count); self.start_index += count; if self.start_index == self.binary_tree_size { + // In order to be able to get the root of a completely trimmed tree, + // we need to cache it. new_cache.push(Some(root)); } self.cache = new_cache; @@ -223,7 +235,7 @@ where for level in 0..depth { if absolute_start_index % 2 == 1 { - hashes.push_front(self.cache[level].unwrap()); + hashes.push_front(self.cache[level].expect("cache is invalid")); end_index += 1; } if hashes.len() % 2 == 1 { diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 5ff2d43ac616..75604d548de4 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -346,10 +346,19 @@ fn pushing_new_leaves() { tree.push([number; 88]); tree.push([number; 88]); tree.push([number; 88]); - assert_eq!(tree.merkle_root(), *expected_root); + + dbg!(i); + dbg!(&tree); + let (root, start_path, end_path) = tree.merkle_root_and_paths_for_range(1); + assert_eq!(root, *expected_root); + assert_eq!(start_path.len(), end_path.len()); tree.trim_start(2); - assert_eq!(tree.merkle_root(), *expected_root); + + dbg!(i); + let (root, start_path, end_path) = tree.merkle_root_and_paths_for_range(1); + assert_eq!(root, *expected_root); + assert_eq!(start_path.len(), end_path.len()); } } From 3a951852f38d4c0b2f94b25d3ea91697b3ca0b71 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Fri, 31 May 2024 17:32:05 +0300 Subject: [PATCH 22/27] Fix minor error --- core/lib/mini_merkle_tree/src/lib.rs | 2 +- core/lib/mini_merkle_tree/src/tests.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index b831e42059a6..de13575a2ec7 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -183,7 +183,7 @@ where if self.start_index + self.hashes.len() > self.binary_tree_size { self.binary_tree_size *= 2; if self.cache.len() < tree_depth_by_size(self.binary_tree_size) { - self.cache.push(Some(self.merkle_root())); + self.cache.push(None); } } } diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 75604d548de4..4f172fbb6377 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -173,9 +173,13 @@ fn verify_range_merkle_proof( for (start_item, end_item) in start_path.iter().zip(end_path.iter()) { if start_index % 2 == 1 { hashes.push_front(start_item.unwrap()); + } else { + assert_eq!(start_item, &None); } if hashes.len() % 2 == 1 { hashes.push_back(end_item.unwrap()); + } else { + assert_eq!(end_item, &None); } let next_level_len = hashes.len() / 2; From 96cc1122b6eb7a9ff7c7b13075cfa766e966eef9 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Sat, 1 Jun 2024 00:29:46 +0300 Subject: [PATCH 23/27] Remove dbg --- core/lib/mini_merkle_tree/src/tests.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 4f172fbb6377..3fb63afbff60 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -351,15 +351,12 @@ fn pushing_new_leaves() { tree.push([number; 88]); tree.push([number; 88]); - dbg!(i); - dbg!(&tree); let (root, start_path, end_path) = tree.merkle_root_and_paths_for_range(1); assert_eq!(root, *expected_root); assert_eq!(start_path.len(), end_path.len()); tree.trim_start(2); - dbg!(i); let (root, start_path, end_path) = tree.merkle_root_and_paths_for_range(1); assert_eq!(root, *expected_root); assert_eq!(start_path.len(), end_path.len()); From a011c444ff711216fc6161464bd85c64c15bfc25 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Tue, 4 Jun 2024 14:56:02 +0300 Subject: [PATCH 24/27] Fixes and comments --- core/lib/mini_merkle_tree/benches/tree.rs | 12 ++--- core/lib/mini_merkle_tree/src/lib.rs | 55 ++++++++++++++--------- core/lib/mini_merkle_tree/src/tests.rs | 53 +++++++++++++--------- 3 files changed, 70 insertions(+), 50 deletions(-) diff --git a/core/lib/mini_merkle_tree/benches/tree.rs b/core/lib/mini_merkle_tree/benches/tree.rs index c59c0dc28306..78d9f8dcd55f 100644 --- a/core/lib/mini_merkle_tree/benches/tree.rs +++ b/core/lib/mini_merkle_tree/benches/tree.rs @@ -1,8 +1,6 @@ //! Basic benchmarks for `MiniMerkleTree`. -use criterion::{ - criterion_group, criterion_main, BatchSize, Bencher, BenchmarkId, Criterion, Throughput, -}; +use criterion::{criterion_group, criterion_main, Bencher, BenchmarkId, Criterion, Throughput}; use zksync_mini_merkle_tree::MiniMerkleTree; const TREE_SIZES: &[usize] = &[32, 64, 128, 256, 512, 1_024]; @@ -10,17 +8,13 @@ const TREE_SIZES: &[usize] = &[32, 64, 128, 256, 512, 1_024]; fn compute_merkle_root(bencher: &mut Bencher<'_>, tree_size: usize) { let leaves = (0..tree_size).map(|i| [i as u8; 88]); let tree = MiniMerkleTree::new(leaves, None); - bencher.iter_batched(|| &tree, MiniMerkleTree::merkle_root, BatchSize::SmallInput); + bencher.iter(|| tree.merkle_root()); } fn compute_merkle_path(bencher: &mut Bencher<'_>, tree_size: usize) { let leaves = (0..tree_size).map(|i| [i as u8; 88]); let tree = MiniMerkleTree::new(leaves, None); - bencher.iter_batched( - || tree.clone(), - |tree| tree.merkle_root_and_path(tree_size / 3), - BatchSize::SmallInput, - ); + bencher.iter(|| tree.merkle_root_and_path(tree_size / 3)); } fn basic_benches(criterion: &mut Criterion) { diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index de13575a2ec7..7ae4fb79cdb2 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -38,7 +38,14 @@ pub struct MiniMerkleTree { hasher: H, hashes: VecDeque, binary_tree_size: usize, + /// Index of the leftmost untrimmed leaf. start_index: usize, + /// Left subset of the Merkle path to the first untrimmed leaf (i.e., a leaf with index self.start_index). + /// Merkle path starts from the bottom of the tree and goes up. + /// Used to fill in data for trimmed tree leaves when computing Merkle paths and the root hash. + /// Because only the left subset of the path is used, the cache is not invalidated when new leaves are + /// pushed into the tree. If all leaves are trimmed, cache is the left subset of the Merkle path to + /// the next leaf to be inserted, which still has index self.start_index. cache: Vec>, } @@ -131,8 +138,7 @@ where } /// Returns the root hash of this tree. - /// # Panics - /// Should never panic, unless there is a bug. + #[allow(clippy::missing_panics_doc)] // Should never panic, unless there is a bug. pub fn merkle_root(&self) -> H256 { if self.hashes.is_empty() { let depth = tree_depth_by_size(self.binary_tree_size); @@ -196,67 +202,74 @@ where self.push_hash(leaf_hash); } - /// Caches the rightmost `count` leaves. + /// Trims and caches the leftmost `count` leaves. /// Does not affect the root hash, but makes it impossible to get the paths to the cached leaves. /// # Panics - /// Panics if `count` is greater than the number of non-cached leaves in the tree. + /// Panics if `count` is greater than the number of untrimmed leaves in the tree. pub fn trim_start(&mut self, count: usize) { - assert!(self.hashes.len() >= count, "not enough leaves to cache"); + assert!(self.hashes.len() >= count, "not enough leaves to trim"); let mut new_cache = vec![]; // Cache is a left subset of the path to the first untrimmed leaf. let root = self.compute_merkle_root_and_path(count, Some(&mut new_cache), Some(Side::Left)); self.hashes.drain(..count); self.start_index += count; if self.start_index == self.binary_tree_size { - // In order to be able to get the root of a completely trimmed tree, - // we need to cache it. + // If the tree is completely trimmed *and* will grow on the next push, + // we need to cache the root. new_cache.push(Some(root)); } self.cache = new_cache; } + /// Computes the Merkle root hash. + /// If `path` is `Some`, also computes the Merkle path to the leaf with the specified + /// `index` (relative to `self.start_index`). + /// If `side` is `Some`, only the corresponding side subset of the path is computed (`Some` for + /// elemenets in the `side` subset of the path, `None` for the other elements). fn compute_merkle_root_and_path( &self, - mut end_index: usize, - mut end_path: Option<&mut Vec>>, + mut index: usize, + mut path: Option<&mut Vec>>, side: Option, ) -> H256 { let depth = tree_depth_by_size(self.binary_tree_size); - if let Some(right_path) = end_path.as_deref_mut() { + if let Some(right_path) = path.as_deref_mut() { right_path.reserve(depth); } let mut hashes = self.hashes.clone(); let mut absolute_start_index = self.start_index; - if hashes.is_empty() { - hashes.push_back(self.hasher.empty_subtree_hash(0)); - } - for level in 0..depth { + // If the first untrimmed leaf is a right sibling, + // add it's left sibling to `hashes` from cache for convenient iteration later. if absolute_start_index % 2 == 1 { hashes.push_front(self.cache[level].expect("cache is invalid")); - end_index += 1; + index += 1; } + // At this point `hashes` always starts from the left sibling node. + // If it ends on the left sibling node, add the right sibling node to `hashes` + // for convenient iteration later. if hashes.len() % 2 == 1 { hashes.push_back(self.hasher.empty_subtree_hash(level)); } - if let Some(path) = end_path.as_deref_mut() { + if let Some(path) = path.as_deref_mut() { let hash = match side { - Some(Side::Left) if end_index % 2 == 0 => None, - Some(Side::Right) if end_index % 2 == 1 => None, - _ => hashes.get(end_index ^ 1).copied(), + Some(Side::Left) if index % 2 == 0 => None, + Some(Side::Right) if index % 2 == 1 => None, + _ => hashes.get(index ^ 1).copied(), }; path.push(hash); } let level_len = hashes.len() / 2; + // Since `hashes` has an even number of elements, we can simply iterate over the pairs. for i in 0..level_len { hashes[i] = self.hasher.compress(&hashes[2 * i], &hashes[2 * i + 1]); } hashes.truncate(level_len); - end_index /= 2; + index /= 2; absolute_start_index /= 2; } @@ -269,6 +282,8 @@ fn tree_depth_by_size(tree_size: usize) -> usize { tree_size.trailing_zeros() as usize } +/// Used to represent subsets of a Merkle path. +/// `Left` are the left sibling nodes, `Right` are the right sibling nodes. #[derive(Debug, Clone, Copy)] enum Side { Left, diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index 3fb63afbff60..5aadab1d4e6f 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -209,18 +209,24 @@ fn merkle_proofs_are_valid_in_small_tree() { fn merkle_proofs_are_valid_for_ranges() { let mut leaves: Vec<_> = (1_u8..=50).map(|byte| [byte; 88]).collect(); let mut tree = MiniMerkleTree::new(leaves.clone().into_iter(), None); - - for i in 1..=50 { - let (merkle_root, start_path, end_path) = tree.merkle_root_and_paths_for_range(i); - verify_range_merkle_proof(&leaves[..i], 0, &start_path, &end_path, merkle_root); - } - - tree.trim_start(25); - leaves.drain(..25); - - for i in 1..=25 { - let (merkle_root, start_path, end_path) = tree.merkle_root_and_paths_for_range(i); - verify_range_merkle_proof(&leaves[..i], 25, &start_path, &end_path, merkle_root); + let mut start_index = 0; + + for trimmed_count in 1..10 { + tree.trim_start(trimmed_count); + leaves.drain(..trimmed_count); + start_index += trimmed_count; + let tree_len = tree.hashes.len(); + + for i in 1..=tree_len { + let (merkle_root, start_path, end_path) = tree.merkle_root_and_paths_for_range(i); + verify_range_merkle_proof( + &leaves[..i], + start_index, + &start_path, + &end_path, + merkle_root, + ); + } } } @@ -376,13 +382,18 @@ fn trim_all_and_grow() { #[test] fn trim_all_and_check_root() { - let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), None); - let root = tree.merkle_root(); - tree.trim_start(4); - assert_eq!(tree.merkle_root(), root); - - let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), Some(8)); - let root = tree.merkle_root(); - tree.trim_start(4); - assert_eq!(tree.merkle_root(), root); + for len in 1..=50 { + let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(len), None); + let root = tree.merkle_root(); + tree.trim_start(len); + assert_eq!(tree.merkle_root(), root); + + let mut tree = MiniMerkleTree::new( + iter::repeat([1; 88]).take(len), + Some(len.next_power_of_two() * 2), + ); + let root = tree.merkle_root(); + tree.trim_start(len); + assert_eq!(tree.merkle_root(), root); + } } From c683e2de647ace74a8decbbbc107661fbf032c27 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 5 Jun 2024 12:55:20 +0300 Subject: [PATCH 25/27] Fix comments --- core/lib/mini_merkle_tree/src/lib.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 7ae4fb79cdb2..5c9fbac089b7 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -36,16 +36,19 @@ const MAX_TREE_DEPTH: usize = 32; #[derive(Debug, Clone)] pub struct MiniMerkleTree { hasher: H, + /// Stores untrimmed (uncached) leaves of the tree. hashes: VecDeque, + /// Size of the tree. Always a power of 2. + /// If it is greater than `self.start_index + self.hashes.len()`, the remaining leaves are empty. binary_tree_size: usize, /// Index of the leftmost untrimmed leaf. start_index: usize, - /// Left subset of the Merkle path to the first untrimmed leaf (i.e., a leaf with index self.start_index). + /// Left subset of the Merkle path to the first untrimmed leaf (i.e., a leaf with index `self.start_index`). /// Merkle path starts from the bottom of the tree and goes up. /// Used to fill in data for trimmed tree leaves when computing Merkle paths and the root hash. /// Because only the left subset of the path is used, the cache is not invalidated when new leaves are /// pushed into the tree. If all leaves are trimmed, cache is the left subset of the Merkle path to - /// the next leaf to be inserted, which still has index self.start_index. + /// the next leaf to be inserted, which still has index `self.start_index`. cache: Vec>, } @@ -166,7 +169,7 @@ where } /// Returns the root hash and the Merkle proofs for a range of leafs. - /// The range is 0..length, where `0` is the leftmost untrimmed leaf. + /// The range is 0..length, where `0` is the leftmost untrimmed leaf (i.e. leaf under `self.start_index`). /// # Panics /// Panics if `length` is 0 or greater than the number of leaves in the tree. pub fn merkle_root_and_paths_for_range( @@ -224,8 +227,8 @@ where /// Computes the Merkle root hash. /// If `path` is `Some`, also computes the Merkle path to the leaf with the specified /// `index` (relative to `self.start_index`). - /// If `side` is `Some`, only the corresponding side subset of the path is computed (`Some` for - /// elemenets in the `side` subset of the path, `None` for the other elements). + /// If `side` is `Some`, only the corresponding side subset of the path is computed + /// (`Some` for elements in the `side` subset of the path, `None` for the other elements). fn compute_merkle_root_and_path( &self, mut index: usize, @@ -233,8 +236,8 @@ where side: Option, ) -> H256 { let depth = tree_depth_by_size(self.binary_tree_size); - if let Some(right_path) = path.as_deref_mut() { - right_path.reserve(depth); + if let Some(path) = path.as_deref_mut() { + path.reserve(depth); } let mut hashes = self.hashes.clone(); From ade007df776e64c55dcea56fe16e87f2c56ca00f Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Wed, 5 Jun 2024 17:53:39 +0300 Subject: [PATCH 26/27] Make tree generic over leaf type --- core/lib/mini_merkle_tree/src/lib.rs | 58 +++++++++++++++------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index 5c9fbac089b7..d58e170e06d2 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -5,9 +5,9 @@ #![warn(clippy::all, clippy::pedantic)] #![allow(clippy::must_use_candidate, clippy::similar_names)] -use std::{collections::VecDeque, iter}; +use std::{collections::VecDeque, iter, marker::PhantomData}; -use once_cell::sync::Lazy; +use once_cell::sync::OnceCell; #[cfg(test)] mod tests; @@ -34,7 +34,7 @@ const MAX_TREE_DEPTH: usize = 32; /// leaves). Cache itself only takes up `O(depth)` space. However, caching prevents the retrieval of paths to the /// cached leaves. #[derive(Debug, Clone)] -pub struct MiniMerkleTree { +pub struct MiniMerkleTree { hasher: H, /// Stores untrimmed (uncached) leaves of the tree. hashes: VecDeque, @@ -50,11 +50,13 @@ pub struct MiniMerkleTree { /// pushed into the tree. If all leaves are trimmed, cache is the left subset of the Merkle path to /// the next leaf to be inserted, which still has index `self.start_index`. cache: Vec>, + /// Leaf type marker + _leaf: PhantomData, } -impl MiniMerkleTree +impl> MiniMerkleTree where - KeccakHasher: HashEmptySubtree, + KeccakHasher: HashEmptySubtree, { /// Creates a new Merkle tree from the supplied leaves. If `min_tree_size` is supplied and is larger /// than the number of the supplied leaves, the leaves are padded to `min_tree_size` with `[0_u8; LEAF_SIZE]` entries. @@ -63,17 +65,14 @@ where /// # Panics /// /// Panics in the same situations as [`Self::with_hasher()`]. - pub fn new( - leaves: impl Iterator, - min_tree_size: Option, - ) -> Self { + pub fn new(leaves: impl Iterator, min_tree_size: Option) -> Self { Self::with_hasher(KeccakHasher, leaves, min_tree_size) } } -impl MiniMerkleTree +impl, H> MiniMerkleTree where - H: HashEmptySubtree, + H: HashEmptySubtree, { /// Creates a new Merkle tree from the supplied leaves. If `min_tree_size` is supplied and is larger than the /// number of the supplied leaves, the leaves are padded to `min_tree_size` with `[0_u8; LEAF_SIZE]` entries, @@ -87,10 +86,12 @@ where /// - The number of leaves is greater than `2^32`. pub fn with_hasher( hasher: H, - leaves: impl Iterator, + leaves: impl Iterator, min_tree_size: Option, ) -> Self { - let hashes: Vec<_> = leaves.map(|bytes| hasher.hash_bytes(&bytes)).collect(); + let hashes: Vec<_> = leaves + .map(|bytes| hasher.hash_bytes(bytes.as_ref())) + .collect(); Self::from_hashes(hasher, hashes.into_iter(), min_tree_size) } @@ -132,6 +133,7 @@ where binary_tree_size, start_index: 0, cache: vec![None; depth], + _leaf: PhantomData, } } @@ -200,8 +202,8 @@ where /// Adds a new leaf to the tree (replaces leftmost empty leaf). /// If the tree is full, its size is doubled. /// Note: empty leaves != zero leaves. - pub fn push(&mut self, leaf: [u8; LEAF_SIZE]) { - let leaf_hash = self.hasher.hash_bytes(&leaf); + pub fn push(&mut self, leaf: L) { + let leaf_hash = self.hasher.hash_bytes(leaf.as_ref()); self.push_hash(leaf_hash); } @@ -294,23 +296,25 @@ enum Side { } /// Hashing of empty binary Merkle trees. -pub trait HashEmptySubtree: - 'static + Send + Sync + Hasher -{ - /// Returns the hash of an empty subtree with the given depth. Implementations - /// are encouraged to cache the returned values. - fn empty_subtree_hash(&self, depth: usize) -> H256; +pub trait HashEmptySubtree: 'static + Send + Sync + Hasher { + /// Returns the hash of an empty subtree with the given depth. + /// Implementations are encouraged to cache the returned values. + fn empty_subtree_hash(&self, depth: usize) -> H256 { + static EMPTY_TREE_HASHES: OnceCell> = OnceCell::new(); + EMPTY_TREE_HASHES.get_or_init(|| compute_empty_tree_hashes(self.empty_hash()))[depth] + } + + /// Returns an empty hash + fn empty_hash(&self) -> H256; } -impl HashEmptySubtree<88> for KeccakHasher { - fn empty_subtree_hash(&self, depth: usize) -> H256 { - static EMPTY_TREE_HASHES: Lazy> = Lazy::new(compute_empty_tree_hashes::<88>); - EMPTY_TREE_HASHES[depth] +impl HashEmptySubtree<[u8; 88]> for KeccakHasher { + fn empty_hash(&self) -> H256 { + self.hash_bytes(&[0_u8; 88]) } } -fn compute_empty_tree_hashes() -> Vec { - let empty_leaf_hash = KeccakHasher.hash_bytes(&[0_u8; LEAF_SIZE]); +fn compute_empty_tree_hashes(empty_leaf_hash: H256) -> Vec { iter::successors(Some(empty_leaf_hash), |hash| { Some(KeccakHasher.compress(hash, hash)) }) From 7235b8d4737521d26566ec195a82189941ac3cd9 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Thu, 6 Jun 2024 22:39:02 +0300 Subject: [PATCH 27/27] empty_leaf_hash --- core/lib/mini_merkle_tree/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/lib/mini_merkle_tree/src/lib.rs b/core/lib/mini_merkle_tree/src/lib.rs index d58e170e06d2..a3a9266b6f11 100644 --- a/core/lib/mini_merkle_tree/src/lib.rs +++ b/core/lib/mini_merkle_tree/src/lib.rs @@ -301,15 +301,15 @@ pub trait HashEmptySubtree: 'static + Send + Sync + Hasher { /// Implementations are encouraged to cache the returned values. fn empty_subtree_hash(&self, depth: usize) -> H256 { static EMPTY_TREE_HASHES: OnceCell> = OnceCell::new(); - EMPTY_TREE_HASHES.get_or_init(|| compute_empty_tree_hashes(self.empty_hash()))[depth] + EMPTY_TREE_HASHES.get_or_init(|| compute_empty_tree_hashes(self.empty_leaf_hash()))[depth] } /// Returns an empty hash - fn empty_hash(&self) -> H256; + fn empty_leaf_hash(&self) -> H256; } impl HashEmptySubtree<[u8; 88]> for KeccakHasher { - fn empty_hash(&self) -> H256 { + fn empty_leaf_hash(&self) -> H256 { self.hash_bytes(&[0_u8; 88]) } }