Skip to content

Commit 780f959

Browse files
committed
starknet_committer: make fetch nodes depend on db layout
1 parent 815ce74 commit 780f959

File tree

5 files changed

+131
-55
lines changed

5 files changed

+131
-55
lines changed

crates/starknet_committer/src/db.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod db_layout;
12
#[cfg(any(feature = "testing", test))]
23
pub mod external_test_utils;
34
pub mod facts_db;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use starknet_patricia::patricia_merkle_tree::filled_tree::node::FilledNode;
2+
use starknet_patricia::patricia_merkle_tree::node_data::leaf::Leaf;
3+
use starknet_patricia_storage::errors::DeserializationError;
4+
use starknet_patricia_storage::storage_trait::DbValue;
5+
6+
pub trait NodeLayout<L: Leaf> {
7+
type ChildData: Copy;
8+
type DeserializationContext;
9+
fn deserialize_node(
10+
value: &DbValue,
11+
deserialize_context: &Self::DeserializationContext,
12+
) -> Result<FilledNode<L, Self::ChildData>, DeserializationError>;
13+
}

crates/starknet_committer/src/db/facts_db/create_facts_tree.rs

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ use starknet_patricia_storage::db_object::HasStaticPrefix;
2424
use starknet_patricia_storage::storage_trait::Storage;
2525
use tracing::warn;
2626

27-
use crate::db::facts_db::traversal::calculate_subtrees_roots;
27+
use crate::db::db_layout::NodeLayout;
28+
use crate::db::facts_db::db::FactsNodeLayout;
29+
use crate::db::facts_db::traversal::get_roots_from_storage;
2830
use crate::db::facts_db::types::FactsSubTree;
2931

