diff --git a/crates/committer/src/hash/hash_trait.rs b/crates/committer/src/hash/hash_trait.rs index 0323ba26..ac14ef89 100644 --- a/crates/committer/src/hash/hash_trait.rs +++ b/crates/committer/src/hash/hash_trait.rs @@ -6,5 +6,5 @@ pub struct HashOutput(pub Felt); impl HashOutput { #[allow(dead_code)] pub(crate) const ZERO: HashOutput = HashOutput(Felt::ZERO); - pub(crate) const ROOT_OF_EMPTY_TREE: HashOutput = Self::ZERO; + pub const ROOT_OF_EMPTY_TREE: HashOutput = Self::ZERO; } diff --git a/crates/committer/src/patricia_merkle_tree/filled_tree/tree.rs b/crates/committer/src/patricia_merkle_tree/filled_tree/tree.rs index e0e071b2..6628b449 100644 --- a/crates/committer/src/patricia_merkle_tree/filled_tree/tree.rs +++ b/crates/committer/src/patricia_merkle_tree/filled_tree/tree.rs @@ -185,6 +185,19 @@ impl FilledTreeImpl { } } } + + fn create_unmodified( + updated_skeleton: impl UpdatedSkeletonTree, + ) -> Result> { + let root_node = updated_skeleton.get_node(NodeIndex::ROOT)?; + let UpdatedSkeletonNode::UnmodifiedSubTree(root_hash) = root_node else { + panic!("A root of tree without modifications is expected to be a unmodified subtree.") + }; + Ok(Self { + tree_map: HashMap::new(), + root_hash: *root_hash, + }) + } } impl FilledTree for FilledTreeImpl { @@ -195,6 +208,9 @@ impl FilledTree for FilledTreeImpl { // Compute the filled tree in two steps: // 1. Create a map containing the tree structure without hash values. // 2. Fill in the hash values. + if leaf_modifications.is_empty() { + return Self::create_unmodified(updated_skeleton); + } let filled_tree_map = Arc::new(Self::initialize_with_placeholders(&updated_skeleton)); let root_hash = Self::compute_filled_tree_rec::( Arc::new(updated_skeleton), diff --git a/crates/committer/src/patricia_merkle_tree/original_skeleton_tree/create_tree.rs b/crates/committer/src/patricia_merkle_tree/original_skeleton_tree/create_tree.rs index c07283ca..bbfcccfe 100644 --- a/crates/committer/src/patricia_merkle_tree/original_skeleton_tree/create_tree.rs +++ b/crates/committer/src/patricia_merkle_tree/original_skeleton_tree/create_tree.rs @@ -178,6 +178,14 @@ impl OriginalSkeletonTreeImpl { sorted_leaf_indices: &[NodeIndex], root_hash: HashOutput, ) -> OriginalSkeletonTreeResult { + if sorted_leaf_indices.is_empty() { + return Ok(Self { + nodes: HashMap::from([( + NodeIndex::ROOT, + OriginalSkeletonNode::UnmodifiedSubTree(root_hash), + )]), + }); + } if root_hash == HashOutput::ROOT_OF_EMPTY_TREE { return Ok(Self { nodes: HashMap::new(), diff --git a/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/compute_updated_skeleton_tree.rs b/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/compute_updated_skeleton_tree.rs index b9cf0ebd..024c4c14 100644 --- a/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/compute_updated_skeleton_tree.rs +++ b/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/compute_updated_skeleton_tree.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::patricia_merkle_tree::node_data::inner_node::EdgePathLength; use crate::patricia_merkle_tree::node_data::inner_node::PathToBottom; use crate::patricia_merkle_tree::node_data::leaf::LeafModifications; @@ -7,9 +9,11 @@ use crate::patricia_merkle_tree::original_skeleton_tree::tree::OriginalSkeletonN use crate::patricia_merkle_tree::original_skeleton_tree::tree::OriginalSkeletonTree; use crate::patricia_merkle_tree::original_skeleton_tree::utils::split_leaves; use crate::patricia_merkle_tree::types::NodeIndex; +use crate::patricia_merkle_tree::updated_skeleton_tree::errors::UpdatedSkeletonTreeError; use crate::patricia_merkle_tree::updated_skeleton_tree::node::UpdatedSkeletonNode; use crate::patricia_merkle_tree::updated_skeleton_tree::tree::UpdatedSkeletonNodeMap; use crate::patricia_merkle_tree::updated_skeleton_tree::tree::UpdatedSkeletonTreeImpl; +use crate::patricia_merkle_tree::updated_skeleton_tree::tree::UpdatedSkeletonTreeResult; #[cfg(test)] #[path = "compute_updated_skeleton_tree_test.rs"] @@ -397,4 +401,23 @@ impl UpdatedSkeletonTreeImpl { self.node_from_edge_data(&path_to_new_bottom, &new_bottom_index, &bottom) } + + pub(crate) fn create_unmodified( + original_skeleton: &impl OriginalSkeletonTree, + ) -> UpdatedSkeletonTreeResult { + let original_root_node = original_skeleton + .get_nodes() + .get(&NodeIndex::ROOT) + .ok_or(UpdatedSkeletonTreeError::MissingNode(NodeIndex::ROOT))?; + let OriginalSkeletonNode::UnmodifiedSubTree(root_hash) = original_root_node else { + panic!("A root of tree without modifications is expected to be an unmodified node.") + }; + + Ok(Self { + skeleton_tree: HashMap::from([( + NodeIndex::ROOT, + UpdatedSkeletonNode::UnmodifiedSubTree(*root_hash), + )]), + }) + } } diff --git a/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/compute_updated_skeleton_tree_test.rs b/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/compute_updated_skeleton_tree_test.rs index d7e40db3..7fa91dda 100644 --- a/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/compute_updated_skeleton_tree_test.rs +++ b/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/compute_updated_skeleton_tree_test.rs @@ -1,12 +1,15 @@ use ethnum::U256; use pretty_assertions::assert_eq; use rstest::{fixture, rstest}; +use std::collections::HashMap; use crate::felt::Felt; use crate::hash::hash_trait::HashOutput; +use crate::patricia_merkle_tree::filled_tree::tree::{FilledTree, FilledTreeImpl}; use crate::patricia_merkle_tree::node_data::inner_node::{EdgePathLength, PathToBottom}; use crate::patricia_merkle_tree::original_skeleton_tree::node::OriginalSkeletonNode; use crate::patricia_merkle_tree::original_skeleton_tree::tree::OriginalSkeletonNodeMap; +use crate::patricia_merkle_tree::original_skeleton_tree::tree::OriginalSkeletonTreeImpl; use crate::patricia_merkle_tree::test_utils::small_tree_index_to_full; use crate::patricia_merkle_tree::types::{NodeIndex, SubTreeHeight}; use crate::patricia_merkle_tree::updated_skeleton_tree::compute_updated_skeleton_tree::{ @@ -14,6 +17,10 @@ use crate::patricia_merkle_tree::updated_skeleton_tree::compute_updated_skeleton }; use crate::patricia_merkle_tree::updated_skeleton_tree::node::UpdatedSkeletonNode; use crate::patricia_merkle_tree::updated_skeleton_tree::tree::UpdatedSkeletonTreeImpl; +use crate::patricia_merkle_tree::updated_skeleton_tree::{ + hash_function::TreeHashFunctionImpl, tree::UpdatedSkeletonTree, +}; +use crate::storage::map_storage::MapStorage; #[fixture] fn updated_skeleton( @@ -490,3 +497,18 @@ pub(crate) fn as_fully_indexed( .map(|index| NodeIndex::from_subtree_index(index, SubTreeHeight::new(subtree_height))) .collect() } + +#[rstest] +#[case::empty_tree(HashOutput::ROOT_OF_EMPTY_TREE)] +#[case::non_empty_tree(HashOutput(Felt::from(77_u128)))] +#[tokio::test] +async fn test_update_non_modified_tree(#[case] root_hash: HashOutput) { + let mut original_skeleton_tree = + OriginalSkeletonTreeImpl::create_impl(&MapStorage::default(), &[], root_hash).unwrap(); + let updated = + UpdatedSkeletonTreeImpl::create(&mut original_skeleton_tree, &HashMap::new()).unwrap(); + let filled = FilledTreeImpl::create::(updated, HashMap::new()) + .await + .unwrap(); + assert_eq!(root_hash, filled.get_root_hash()); +} diff --git a/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/tree.rs b/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/tree.rs index 0da7cd6a..bdef2226 100644 --- a/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/tree.rs +++ b/crates/committer/src/patricia_merkle_tree/updated_skeleton_tree/tree.rs @@ -45,6 +45,9 @@ impl UpdatedSkeletonTree for UpdatedSkeletonTreeImpl { original_skeleton: &mut impl OriginalSkeletonTree, leaf_modifications: &LeafModifications, ) -> UpdatedSkeletonTreeResult { + if leaf_modifications.is_empty() { + return Self::create_unmodified(original_skeleton); + } let skeleton_tree = Self::finalize_bottom_layer(original_skeleton, leaf_modifications); let mut updated_skeleton_tree = UpdatedSkeletonTreeImpl { skeleton_tree }; @@ -64,7 +67,9 @@ impl UpdatedSkeletonTree for UpdatedSkeletonTreeImpl { UpdatedSkeletonNode::Edge(path_to_bottom) } OriginalSkeletonNode::UnmodifiedSubTree(_) => { - unreachable!("Root node cannot be unmodified.") + unreachable!( + "Root node cannot be unmodified when there are some modifications." + ) } };