diff --git a/checks-config/era.dic b/checks-config/era.dic index a3e91776496..3741e158dfa 100644 --- a/checks-config/era.dic +++ b/checks-config/era.dic @@ -969,5 +969,7 @@ preloaded e2e upcasting foundryup +uncached +untrimmed UNNEST semver diff --git a/core/lib/mini_merkle_tree/benches/tree.rs b/core/lib/mini_merkle_tree/benches/tree.rs index 8ea4128ac34..78d9f8dcd55 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,21 +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.clone(), - 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 deb92951876..3d4ff3cf561 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::iter; +use std::{collections::VecDeque, iter, marker::PhantomData}; -use once_cell::sync::Lazy; +use once_cell::sync::OnceCell; #[cfg(test)] mod tests; @@ -19,21 +19,44 @@ 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 (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 { +pub struct MiniMerkleTree { hasher: H, - hashes: Box<[H256]>, + /// 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`). + /// 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>, + /// 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. @@ -42,32 +65,52 @@ 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. + /// number of the supplied leaves, the leaves are padded to `min_tree_size` with `[0_u8; LEAF_SIZE]` 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 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.as_ref())) + .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: Box<[H256]> = leaves.map(|bytes| hasher.hash_bytes(&bytes)).collect(); + 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!( @@ -76,8 +119,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", 1u64 << MAX_TREE_DEPTH ); @@ -86,67 +131,153 @@ where hasher, hashes, binary_tree_size, + start_index: 0, + cache: vec![None; depth], + _leaf: PhantomData, } } + /// Returns `true` if the tree is empty. + pub fn is_empty(&self) -> bool { + self.start_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 { + #[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); - self.hasher.empty_subtree_hash(depth) - } else { - self.compute_merkle_root_and_path(0, None) + 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].expect("cache is invalid"); + } } + 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 merkle_path = vec![]; - let root_hash = self.compute_merkle_root_and_path(index, Some(&mut merkle_path)); - (root_hash, merkle_path) + /// `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![]; + 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 (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( + &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 = + 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). + /// If the tree is full, its size is doubled. + /// Note: empty leaves != zero leaves. + 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; + if self.cache.len() < tree_depth_by_size(self.binary_tree_size) { + self.cache.push(None); + } + } + } + + /// 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: L) { + let leaf_hash = self.hasher.hash_bytes(leaf.as_ref()); + self.push_hash(leaf_hash); + } + + /// 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 untrimmed leaves in the tree. + pub fn trim_start(&mut self, count: usize) { + 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 { + // 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 elements in the `side` subset of the path, `None` for the other elements). fn compute_merkle_root_and_path( - self, + &self, mut index: usize, - mut merkle_path: Option<&mut Vec>, + mut path: Option<&mut Vec>>, + side: Option, ) -> H256 { - assert!(index < 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(path) = path.as_deref_mut() { + path.reserve(depth); } - let mut hashes = self.hashes; - let mut level_len = hashes.len(); + let mut hashes = self.hashes.clone(); + 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 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 { - empty_hash_at_level + // 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")); + 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) = path.as_deref_mut() { + let hash = match side { + Some(Side::Left) if index % 2 == 0 => None, + Some(Side::Right) if index % 2 == 1 => None, + _ => hashes.get(index ^ 1).copied(), }; - merkle_path.push(adjacent_hash); + path.push(hash); } - for i in 0..(level_len / 2) { + 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]); } - if level_len % 2 == 1 { - hashes[level_len / 2] = self - .hasher - .compress(&hashes[level_len - 1], &empty_hash_at_level); - } + hashes.truncate(level_len); index /= 2; - level_len = level_len / 2 + level_len % 2; + absolute_start_index /= 2; } + hashes[0] } } @@ -156,24 +287,34 @@ fn tree_depth_by_size(tree_size: usize) -> usize { tree_size.trailing_zeros() as usize } -/// 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; +/// 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, + Right, } -impl HashEmptySubtree<88> for KeccakHasher { +/// 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 { - static EMPTY_TREE_HASHES: Lazy> = Lazy::new(compute_empty_tree_hashes::<88>); - EMPTY_TREE_HASHES[depth] + static EMPTY_TREE_HASHES: OnceCell> = OnceCell::new(); + EMPTY_TREE_HASHES.get_or_init(|| compute_empty_tree_hashes(self.empty_leaf_hash()))[depth] + } + + /// Returns an empty hash + fn empty_leaf_hash(&self) -> H256; +} + +impl HashEmptySubtree<[u8; 88]> for KeccakHasher { + fn empty_leaf_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)) }) diff --git a/core/lib/mini_merkle_tree/src/tests.rs b/core/lib/mini_merkle_tree/src/tests.rs index c534c87523c..5aadab1d4e6 100644 --- a/core/lib/mini_merkle_tree/src/tests.rs +++ b/core/lib/mini_merkle_tree/src/tests.rs @@ -1,5 +1,7 @@ //! Tests for `MiniMerkleTree`. +use std::collections::VecDeque; + use super::*; #[test] @@ -156,24 +158,85 @@ fn verify_merkle_proof( assert_eq!(hash, merkle_root); } +fn verify_range_merkle_proof( + items: &[[u8; 88]], + mut start_index: usize, + start_path: &[Option], + end_path: &[Option], + merkle_root: H256, +) { + assert_eq!(start_path.len(), end_path.len()); + + let hasher = KeccakHasher; + let mut hashes: VecDeque<_> = items.iter().map(|item| hasher.hash_bytes(item)).collect(); + + 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; + for i in 0..next_level_len { + hashes[i] = hasher.compress(&hashes[2 * i], &hashes[2 * i + 1]); + } + + hashes.truncate(next_level_len); + start_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_ranges() { + let mut leaves: Vec<_> = (1_u8..=50).map(|byte| [byte; 88]).collect(); + let mut tree = MiniMerkleTree::new(leaves.clone().into_iter(), None); + 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, + ); + } + } +} + #[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 +248,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,15 +268,132 @@ 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); } } } + +#[test] +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(0)); + + 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 shrink after caching + tree.trim_start(6); + assert_eq!(tree.binary_tree_size, 8); + assert_eq!(tree.merkle_root(), KeccakHasher.empty_subtree_hash(3)); +} + +#[test] +fn caching_leaves() { + let leaves = (1..=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.trim_start(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.trim_start(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]); + + 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); + + 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()); + } +} + +#[test] +fn trim_all_and_grow() { + let mut tree = MiniMerkleTree::new(iter::repeat([1; 88]).take(4), None); + tree.trim_start(4); + tree.push([1; 88]); + let expected_root = "0xfa4c924185122254742622b10b68df8de89d33f685ee579f37a50c552b0d245d" + .parse() + .unwrap(); + assert_eq!(tree.merkle_root(), expected_root); +} + +#[test] +fn trim_all_and_check_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); + } +}