3032
#[cfg(test)]
@@ -42,65 +44,85 @@ macro_rules! log_trivial_modification {
4244
/// Given a list of subtrees, traverses towards their leaves and fetches all non-empty,
4345
/// unmodified nodes. If `compare_modified_leaves` is set, function logs out a warning when
4446
/// encountering a trivial modification. Fills the previous leaf values if it is not none.
45-
async fn fetch_nodes<'a, L: Leaf>(
47+
async fn fetch_nodes<'a, L, Layout, SubTree>(
4648
skeleton_tree: &mut OriginalSkeletonTreeImpl<'a>,
47-
subtrees: Vec<FactsSubTree<'a>>,
49+
subtrees: Vec<SubTree>,
4850
storage: &mut impl Storage,
4951
leaf_modifications: &LeafModifications<L>,
5052
config: &impl OriginalSkeletonTreeConfig<L>,
5153
mut previous_leaves: Option<&mut HashMap<NodeIndex, L>>,
5254
key_context: &<L as HasStaticPrefix>::KeyContext,
53-
) -> OriginalSkeletonTreeResult<()> {
55+
) -> OriginalSkeletonTreeResult<()>
56+
where
57+
L: Leaf,
58+
Layout: NodeLayout<L>,
59+
SubTree: SubTreeTrait<'a, NodeData = Layout::ChildData, NodeContext = Layout::DeserializationContext>,
60+
{
5461
let mut current_subtrees = subtrees;
5562
let mut next_subtrees = Vec::new();
5663
while !current_subtrees.is_empty() {
5764
let should_fetch_modified_leaves =
5865
config.compare_modified_leaves() || previous_leaves.is_some();
5966
let filled_roots =
60-
calculate_subtrees_roots::<L>(&current_subtrees, storage, key_context).await?;
67+
get_roots_from_storage::<L, Layout>(&current_subtrees, storage, key_context).await?;
6168
for (filled_root, subtree) in filled_roots.into_iter().zip(current_subtrees.iter()) {
6269
match filled_root.data {
6370
// Binary node.
64-
NodeData::<L, HashOutput>::Binary(BinaryData { left_data, right_data }) => {
71+
NodeData::Binary(BinaryData { left_data, right_data }) => {
6572
if subtree.is_unmodified() {
6673
skeleton_tree.nodes.insert(
67-
subtree.root_index,
74+
subtree.get_root_index(),
6875
OriginalSkeletonNode::UnmodifiedSubTree(filled_root.hash),
6976
);
7077
continue;
7178
}
72-
skeleton_tree.nodes.insert(subtree.root_index, OriginalSkeletonNode::Binary);
79+
skeleton_tree
80+
.nodes
81+
.insert(subtree.get_root_index(), OriginalSkeletonNode::Binary);
7382
let (left_subtree, right_subtree) =
7483
subtree.get_children_subtrees(left_data, right_data);
7584

7685
handle_subtree(
7786
skeleton_tree,
7887
&mut next_subtrees,
7988
left_subtree,
89+
left_data,
8090
should_fetch_modified_leaves,
8191
);
8292
handle_subtree(
8393
skeleton_tree,
8494
&mut next_subtrees,
8595
right_subtree,
96+
right_data,
8697
should_fetch_modified_leaves,
8798
)
8899
}
89100
// Edge node.
90-
NodeData::<L, HashOutput>::Edge(EdgeData { bottom_data, path_to_bottom }) => {
91-
skeleton_tree
92-
.nodes
93-
.insert(subtree.root_index, OriginalSkeletonNode::Edge(path_to_bottom));
94-
if subtree.is_unmodified() {
95-
skeleton_tree.nodes.insert(
96-
path_to_bottom.bottom_index(subtree.root_index),
97-
OriginalSkeletonNode::UnmodifiedSubTree(bottom_data),
98-
);
99-
continue;
100-
}
101+
NodeData::Edge(EdgeData { bottom_data, path_to_bottom }) => {
102+
skeleton_tree.nodes.insert(
103+
subtree.get_root_index(),
104+
OriginalSkeletonNode::Edge(path_to_bottom),
105+
);
106+
101107
// Parse bottom.
102108
let (bottom_subtree, previously_empty_leaves_indices) =
103109
subtree.get_bottom_subtree(&path_to_bottom, bottom_data);
110+
111+
if subtree.is_unmodified() {
112+
if !SubTree::should_traverse_unmodified_children() {
113+
skeleton_tree.nodes.insert(
114+
path_to_bottom.bottom_index(subtree.get_root_index()),
115+
OriginalSkeletonNode::UnmodifiedSubTree(
116+
SubTree::unmodified_child_hash(bottom_data).unwrap(),
117+
),
118+
);
119+
} else {
120+
// With index layout we need to traverse an unmodified bottom node.
121+
next_subtrees.push(bottom_subtree)
122+
}
123+
continue;
124+
}
125+
104126
if let Some(ref mut leaves) = previous_leaves {
105127
leaves.extend(
106128
previously_empty_leaves_indices
@@ -119,23 +141,28 @@ async fn fetch_nodes<'a, L: Leaf>(
119141
skeleton_tree,
120142
&mut next_subtrees,
121143
bottom_subtree,
144+
bottom_data,
122145
should_fetch_modified_leaves,
123146
);
124147
}
125148
// Leaf node.
126149
NodeData::Leaf(previous_leaf) => {
127150
if subtree.is_unmodified() {
128-
warn!("Unexpectedly deserialized leaf sibling.")
151+
skeleton_tree.nodes.insert(
152+
subtree.get_root_index(),
153+
OriginalSkeletonNode::UnmodifiedSubTree(filled_root.hash),
154+
);
129155
} else {
156+
let root_index = subtree.get_root_index();
130157
// Modified leaf.
131158
if config.compare_modified_leaves()
132-
&& L::compare(leaf_modifications, &subtree.root_index, &previous_leaf)?
159+
&& L::compare(leaf_modifications, &root_index, &previous_leaf)?
133160
{
134-
log_trivial_modification!(subtree.root_index, previous_leaf);
161+
log_trivial_modification!(root_index, previous_leaf);
135162
}
136163
// If previous values of modified leaves are requested, add this leaf.
137164
if let Some(ref mut leaves) = previous_leaves {
138-
leaves.insert(subtree.root_index, previous_leaf);
165+
leaves.insert(root_index, previous_leaf);
139166
}
140167
}
141168
}
@@ -166,9 +193,9 @@ pub async fn create_original_skeleton_tree<'a, L: Leaf>(
166193
)?;
167194
return Ok(OriginalSkeletonTreeImpl::create_empty(sorted_leaf_indices));
168195
}
169-
let main_subtree = FactsSubTree::create(sorted_leaf_indices, NodeIndex::ROOT, root_hash);
196+
let main_subtree = FactsSubTree { sorted_leaf_indices, root_index: NodeIndex::ROOT, root_hash };
170197
let mut skeleton_tree = OriginalSkeletonTreeImpl { nodes: HashMap::new(), sorted_leaf_indices };
171-
fetch_nodes::<L>(
198+
fetch_nodes::<L, FactsNodeLayout, FactsSubTree<'a>>(
172199
&mut skeleton_tree,
173200
vec![main_subtree],
174201
storage,
@@ -202,7 +229,7 @@ pub async fn create_original_skeleton_tree_and_get_previous_leaves<'a, L: Leaf>(
202229
let main_subtree = FactsSubTree { sorted_leaf_indices, root_index: NodeIndex::ROOT, root_hash };
203230
let mut skeleton_tree = OriginalSkeletonTreeImpl { nodes: HashMap::new(), sorted_leaf_indices };
204231
let mut leaves = HashMap::new();
205-
fetch_nodes::<L>(
232+
fetch_nodes::<L, FactsNodeLayout, FactsSubTree<'a>>(
206233
&mut skeleton_tree,
207234
vec![main_subtree],
208235
storage,
@@ -237,19 +264,40 @@ pub async fn get_leaves<'a, L: Leaf>(
237264

238265
/// Handles a subtree referred by an edge or a binary node. Decides whether we deserialize the
239266
/// referred subtree or not.
240-
fn handle_subtree<'a>(
267+
fn handle_subtree<'a, SubTree: SubTreeTrait<'a>>(
241268
skeleton_tree: &mut OriginalSkeletonTreeImpl<'a>,
242-
next_subtrees: &mut Vec<FactsSubTree<'a>>,
243-
subtree: FactsSubTree<'a>,
269+
next_subtrees: &mut Vec<SubTree>,
270+
subtree: SubTree,
271+
subtree_data: SubTree::NodeData,
244272
should_fetch_modified_leaves: bool,
245273
) {
246-
if !subtree.is_leaf() || (should_fetch_modified_leaves && !subtree.is_unmodified()) {
274+
let is_leaf = subtree.is_leaf();
275+
let is_unmodified = subtree.is_unmodified();
276+
277+
// 1. Internal node → always traverse.
278+
if !is_leaf {
279+
next_subtrees.push(subtree);
280+
return;
281+
}
282+
283+
// 2. Modified leaf.
284+
if !is_unmodified {
285+
if should_fetch_modified_leaves {
286+
next_subtrees.push(subtree);
287+
}
288+
return;
289+
}
290+
291+
// 3. Unmodified leaf sibling.
292+
if !SubTree::should_traverse_unmodified_children() {
293+
skeleton_tree.nodes.insert(
294+
subtree.get_root_index(),
295+
OriginalSkeletonNode::UnmodifiedSubTree(
296+
SubTree::unmodified_child_hash(subtree_data).unwrap(),
297+
),
298+
);
299+
} else {
247300
next_subtrees.push(subtree);
248-
} else if subtree.is_unmodified() {
249-
// Leaf sibling.
250-
skeleton_tree
251-
.nodes
252-
.insert(subtree.root_index, OriginalSkeletonNode::UnmodifiedSubTree(subtree.root_hash));
253301
}
254302
}
255303

crates/starknet_committer/src/db/facts_db/db.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,27 @@ use std::collections::HashMap;
22

33
use starknet_api::core::ContractAddress;
44
use starknet_api::hash::HashOutput;
5-
use starknet_patricia::patricia_merkle_tree::node_data::leaf::LeafModifications;
5+
use starknet_patricia::patricia_merkle_tree::filled_tree::node_serde::FactNodeDeserializationContext;
6+
use starknet_patricia::patricia_merkle_tree::node_data::leaf::{Leaf, LeafModifications};
67
use starknet_patricia::patricia_merkle_tree::original_skeleton_tree::tree::OriginalSkeletonTreeImpl;
78
use starknet_patricia::patricia_merkle_tree::types::{NodeIndex, SortedLeafIndices};
8-
use starknet_patricia_storage::db_object::EmptyKeyContext;
9+
use starknet_patricia_storage::db_object::{DBObject, EmptyKeyContext};
10+
use starknet_patricia_storage::errors::DeserializationError;
911
use starknet_patricia_storage::map_storage::MapStorage;
10-
use starknet_patricia_storage::storage_trait::Storage;
12+
use starknet_patricia_storage::storage_trait::{DbValue, Storage};
1113

1214
use crate::block_committer::input::{
1315
contract_address_into_node_index,
1416
Config,
1517
ConfigImpl,
1618
StarknetStorageValue,
1719
};
20+
use crate::db::db_layout::NodeLayout;
1821
use crate::db::facts_db::create_facts_tree::{
1922
create_original_skeleton_tree,
2023
create_original_skeleton_tree_and_get_previous_leaves,
2124
};
25+
use crate::db::facts_db::types::FactDbFilledNode;
2226
use crate::db::forest_trait::{ForestReader, ForestWriter};
2327
use crate::forest::filled_forest::FilledForest;
2428
use crate::forest::forest_errors::{ForestError, ForestResult};
@@ -31,6 +35,18 @@ use crate::patricia_merkle_tree::tree::{
3135
};
3236
use crate::patricia_merkle_tree::types::CompiledClassHash;
3337

38+
pub struct FactsNodeLayout {}
39+
40+
impl<L: Leaf> NodeLayout<L> for FactsNodeLayout {
41+
type ChildData = HashOutput;
42+
type DeserializationContext = FactNodeDeserializationContext;
43+
fn deserialize_node(
44+
value: &DbValue,
45+
deserialize_context: &Self::DeserializationContext,
46+
) -> Result<FactDbFilledNode<L>, DeserializationError> {
47+
DBObject::deserialize(value, deserialize_context)
48+
}
49+
}
3450
pub struct FactsDb<S: Storage> {
3551
// TODO(Yoav): Define StorageStats trait and impl it here. Then, make the storage field
3652
// private.

crates/starknet_committer/src/db/facts_db/traversal.rs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::collections::HashMap;
22

33
use starknet_api::hash::HashOutput;
44
use starknet_patricia::patricia_merkle_tree::filled_tree::node::FilledNode;
5-
use starknet_patricia::patricia_merkle_tree::filled_tree::node_serde::FactNodeDeserializationContext;
65
use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{
76
NodeData,
87
Preimage,
@@ -11,43 +10,41 @@ use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{
1110
use starknet_patricia::patricia_merkle_tree::node_data::leaf::Leaf;
1211
use starknet_patricia::patricia_merkle_tree::traversal::{SubTreeTrait, TraversalResult};
1312
use starknet_patricia::patricia_merkle_tree::types::{NodeIndex, SortedLeafIndices};
14-
use starknet_patricia_storage::db_object::{DBObject, HasStaticPrefix};
13+
use starknet_patricia_storage::db_object::HasStaticPrefix;
1514
use starknet_patricia_storage::errors::StorageError;
1615
use starknet_patricia_storage::storage_trait::{create_db_key, DbKey, Storage};
1716

17+
use crate::db::db_layout::NodeLayout;
18+
use crate::db::facts_db::db::FactsNodeLayout;
1819
use crate::db::facts_db::types::FactsSubTree;
1920

2021
#[cfg(test)]
2122
#[path = "traversal_test.rs"]
2223
pub mod traversal_test;
2324

2425
// TODO(Aviv, 17/07/2024): Split between storage prefix implementation and function logic.
25-
pub async fn calculate_subtrees_roots<'a, L: Leaf>(
26-
subtrees: &[FactsSubTree<'a>],
26+
pub async fn get_roots_from_storage<'a, L: Leaf, Layout: NodeLayout<L>>(
27+
subtrees: &[impl SubTreeTrait<
28+
'a,
29+
NodeData = Layout::ChildData,
30+
NodeContext = Layout::DeserializationContext,
31+
>],
2732
storage: &mut impl Storage,
2833
key_context: &<L as HasStaticPrefix>::KeyContext,
29-
) -> TraversalResult<Vec<FilledNode<L>>> {
34+
) -> TraversalResult<Vec<FilledNode<L, Layout::ChildData>>> {
3035
let mut subtrees_roots = vec![];
3136
let db_keys: Vec<DbKey> = subtrees
3237
.iter()
3338
.map(|subtree| {
34-
create_db_key(
35-
subtree.get_root_prefix::<L>(key_context),
36-
&subtree.root_hash.0.to_bytes_be(),
37-
)
39+
create_db_key(subtree.get_root_prefix::<L>(key_context), &subtree.get_root_suffix())
3840
})
3941
.collect();
4042

4143
let db_vals = storage.mget(&db_keys.iter().collect::<Vec<&DbKey>>()).await?;
4244
for ((subtree, optional_val), db_key) in subtrees.iter().zip(db_vals.iter()).zip(db_keys) {
4345
let Some(val) = optional_val else { Err(StorageError::MissingKey(db_key))? };
44-
subtrees_roots.push(FilledNode::deserialize(
45-
val,
46-
&FactNodeDeserializationContext {
47-
is_leaf: subtree.is_leaf(),
48-
node_hash: subtree.root_hash,
49-
},
50-
)?)
46+
let filled_node = Layout::deserialize_node(val, &subtree.get_root_context())?;
47+
subtrees_roots.push(filled_node);
5148
}
5249
Ok(subtrees_roots)
5350
}
@@ -100,7 +97,8 @@ pub(crate) async fn fetch_patricia_paths_inner<'a, L: Leaf>(
10097
let mut next_subtrees = Vec::new();
10198
while !current_subtrees.is_empty() {
10299
let filled_roots =
103-
calculate_subtrees_roots::<L>(&current_subtrees, storage, key_context).await?;
100+
get_roots_from_storage::<L, FactsNodeLayout>(&current_subtrees, storage, key_context)
101+
.await?;
104102
for (filled_root, subtree) in filled_roots.into_iter().zip(current_subtrees.iter()) {
105103
// Always insert root.
106104
// No need to insert an unmodified node (which is not the root), because its parent is

0 commit comments

Comments
 (0)