From e934dd213d61078b693ae429529d147a9c0c163e Mon Sep 17 00:00:00 2001 From: Nimrod Weiss Date: Thu, 11 Apr 2024 09:00:30 +0300 Subject: [PATCH] chore: building original skeleton --- Cargo.lock | 7 + Cargo.toml | 1 + crates/committer/Cargo.toml | 2 + crates/committer/src/hash/types.rs | 2 +- crates/committer/src/patricia_merkle_tree.rs | 1 + .../src/patricia_merkle_tree/errors.rs | 7 +- .../src/patricia_merkle_tree/filled_node.rs | 45 ++- .../original_skeleton_calc.rs | 196 +++++++++++ .../original_skeleton_calc_test.rs | 308 ++++++++++++++++++ .../original_skeleton_node.rs | 1 + .../original_skeleton_tree.rs | 2 +- .../src/patricia_merkle_tree/types.rs | 49 ++- crates/committer/src/storage/storage_trait.rs | 35 +- crates/committer/src/types.rs | 43 ++- 14 files changed, 683 insertions(+), 16 deletions(-) create mode 100644 crates/committer/src/patricia_merkle_tree/original_skeleton_calc.rs create mode 100644 crates/committer/src/patricia_merkle_tree/original_skeleton_calc_test.rs diff --git a/Cargo.lock b/Cargo.lock index 1f6bd33c..01d48031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "bisection" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e079a1bab0ecce6cf4b4b74c0c37afa4a697136eb3b127875c84a8f04a8c3" + [[package]] name = "bitvec" version = "1.0.1" @@ -133,6 +139,7 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" name = "committer" version = "0.1.0-rc.0" dependencies = [ + "bisection", "derive_more", "pretty_assertions", "rstest", diff --git a/Cargo.toml b/Cargo.toml index 172cfd78..1a36daca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ license = "Apache-2.0" license-file = "LICENSE" [workspace.dependencies] +bisection = "0.1.0" clap = { version = "4.5.4", features = ["cargo", "derive"] } derive_more = "0.99.17" pretty_assertions = "1.2.1" diff --git a/crates/committer/Cargo.toml b/crates/committer/Cargo.toml index 4e0faa63..49cc7eeb 100644 --- a/crates/committer/Cargo.toml +++ b/crates/committer/Cargo.toml @@ -14,5 +14,7 @@ pretty_assertions.workspace = true rstest.workspace = true [dependencies] +bisection.workspace = true derive_more.workspace = true starknet-types-core.workspace = true + diff --git a/crates/committer/src/hash/types.rs b/crates/committer/src/hash/types.rs index db4ab7e5..798c43d4 100644 --- a/crates/committer/src/hash/types.rs +++ b/crates/committer/src/hash/types.rs @@ -6,7 +6,7 @@ use crate::types::Felt; pub(crate) struct HashInputPair(pub Felt, pub Felt); #[allow(dead_code)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub(crate) struct HashOutput(pub Felt); #[allow(dead_code)] diff --git a/crates/committer/src/patricia_merkle_tree.rs b/crates/committer/src/patricia_merkle_tree.rs index 165672b4..de295e1d 100644 --- a/crates/committer/src/patricia_merkle_tree.rs +++ b/crates/committer/src/patricia_merkle_tree.rs @@ -1,6 +1,7 @@ pub mod errors; pub mod filled_node; pub mod filled_tree; +pub mod original_skeleton_calc; pub mod original_skeleton_node; pub mod original_skeleton_tree; pub mod types; diff --git a/crates/committer/src/patricia_merkle_tree/errors.rs b/crates/committer/src/patricia_merkle_tree/errors.rs index 6105c696..639e386b 100644 --- a/crates/committer/src/patricia_merkle_tree/errors.rs +++ b/crates/committer/src/patricia_merkle_tree/errors.rs @@ -1,6 +1,11 @@ // TODO(Amos, 01/04/2024): Add error types. #[derive(Debug)] -pub(crate) enum OriginalSkeletonTreeError {} +#[allow(dead_code)] +pub(crate) enum OriginalSkeletonTreeError { + // TODO(Nimrod, 5/5/2024): If possible, divide Deserialization to more specific types of errors. + Deserialization(String), + StorageRead, +} #[derive(Debug)] pub(crate) enum UpdatedSkeletonTreeError { diff --git a/crates/committer/src/patricia_merkle_tree/filled_node.rs b/crates/committer/src/patricia_merkle_tree/filled_node.rs index e8329d67..d977ab88 100644 --- a/crates/committer/src/patricia_merkle_tree/filled_node.rs +++ b/crates/committer/src/patricia_merkle_tree/filled_node.rs @@ -1,16 +1,24 @@ use crate::patricia_merkle_tree::types::{EdgeData, LeafDataTrait}; +use crate::storage::storage_trait::{StorageKey, StorageValue}; use crate::{hash::types::HashOutput, types::Felt}; + +use super::original_skeleton_tree::OriginalSkeletonTreeResult; +use super::types::{EdgePath, EdgePathLength, PathToBottom}; + // TODO(Nimrod, 1/6/2024): Swap to starknet-types-core types once implemented. #[allow(dead_code)] +#[derive(Debug, PartialEq, Eq)] pub(crate) struct ClassHash(pub Felt); + #[allow(dead_code)] +#[derive(Debug, PartialEq, Eq)] pub(crate) struct Nonce(pub Felt); #[allow(dead_code)] /// A node in a Patricia-Merkle tree which was modified during an update. pub(crate) struct FilledNode { pub(crate) hash: HashOutput, - data: NodeData, + pub(crate) data: NodeData, } #[allow(dead_code)] @@ -28,6 +36,7 @@ pub(crate) struct BinaryData { } #[allow(dead_code)] +#[derive(Debug, Eq, PartialEq)] pub(crate) enum LeafData { StorageValue(Felt), CompiledClassHash(ClassHash), @@ -55,3 +64,37 @@ impl LeafDataTrait for LeafData { } } } + +#[allow(dead_code)] +impl FilledNode { + pub(crate) fn deserialize( + key: &StorageKey, + value: &StorageValue, + ) -> OriginalSkeletonTreeResult { + // TODO(Nimrod, 30/4/2024): Compare to constant values once [PR #18] (https://github.com/starkware-libs/committer/pull/18) is merged. + if value.0.len() == 64 { + Ok(Self { + hash: HashOutput(Felt::from_bytes_be_slice(&key.0)), + data: NodeData::Binary(BinaryData { + left_hash: HashOutput(Felt::from_bytes_be_slice(&value.0[..32])), + right_hash: HashOutput(Felt::from_bytes_be_slice(&value.0[32..])), + }), + }) + } + // TODO(Nimrod, 30/4/2024): Compare to constant values once [PR #18] (https://github.com/starkware-libs/committer/pull/18) is merged. + else if value.0.len() == 65 { + return Ok(Self { + hash: HashOutput(Felt::from_bytes_be_slice(&key.0)), + data: NodeData::Edge(EdgeData { + bottom_hash: HashOutput(Felt::from_bytes_be_slice(&value.0[..32])), + path_to_bottom: PathToBottom { + path: EdgePath(Felt::from_bytes_be_slice(&value.0[32..64])), + length: EdgePathLength(value.0[64]), + }, + }), + }); + } else { + todo!("Deserialize leaves.") + } + } +} diff --git a/crates/committer/src/patricia_merkle_tree/original_skeleton_calc.rs b/crates/committer/src/patricia_merkle_tree/original_skeleton_calc.rs new file mode 100644 index 00000000..cfb37eba --- /dev/null +++ b/crates/committer/src/patricia_merkle_tree/original_skeleton_calc.rs @@ -0,0 +1,196 @@ +use bisection::{bisect_left, bisect_right}; + +use crate::hash::types::HashOutput; +use crate::patricia_merkle_tree::errors::OriginalSkeletonTreeError; +use crate::patricia_merkle_tree::filled_node::BinaryData; +use crate::patricia_merkle_tree::filled_node::FilledNode; +use crate::patricia_merkle_tree::filled_node::NodeData; +use crate::patricia_merkle_tree::original_skeleton_tree::OriginalSkeletonTreeResult; +use crate::patricia_merkle_tree::types::EdgeData; +use crate::patricia_merkle_tree::types::TreeHeight; +use crate::patricia_merkle_tree::{ + filled_node::LeafData, original_skeleton_node::OriginalSkeletonNode, types::NodeIndex, +}; +use crate::storage::storage_trait::Storage; +use crate::storage::storage_trait::StorageKey; +use crate::storage::storage_trait::StoragePrefix; +use crate::types::Felt; +use std::collections::HashMap; + +#[cfg(test)] +#[path = "original_skeleton_calc_test.rs"] +pub mod original_skeleton_calc_test; + +#[allow(dead_code)] +pub(crate) struct OriginalSkeletonTreeImpl { + nodes: HashMap>, + leaf_modifications: HashMap, + tree_height: TreeHeight, +} + +#[allow(dead_code)] +struct SubTree<'a> { + pub sorted_leaf_indices: &'a [NodeIndex], + pub root_index: NodeIndex, + pub root_hash: HashOutput, +} + +#[allow(dead_code)] +impl<'a> SubTree<'a> { + pub(crate) fn get_height(&self, total_tree_height: &TreeHeight) -> TreeHeight { + TreeHeight(total_tree_height.0 - self.root_index.0.bits() + 1) + } + + pub(crate) fn split_leaves( + &self, + total_tree_height: &TreeHeight, + ) -> (&'a [NodeIndex], &'a [NodeIndex]) { + let height = self.get_height(total_tree_height); + let leftmost_index_in_right_subtree = ((self.root_index.times_two_to_the_power(1)) + + NodeIndex(Felt::ONE)) + .times_two_to_the_power(height.0 - 1); + let mid = bisect_left(self.sorted_leaf_indices, &leftmost_index_in_right_subtree); + ( + &self.sorted_leaf_indices[..mid], + &self.sorted_leaf_indices[mid..], + ) + } + + pub(crate) fn is_sibling(&self) -> bool { + self.sorted_leaf_indices.is_empty() + } +} + +#[allow(dead_code)] +impl OriginalSkeletonTreeImpl { + /// Fetches the Patricia witnesses, required to build the original skeleton tree from storage. + /// Given a list of subtrees, traverses towards their leaves and fetches all non-empty and + /// sibling nodes. Assumes no subtrees of height 0 (leaves). + + fn fetch_nodes( + &mut self, + subtrees: Vec>, + storage: &impl Storage, + ) -> OriginalSkeletonTreeResult<()> { + if subtrees.is_empty() { + return Ok(()); + } + let mut next_subtrees = Vec::new(); + let mut subtrees_roots = Vec::new(); + for subtree in subtrees.iter() { + let val = storage + .get( + &StorageKey::from(subtree.root_hash.0).with_prefix(StoragePrefix::PatriciaNode), + ) + .ok_or(OriginalSkeletonTreeError::StorageRead)?; + subtrees_roots.push(FilledNode::deserialize( + &StorageKey::from(subtree.root_hash.0), + &val, + )?) + } + for (filled_node, subtree) in subtrees_roots.into_iter().zip(subtrees.iter()) { + match filled_node.data { + // Binary node. + NodeData::Binary(BinaryData { + left_hash, + right_hash, + }) => { + if subtree.is_sibling() { + self.nodes.insert( + subtree.root_index, + OriginalSkeletonNode::LeafOrBinarySibling(filled_node.hash), + ); + continue; + } + self.nodes + .insert(subtree.root_index, OriginalSkeletonNode::Binary); + let (left_leaves, right_leaves) = subtree.split_leaves(&self.tree_height); + let left_root_index = subtree.root_index.times_two_to_the_power(1); + let left_subtree = SubTree { + sorted_leaf_indices: left_leaves, + root_index: left_root_index, + root_hash: left_hash, + }; + let right_subtree = SubTree { + sorted_leaf_indices: right_leaves, + root_index: left_root_index + NodeIndex(Felt::ONE), + root_hash: right_hash, + }; + if subtree.get_height(&self.tree_height).is_of_height_one() { + // Children are leaves. + if left_subtree.is_sibling() { + self.nodes.insert( + left_subtree.root_index, + OriginalSkeletonNode::LeafOrBinarySibling(left_hash), + ); + } + if right_subtree.is_sibling() { + self.nodes.insert( + right_subtree.root_index, + OriginalSkeletonNode::LeafOrBinarySibling(right_hash), + ); + } + continue; + } + next_subtrees.extend(vec![left_subtree, right_subtree]); + } + // Edge node. + NodeData::Edge(EdgeData { + bottom_hash, + path_to_bottom, + }) => { + if subtree.is_sibling() { + // Sibling will remain an edge node. No need to open the bottom. + self.nodes.insert( + subtree.root_index, + OriginalSkeletonNode::EdgeSibling(EdgeData { + bottom_hash, + path_to_bottom, + }), + ); + continue; + } + // Parse bottom. + let bottom_index = path_to_bottom.bottom_index(subtree.root_index); + let bottom_height = + subtree.get_height(&self.tree_height) - TreeHeight(path_to_bottom.length.0); + let leftmost_in_subtree = bottom_index.times_two_to_the_power(bottom_height.0); + let rightmost_in_subtree = leftmost_in_subtree + + (NodeIndex(Felt::ONE).times_two_to_the_power(bottom_height.0)) + - NodeIndex(Felt::ONE); + let bottom_leaves = &subtree.sorted_leaf_indices[bisect_left( + subtree.sorted_leaf_indices, + &leftmost_in_subtree, + ) + ..bisect_right(subtree.sorted_leaf_indices, &rightmost_in_subtree)]; + self.nodes.insert( + subtree.root_index, + OriginalSkeletonNode::Edge { path_to_bottom }, + ); + if bottom_height.is_leaf_height() { + if bottom_leaves.is_empty() { + // Bottom is a leaf sibling. + self.nodes.insert( + bottom_index, + OriginalSkeletonNode::LeafOrBinarySibling(bottom_hash), + ); + } + continue; + } + let bottom_subtree = SubTree { + sorted_leaf_indices: bottom_leaves, + root_index: bottom_index, + root_hash: bottom_hash, + }; + next_subtrees.push(bottom_subtree); + } + NodeData::Leaf(_) => { + return Err(OriginalSkeletonTreeError::Deserialization( + "Fetch nodes called on a leaf subtree. Should not happen.".to_string(), + )) + } + } + } + self.fetch_nodes(next_subtrees, storage) + } +} diff --git a/crates/committer/src/patricia_merkle_tree/original_skeleton_calc_test.rs b/crates/committer/src/patricia_merkle_tree/original_skeleton_calc_test.rs new file mode 100644 index 00000000..bacdd636 --- /dev/null +++ b/crates/committer/src/patricia_merkle_tree/original_skeleton_calc_test.rs @@ -0,0 +1,308 @@ +use crate::hash::types::HashOutput; +use crate::patricia_merkle_tree::original_skeleton_calc::LeafData; +use crate::patricia_merkle_tree::original_skeleton_node::OriginalSkeletonNode; +use crate::patricia_merkle_tree::types::{EdgePath, EdgePathLength, NodeIndex, PathToBottom}; +use crate::types::Felt; +use pretty_assertions::assert_eq; +use rstest::rstest; +use std::collections::HashMap; + +use crate::patricia_merkle_tree::types::TreeHeight; +use crate::storage::storage_trait::{StorageKey, StoragePrefix, StorageValue}; + +use super::OriginalSkeletonTreeImpl; +use super::SubTree; + +#[rstest] +// This test assumes for simplicity that hash is addition (i.e hash(a,b) = a + b). +/// +/// Old tree structure: +/// +/// 50 +/// / \ +/// 30 20 +/// / \ \ +/// 17 13 \ +/// / \ \ \ +/// 8 9 11 15 +/// +/// Modified leaves indices: [8, 10, 13] +/// +/// Expected skeleton: +/// +/// B +/// / \ +/// B E +/// / \ \ +/// B E \ +/// / \ \ \ +/// NZ 9 11 15 +/// +/// + +#[case::simple_tree_of_height_3( + HashMap::from([ + create_leaf_entry(8), + create_leaf_entry(9), + create_leaf_entry(11), + create_leaf_entry(15), + create_binary_entry(8, 9), + create_edge_entry(11, 1, 1), + create_binary_entry(17, 13), + create_edge_entry(15, 3, 2), + create_binary_entry(30, 20) + ]), + create_modifications(vec![(8, 4), (10, 3), (13, 2)]), + HashOutput(Felt::from(50_u128)), + HashMap::from([ + (NodeIndex::from(1), OriginalSkeletonNode::Binary), + (NodeIndex::from(2), OriginalSkeletonNode::Binary), + (NodeIndex::from(3), OriginalSkeletonNode::Edge { + path_to_bottom: PathToBottom { + path: EdgePath(Felt::from(3_u128)), + length: EdgePathLength(2), + }, + }), + (NodeIndex::from(4), OriginalSkeletonNode::Binary), + (NodeIndex::from(5), OriginalSkeletonNode::Edge { + path_to_bottom: PathToBottom { + path: EdgePath(Felt::from(1_u128)), + length: EdgePathLength(1) + } + }), + (NodeIndex::from(9), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(9_u128)) + )), + (NodeIndex::from(15), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(15_u128)) + )), + (NodeIndex::from(11), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(11_u128)) + )), + ]), + TreeHeight(3) +)] +/// Old tree structure: +/// +/// 29 +/// / \ +/// 13 16 +/// / / \ +/// 12 5 11 +/// / \ \ / \ +/// 10 2 3 4 7 +/// +/// Modified leaves indices: [8, 11, 13] +/// +/// Expected skeleton: +/// +/// B +/// / \ +/// E B +/// / / \ +/// B E E +/// / \ \ \ +/// NZ 2 NZ NZ +/// + +#[case::another_simple_tree_of_height_3( + HashMap::from([ + create_leaf_entry(10), + create_leaf_entry(2), + create_leaf_entry(3), + create_leaf_entry(4), + create_leaf_entry(7), + create_binary_entry(10, 2), + create_edge_entry(3, 1, 1), + create_binary_entry(4, 7), + create_edge_entry(12, 0, 1), + create_binary_entry(5, 11), + create_binary_entry(13, 16), + ]), + create_modifications(vec![(8, 5), (11, 1), (13, 3)]), + HashOutput(Felt::from(29_u128)), + HashMap::from([ + (NodeIndex::from(1), OriginalSkeletonNode::Binary), + (NodeIndex::from(2), OriginalSkeletonNode::Edge { + path_to_bottom: PathToBottom { + path: EdgePath(Felt::ZERO), + length: EdgePathLength(1) + } + }), + (NodeIndex::from(3), OriginalSkeletonNode::Binary), + (NodeIndex::from(4), OriginalSkeletonNode::Binary), + (NodeIndex::from(6), OriginalSkeletonNode::Edge { + path_to_bottom: PathToBottom { + path: EdgePath(Felt::from(1_u128)), + length: EdgePathLength(1) + } + }), + (NodeIndex::from(7), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(11_u128)) + )), + (NodeIndex::from(9), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(2_u128)) + )) + ]), + TreeHeight(3) +)] +/// Old tree structure: +/// +/// 116 +/// / \ +/// 26 90 +/// / / \ +/// / 25 65 +/// / \ / \ +/// 24 \ 6 59 +/// / \ \ / / \ +/// 11 13 20 5 19 40 +/// +/// Modified leaves indices: [18, 25, 29, 30] +/// +/// Expected skeleton: +/// +/// B +/// / \ +/// E B +/// / / \ +/// / E B +/// / \ / \ +/// 24 \ E B +/// \ / \ +/// 20 5 40 +/// +#[case::tree_of_height_4_with_long_edge( + HashMap::from([ + create_leaf_entry(11), + create_leaf_entry(13), + create_leaf_entry(20), + create_leaf_entry(5), + create_leaf_entry(19), + create_leaf_entry(40), + create_binary_entry(11, 13), + create_edge_entry(5, 0, 1), + create_binary_entry(19, 40), + create_edge_entry(20, 3, 2), + create_binary_entry(6, 59), + create_edge_entry(24, 0, 2), + create_binary_entry(25, 65), + create_binary_entry(26, 90) + ]), + create_modifications(vec![(18, 5), (25, 1), (29, 15), (30, 3)]), + HashOutput(Felt::from(116_u128)), + HashMap::from([ + (NodeIndex::from(1), OriginalSkeletonNode::Binary), + (NodeIndex::from(2), OriginalSkeletonNode::Edge { + path_to_bottom: PathToBottom { + path: EdgePath(Felt::ZERO), + length: EdgePathLength(2) + } + }), + (NodeIndex::from(3), OriginalSkeletonNode::Binary), + (NodeIndex::from(6), OriginalSkeletonNode::Edge { + path_to_bottom: PathToBottom { + path: EdgePath(Felt::from(3_u128)), + length: EdgePathLength(2) + } + }), + (NodeIndex::from(7), OriginalSkeletonNode::Binary), + (NodeIndex::from(8), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(24_u128)) + )), + (NodeIndex::from(14), OriginalSkeletonNode::Edge { + path_to_bottom: PathToBottom { + path: EdgePath(Felt::ZERO), + length: EdgePathLength(1) + } + }), + (NodeIndex::from(15), OriginalSkeletonNode::Binary), + (NodeIndex::from(27), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(20_u128)) + )), + (NodeIndex::from(28), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(5_u128)) + )), + (NodeIndex::from(31), OriginalSkeletonNode::LeafOrBinarySibling( + HashOutput(Felt::from(40_u128)) + )), + + ]), + TreeHeight(4) +)] +fn test_fetch_nodes( + #[case] storage: HashMap, + #[case] leaf_modifications: HashMap, + #[case] root_hash: HashOutput, + #[case] expected_nodes: HashMap>, + #[case] tree_height: TreeHeight, +) { + let mut skeleton_tree = OriginalSkeletonTreeImpl { + nodes: HashMap::new(), + leaf_modifications, + tree_height, + }; + let mut sorted_leaf_indices: Vec = + skeleton_tree.leaf_modifications.keys().copied().collect(); + sorted_leaf_indices.sort(); + let subtrees = vec![SubTree { + sorted_leaf_indices: &sorted_leaf_indices, + root_index: NodeIndex(Felt::ONE), + root_hash, + }]; + assert!(skeleton_tree.fetch_nodes(subtrees, storage).is_ok()); + assert_eq!(&skeleton_tree.nodes, &expected_nodes); +} + +fn create_32_bytes_entry(simple_val: u8) -> Vec { + let mut res = vec![0; 31]; + res.push(simple_val); + res +} + +fn create_patricia_key(val: u8) -> StorageKey { + StorageKey(create_32_bytes_entry(val)).with_prefix(StoragePrefix::PatriciaNode) +} + +fn create_binary_val(left: u8, right: u8) -> StorageValue { + StorageValue( + (create_32_bytes_entry(left) + .into_iter() + .chain(create_32_bytes_entry(right))) + .collect(), + ) +} + +fn create_edge_val(hash: u8, path: u8, length: u8) -> StorageValue { + StorageValue( + create_32_bytes_entry(hash) + .into_iter() + .chain(create_32_bytes_entry(path)) + .chain([length]) + .collect(), + ) +} + +fn create_modifications(modifications: Vec<(u128, u128)>) -> HashMap { + modifications + .into_iter() + .map(|(idx, val)| { + ( + NodeIndex::from(idx), + LeafData::StorageValue(Felt::from(val)), + ) + }) + .collect() +} + +fn create_leaf_entry(val: u8) -> (StorageKey, StorageValue) { + (create_patricia_key(val), StorageValue(create_32_bytes_entry(val))) +} + +fn create_binary_entry(left: u8, right: u8) -> (StorageKey, StorageValue) { + (create_patricia_key(left + right), create_binary_val(left, right)) +} + +fn create_edge_entry(hash: u8, path: u8, length: u8) -> (StorageKey, StorageValue) { + (create_patricia_key(hash + path + length), create_edge_val(hash, path, length)) +} diff --git a/crates/committer/src/patricia_merkle_tree/original_skeleton_node.rs b/crates/committer/src/patricia_merkle_tree/original_skeleton_node.rs index 4671ae09..854f8c32 100644 --- a/crates/committer/src/patricia_merkle_tree/original_skeleton_node.rs +++ b/crates/committer/src/patricia_merkle_tree/original_skeleton_node.rs @@ -2,6 +2,7 @@ use crate::hash::types::HashOutput; use crate::patricia_merkle_tree::types::{EdgeData, LeafDataTrait, PathToBottom}; #[allow(dead_code)] +#[derive(Debug, PartialEq, Eq)] /// A node in the structure of a Patricia-Merkle tree, before the update. pub(crate) enum OriginalSkeletonNode { Binary, diff --git a/crates/committer/src/patricia_merkle_tree/original_skeleton_tree.rs b/crates/committer/src/patricia_merkle_tree/original_skeleton_tree.rs index 461e902f..6ee2a9ee 100644 --- a/crates/committer/src/patricia_merkle_tree/original_skeleton_tree.rs +++ b/crates/committer/src/patricia_merkle_tree/original_skeleton_tree.rs @@ -16,7 +16,7 @@ pub(crate) type OriginalSkeletonTreeResult = Result { fn compute_original_skeleton_tree( storage: impl Storage, - leaf_indices: &[NodeIndex], + leaf_indices: [NodeIndex], root_hash: HashOutput, tree_height: TreeHeight, ) -> OriginalSkeletonTreeResult>; diff --git a/crates/committer/src/patricia_merkle_tree/types.rs b/crates/committer/src/patricia_merkle_tree/types.rs index 8a7a2a64..55258557 100644 --- a/crates/committer/src/patricia_merkle_tree/types.rs +++ b/crates/committer/src/patricia_merkle_tree/types.rs @@ -47,7 +47,19 @@ impl TreeHashFunction for TreeHashFunctionImpl } #[allow(dead_code)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + derive_more::Add, + derive_more::Mul, + derive_more::Sub, + PartialOrd, + Ord, +)] pub(crate) struct NodeIndex(pub Felt); #[allow(dead_code)] @@ -63,30 +75,61 @@ impl NodeIndex { let PathToBottom { path, length } = path_to_bottom; NodeIndex(index.0 * Felt::TWO.pow(length.0) + path.0) } + + pub(crate) fn times_two_to_the_power(&self, steps: u8) -> Self { + NodeIndex(self.0.times_two_to_the_power(steps)) + } +} + +impl From for NodeIndex { + fn from(value: u128) -> Self { + Self(Felt::from(value)) + } } #[allow(dead_code)] +#[derive(Debug, PartialEq, Eq)] pub(crate) struct EdgePath(pub Felt); #[allow(dead_code)] +#[derive(Debug, Eq, PartialEq)] pub(crate) struct EdgePathLength(pub u8); #[allow(dead_code)] +#[derive(derive_more::Sub)] pub(crate) struct TreeHeight(pub u8); #[allow(dead_code)] +#[derive(Debug, Eq, PartialEq)] pub(crate) struct PathToBottom { pub path: EdgePath, pub length: EdgePathLength, } #[allow(dead_code)] +#[derive(Debug, Eq, PartialEq)] pub(crate) struct EdgeData { - bottom_hash: HashOutput, - path_to_bottom: PathToBottom, + pub(crate) bottom_hash: HashOutput, + pub(crate) path_to_bottom: PathToBottom, } pub(crate) trait LeafDataTrait { /// Returns true if leaf is empty. fn is_empty(&self) -> bool; } + +impl TreeHeight { + pub(crate) fn is_leaf_height(&self) -> bool { + self.0 == 0 + } + + pub(crate) fn is_of_height_one(&self) -> bool { + self.0 == 1 + } +} + +impl PathToBottom { + pub(crate) fn bottom_index(&self, root_index: NodeIndex) -> NodeIndex { + root_index.times_two_to_the_power(self.length.0) + NodeIndex(self.path.0) + } +} diff --git a/crates/committer/src/storage/storage_trait.rs b/crates/committer/src/storage/storage_trait.rs index 788021bb..aa4668d2 100644 --- a/crates/committer/src/storage/storage_trait.rs +++ b/crates/committer/src/storage/storage_trait.rs @@ -1,11 +1,13 @@ -use crate::storage::errors::StorageError; +use crate::{storage::errors::StorageError, types::Felt}; use std::collections::HashMap; #[allow(dead_code)] -pub(crate) struct StorageKey(Vec); +#[derive(PartialEq, Eq, Hash)] +pub(crate) struct StorageKey(pub Vec); #[allow(dead_code)] -pub(crate) struct StorageValue(Vec); +#[derive(Clone)] +pub(crate) struct StorageValue(pub Vec); pub(crate) trait Storage { /// Returns value from storage, if it exists. @@ -20,3 +22,30 @@ pub(crate) trait Storage { /// Deletes value from storage. Returns error if key does not exist. fn delete(&mut self, key: &StorageKey) -> Result<(), StorageError>; } + +impl StorageKey { + pub(crate) fn with_prefix(&self, prefix: StoragePrefix) -> Self { + let mut prefix = prefix.to_bytes().to_vec(); + prefix.extend(&self.0); + StorageKey(prefix) + } +} + +impl From for StorageKey { + fn from(value: Felt) -> Self { + StorageKey(value.to_bytes_be().to_vec()) + } +} + +#[allow(dead_code)] +pub(crate) enum StoragePrefix { + PatriciaNode, +} +#[allow(dead_code)] +impl StoragePrefix { + pub(crate) fn to_bytes(&self) -> &[u8] { + match self { + Self::PatriciaNode => "patricia_node:".as_bytes(), + } + } +} diff --git a/crates/committer/src/types.rs b/crates/committer/src/types.rs index 9e6e88b4..ec232bc0 100644 --- a/crates/committer/src/types.rs +++ b/crates/committer/src/types.rs @@ -1,6 +1,18 @@ use starknet_types_core::felt::{Felt as StarknetTypesFelt, FromStrError}; -#[derive(Eq, PartialEq, Clone, Copy, Debug, Default, Hash, derive_more::Add)] +#[derive( + Eq, + PartialEq, + Clone, + Copy, + Debug, + Default, + Hash, + derive_more::Add, + derive_more::Sub, + PartialOrd, + Ord, +)] pub(crate) struct Felt(StarknetTypesFelt); #[macro_export] @@ -35,17 +47,36 @@ impl std::ops::Mul for Felt { #[allow(dead_code)] impl Felt { - pub const ZERO: Felt = Felt(StarknetTypesFelt::ZERO); - pub const ONE: Felt = Felt(StarknetTypesFelt::ONE); - pub const TWO: Felt = Felt(StarknetTypesFelt::TWO); + pub(crate) const ZERO: Felt = Felt(StarknetTypesFelt::ZERO); + pub(crate) const ONE: Felt = Felt(StarknetTypesFelt::ONE); + pub(crate) const TWO: Felt = Felt(StarknetTypesFelt::TWO); /// Raises `self` to the power of `exponent`. - pub fn pow(&self, exponent: impl Into) -> Self { + pub(crate) fn pow(&self, exponent: impl Into) -> Self { Self(self.0.pow(exponent.into())) } + pub(crate) fn bits(&self) -> u8 { + self.0 + .bits() + .try_into() + .expect("Should not fail as it takes less than 252 bits to represent a felt.") + } + + pub(crate) fn times_two_to_the_power(&self, power: u8) -> Self { + *self * Felt::TWO.pow(power) + } + + pub(crate) fn from_bytes_be_slice(bytes: &[u8]) -> Self { + Self(StarknetTypesFelt::from_bytes_be_slice(bytes)) + } + + pub(crate) fn to_bytes_be(self) -> [u8; 32] { + self.0.to_bytes_be() + } + /// Parse a hex-encoded number into `Felt`. - pub fn from_hex(hex_string: &str) -> Result { + pub(crate) fn from_hex(hex_string: &str) -> Result { Ok(StarknetTypesFelt::from_hex(hex_string)?.into()) } }