Skip to content

Commit

Permalink
chore: building original skeleton
Browse files Browse the repository at this point in the history
  • Loading branch information
nimrod-starkware committed Apr 18, 2024
1 parent 600f4b8 commit 86aa817
Show file tree
Hide file tree
Showing 15 changed files with 712 additions and 13 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/committer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pretty_assertions.workspace = true
rstest.workspace = true

[dependencies]
bisection.workspace = true
derive_more.workspace = true
rayon.workspace = true
starknet-types-core.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/committer/src/patricia_merkle_tree.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
23 changes: 21 additions & 2 deletions crates/committer/src/patricia_merkle_tree/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
use thiserror::Error;

use crate::patricia_merkle_tree::types::NodeIndex;
use crate::storage::errors::StorageError;
use crate::storage::storage_trait::StorageValue;

// TODO(Amos, 01/04/2024): Add error types.
#[derive(Debug)]
pub(crate) enum OriginalSkeletonTreeError {}
#[allow(dead_code)]
#[derive(Debug, Error)]
pub(crate) enum OriginalSkeletonTreeError {
#[error(
"Failed to deserialize the storage value: {0:?} while building the original skeleton tree."
)]
Deserialization(StorageValue),
#[error(
"Unable to read from storage the storage key: {0:?} while building the \
original skeleton tree."
)]
StorageRead(#[from] StorageError),
#[error("`fetch_nodes` method encountered unexpectedally a leaf node at index: {0:?}")]
LeafEncountered(NodeIndex),
}

#[derive(Debug)]
#[allow(dead_code)]
Expand Down
49 changes: 47 additions & 2 deletions crates/committer/src/patricia_merkle_tree/filled_node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use crate::hash::hash_trait::HashOutput;
use crate::patricia_merkle_tree::types::{EdgeData, LeafDataTrait};
use crate::{hash::hash_trait::HashOutput, types::Felt};
use crate::storage::storage_trait::{StorageKey, StorageValue};
use crate::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.

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -32,7 +38,7 @@ pub(crate) struct BinaryData {
}

#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum LeafData {
StorageValue(Felt),
CompiledClassHash(ClassHash),
Expand Down Expand Up @@ -60,3 +66,42 @@ impl LeafDataTrait for LeafData {
}
}
}

#[allow(dead_code)]
impl FilledNode<LeafData> {
pub(crate) fn deserialize(
key: &StorageKey,
value: &StorageValue,
) -> OriginalSkeletonTreeResult<Self> {
// 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(Nimrod, 5/5/2024): See if deserializing nodes data is needed somewhere.
return Ok(Self {
hash: HashOutput(Felt::from_bytes_be_slice(&key.0)),
// Dummy value which will be ignored.
data: NodeData::Leaf(LeafData::StorageValue(Felt::ZERO)),
});
}
}
}
203 changes: 203 additions & 0 deletions crates/committer/src/patricia_merkle_tree/original_skeleton_calc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use crate::hash::hash_trait::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::errors::StorageError;
use crate::storage::storage_trait::Storage;
use crate::storage::storage_trait::StorageKey;
use crate::storage::storage_trait::StoragePrefix;
use crate::types::Felt;
use bisection::{bisect_left, bisect_right};
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<NodeIndex, OriginalSkeletonNode<LeafData>>,
leaf_modifications: HashMap<NodeIndex, LeafData>,
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<SubTree<'_>>,
storage: &impl Storage,
) -> OriginalSkeletonTreeResult<()> {
if subtrees.is_empty() {
return Ok(());
}
let mut next_subtrees = Vec::new();
let subtrees_roots =
OriginalSkeletonTreeImpl::calculate_subtrees_roots(&subtrees, storage)?;
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::LeafEncountered(
subtree.root_index,
))
}
}
}
self.fetch_nodes(next_subtrees, storage)
}

fn calculate_subtrees_roots(
subtrees: &[SubTree<'_>],
storage: &impl Storage,
) -> OriginalSkeletonTreeResult<Vec<FilledNode<LeafData>>> {
let mut subtrees_roots = vec![];
for subtree in subtrees.iter() {
let key =
StorageKey::from(subtree.root_hash.0).with_prefix(StoragePrefix::PatriciaNode);
let val = storage.get(&key).ok_or(StorageError::MissingKey(key))?;
subtrees_roots.push(FilledNode::deserialize(
&StorageKey::from(subtree.root_hash.0),
val,
)?)
}
Ok(subtrees_roots)
}
}
Loading

0 comments on commit 86aa817

Please sign in to comment.