Skip to content

Commit d711681

Browse files
committed
starknet_committer: make fetch nodes depend on db layout
1 parent 9d631f6 commit d711681

File tree

5 files changed

+135
-50
lines changed

5 files changed

+135
-50
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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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::patricia_merkle_tree::traversal::SubTreeTrait;
4+
use starknet_patricia_storage::db_object::DBObject;
5+
6+
/// A trait that specifies the trie db layout.
7+
///
8+
/// The layout determines:
9+
/// [NodeData]: additional data that a node stores about its children.
10+
/// [DeserializationContext]: the context needed to deserialize the node from a raw [DbValue].
11+
/// [SubTree]: the type of the subtree that is used to traverse the trie.
12+
pub trait NodeLayout<'a, L: Leaf>
13+
where
14+
FilledNode<L, Self::NodeData>: DBObject<DeserializeContext = Self::DeserializationContext>,
15+
{
16+
type NodeData: Copy;
17+
type DeserializationContext;
18+
type SubTree: SubTreeTrait<
19+
'a,
20+
NodeData = Self::NodeData,
21+
NodeDeserializeContext = Self::DeserializationContext,
22+
>;
23+
}

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

Lines changed: 83 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::collections::HashMap;
33
use std::fmt::Debug;
44

55
use starknet_api::hash::HashOutput;
6+
use starknet_patricia::patricia_merkle_tree::filled_tree::node::FilledNode;
67
use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{
78
BinaryData,
89
EdgeData,
@@ -18,13 +19,15 @@ use starknet_patricia::patricia_merkle_tree::original_skeleton_tree::tree::{
1819
OriginalSkeletonTreeImpl,
1920
OriginalSkeletonTreeResult,
2021
};
21-
use starknet_patricia::patricia_merkle_tree::traversal::SubTreeTrait;
22+
use starknet_patricia::patricia_merkle_tree::traversal::{SubTreeTrait, UnmodifiedChildTraversal};
2223
use starknet_patricia::patricia_merkle_tree::types::{NodeIndex, SortedLeafIndices};
23-
use starknet_patricia_storage::db_object::HasStaticPrefix;
24+
use starknet_patricia_storage::db_object::{DBObject, HasStaticPrefix};
2425
use starknet_patricia_storage::storage_trait::Storage;
2526
use tracing::warn;
2627

27-
use crate::db::facts_db::traversal::calculate_subtrees_roots;
28+
use crate::db::db_layout::NodeLayout;
29+
use crate::db::facts_db::db::FactsNodeLayout;
30+
use crate::db::facts_db::traversal::get_roots_from_storage;
2831
use crate::db::facts_db::types::FactsSubTree;
2932

3033
#[cfg(test)]
@@ -39,68 +42,90 @@ macro_rules! log_trivial_modification {
3942
}
4043

4144
/// Fetches the Patricia witnesses, required to build the original skeleton tree from storage.
45+
///
4246
/// Given a list of subtrees, traverses towards their leaves and fetches all non-empty,
4347
/// unmodified nodes. If `compare_modified_leaves` is set, function logs out a warning when
4448
/// encountering a trivial modification. Fills the previous leaf values if it is not none.
45-
async fn fetch_nodes<'a, L: Leaf>(
49+
///
50+
/// The function is generic over the DB layout (`Layout`), which controls the concrete node data
51+
/// (`Layout::NodeData`) and traversal strategy (via `Layout::SubTree`).
52+
async fn fetch_nodes<'a, L, Layout>(
4653
skeleton_tree: &mut OriginalSkeletonTreeImpl<'a>,
47-
subtrees: Vec<FactsSubTree<'a>>,
54+
subtrees: Vec<Layout::SubTree>,
4855
storage: &mut impl Storage,
4956
leaf_modifications: &LeafModifications<L>,
5057
config: &impl OriginalSkeletonTreeConfig<L>,
5158
mut previous_leaves: Option<&mut HashMap<NodeIndex, L>>,
5259
key_context: &<L as HasStaticPrefix>::KeyContext,
53-
) -> OriginalSkeletonTreeResult<()> {
60+
) -> OriginalSkeletonTreeResult<()>
61+
where
62+
L: Leaf,
63+
Layout: NodeLayout<'a, L>,
64+
FilledNode<L, Layout::NodeData>: DBObject<DeserializeContext = Layout::DeserializationContext>,
65+
{
5466
let mut current_subtrees = subtrees;
5567
let mut next_subtrees = Vec::new();
5668
while !current_subtrees.is_empty() {
5769
let should_fetch_modified_leaves =
5870
config.compare_modified_leaves() || previous_leaves.is_some();
5971
let filled_roots =
60-
calculate_subtrees_roots::<L>(&current_subtrees, storage, key_context).await?;
72+
get_roots_from_storage::<L, Layout>(&current_subtrees, storage, key_context).await?;
6173
for (filled_root, subtree) in filled_roots.into_iter().zip(current_subtrees.iter()) {
74+
let root_index = subtree.get_root_index();
6275
match filled_root.data {
6376
// Binary node.
6477
NodeData::Binary(BinaryData { left_data, right_data }) => {
6578
if subtree.is_unmodified() {
6679
skeleton_tree.nodes.insert(
67-
subtree.root_index,
80+
root_index,
6881
OriginalSkeletonNode::UnmodifiedSubTree(filled_root.hash),
6982
);
7083
continue;
7184
}
72-
skeleton_tree.nodes.insert(subtree.root_index, OriginalSkeletonNode::Binary);
85+
skeleton_tree.nodes.insert(root_index, OriginalSkeletonNode::Binary);
7386
let (left_subtree, right_subtree) =
7487
subtree.get_children_subtrees(left_data, right_data);
7588

7689
handle_subtree(
7790
skeleton_tree,
7891
&mut next_subtrees,
7992
left_subtree,
93+
left_data,
8094
should_fetch_modified_leaves,
8195
);
8296
handle_subtree(
8397
skeleton_tree,
8498
&mut next_subtrees,
8599
right_subtree,
100+
right_data,
86101
should_fetch_modified_leaves,
87102
)
88103
}
89104
// Edge node.
90105
NodeData::Edge(EdgeData { bottom_data, path_to_bottom }) => {
91106
skeleton_tree
92107
.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-
}
108+
.insert(root_index, OriginalSkeletonNode::Edge(path_to_bottom));
109+
101110
// Parse bottom.
102111
let (bottom_subtree, previously_empty_leaves_indices) =
103112
subtree.get_bottom_subtree(&path_to_bottom, bottom_data);
113+
114+
if subtree.is_unmodified() {
115+
match Layout::SubTree::should_traverse_unmodified_child(bottom_data) {
116+
UnmodifiedChildTraversal::Traverse => {
117+
next_subtrees.push(bottom_subtree);
118+
}
119+
UnmodifiedChildTraversal::Skip(unmodified_child_hash) => {
120+
skeleton_tree.nodes.insert(
121+
path_to_bottom.bottom_index(root_index),
122+
OriginalSkeletonNode::UnmodifiedSubTree(unmodified_child_hash),
123+
);
124+
}
125+
}
126+
continue;
127+
}
128+
104129
if let Some(ref mut leaves) = previous_leaves {
105130
leaves.extend(
106131
previously_empty_leaves_indices
@@ -119,23 +144,27 @@ async fn fetch_nodes<'a, L: Leaf>(
119144
skeleton_tree,
120145
&mut next_subtrees,
121146
bottom_subtree,
147+
bottom_data,
122148
should_fetch_modified_leaves,
123149
);
124150
}
125151
// Leaf node.
126152
NodeData::Leaf(previous_leaf) => {
127153
if subtree.is_unmodified() {
128-
warn!("Unexpectedly deserialized leaf sibling.")
154+
skeleton_tree.nodes.insert(
155+
root_index,
156+
OriginalSkeletonNode::UnmodifiedSubTree(filled_root.hash),
157+
);
129158
} else {
130159
// Modified leaf.
131160
if config.compare_modified_leaves()
132-
&& L::compare(leaf_modifications, &subtree.root_index, &previous_leaf)?
161+
&& L::compare(leaf_modifications, &root_index, &previous_leaf)?
133162
{
134-
log_trivial_modification!(subtree.root_index, previous_leaf);
163+
log_trivial_modification!(root_index, previous_leaf);
135164
}
136165
// If previous values of modified leaves are requested, add this leaf.
137166
if let Some(ref mut leaves) = previous_leaves {
138-
leaves.insert(subtree.root_index, previous_leaf);
167+
leaves.insert(root_index, previous_leaf);
139168
}
140169
}
141170
}
@@ -168,7 +197,7 @@ pub async fn create_original_skeleton_tree<'a, L: Leaf>(
168197
}
169198
let main_subtree = FactsSubTree::create(sorted_leaf_indices, NodeIndex::ROOT, root_hash);
170199
let mut skeleton_tree = OriginalSkeletonTreeImpl { nodes: HashMap::new(), sorted_leaf_indices };
171-
fetch_nodes::<L>(
200+
fetch_nodes::<L, FactsNodeLayout>(
172201
&mut skeleton_tree,
173202
vec![main_subtree],
174203
storage,
@@ -202,7 +231,7 @@ pub async fn create_original_skeleton_tree_and_get_previous_leaves<'a, L: Leaf>(
202231
let main_subtree = FactsSubTree::create(sorted_leaf_indices, NodeIndex::ROOT, root_hash);
203232
let mut skeleton_tree = OriginalSkeletonTreeImpl { nodes: HashMap::new(), sorted_leaf_indices };
204233
let mut leaves = HashMap::new();
205-
fetch_nodes::<L>(
234+
fetch_nodes::<L, FactsNodeLayout>(
206235
&mut skeleton_tree,
207236
vec![main_subtree],
208237
storage,
@@ -240,19 +269,41 @@ pub async fn get_leaves<'a, L: Leaf>(
240269

241270
/// Handles a subtree referred by an edge or a binary node. Decides whether we deserialize the
242271
/// referred subtree or not.
243-
fn handle_subtree<'a>(
272+
fn handle_subtree<'a, SubTree: SubTreeTrait<'a>>(
244273
skeleton_tree: &mut OriginalSkeletonTreeImpl<'a>,
245-
next_subtrees: &mut Vec<FactsSubTree<'a>>,
246-
subtree: FactsSubTree<'a>,
274+
next_subtrees: &mut Vec<SubTree>,
275+
subtree: SubTree,
276+
subtree_data: SubTree::NodeData,
247277
should_fetch_modified_leaves: bool,
248278
) {
249-
if !subtree.is_leaf() || (should_fetch_modified_leaves && !subtree.is_unmodified()) {
279+
let is_leaf = subtree.is_leaf();
280+
let is_modified = !subtree.is_unmodified();
281+
282+
// Internal node → always traverse.
283+
if !is_leaf {
250284
next_subtrees.push(subtree);
251-
} else if subtree.is_unmodified() {
252-
// Leaf sibling.
253-
skeleton_tree
254-
.nodes
255-
.insert(subtree.root_index, OriginalSkeletonNode::UnmodifiedSubTree(subtree.root_hash));
285+
return;
286+
}
287+
288+
// Modified leaf.
289+
if is_modified {
290+
if should_fetch_modified_leaves {
291+
next_subtrees.push(subtree);
292+
}
293+
return;
294+
}
295+
296+
// Unmodified leaf sibling.
297+
match SubTree::should_traverse_unmodified_child(subtree_data) {
298+
UnmodifiedChildTraversal::Traverse => {
299+
next_subtrees.push(subtree);
300+
}
301+
UnmodifiedChildTraversal::Skip(unmodified_child_hash) => {
302+
skeleton_tree.nodes.insert(
303+
subtree.get_root_index(),
304+
OriginalSkeletonNode::UnmodifiedSubTree(unmodified_child_hash),
305+
);
306+
}
256307
}
257308
}
258309

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use std::collections::HashMap;
33
use async_trait::async_trait;
44
use starknet_api::core::ContractAddress;
55
use starknet_api::hash::HashOutput;
6+
use starknet_patricia::patricia_merkle_tree::filled_tree::node_serde::FactNodeDeserializationContext;
67
use starknet_patricia::patricia_merkle_tree::filled_tree::tree::FilledTree;
7-
use starknet_patricia::patricia_merkle_tree::node_data::leaf::LeafModifications;
8+
use starknet_patricia::patricia_merkle_tree::node_data::leaf::{Leaf, LeafModifications};
89
use starknet_patricia::patricia_merkle_tree::original_skeleton_tree::tree::OriginalSkeletonTreeImpl;
910
use starknet_patricia::patricia_merkle_tree::types::{NodeIndex, SortedLeafIndices};
1011
use starknet_patricia_storage::db_object::EmptyKeyContext;
@@ -24,10 +25,12 @@ use crate::block_committer::input::{
2425
ReaderConfig,
2526
StarknetStorageValue,
2627
};
28+
use crate::db::db_layout::NodeLayout;
2729
use crate::db::facts_db::create_facts_tree::{
2830
create_original_skeleton_tree,
2931
create_original_skeleton_tree_and_get_previous_leaves,
3032
};
33+
use crate::db::facts_db::types::FactsSubTree;
3134
use crate::db::forest_trait::{ForestMetadata, ForestMetadataType, ForestReader, ForestWriter};
3235
use crate::forest::filled_forest::FilledForest;
3336
use crate::forest::forest_errors::{ForestError, ForestResult};
@@ -40,6 +43,16 @@ use crate::patricia_merkle_tree::tree::{
4043
};
4144
use crate::patricia_merkle_tree::types::CompiledClassHash;
4245

46+
pub struct FactsNodeLayout {}
47+
48+
impl<'a, L: Leaf> NodeLayout<'a, L> for FactsNodeLayout {
49+
type NodeData = HashOutput;
50+
51+
type DeserializationContext = FactNodeDeserializationContext;
52+
53+
type SubTree = FactsSubTree<'a>;
54+
}
55+
4356
pub struct FactsDb<S: Storage> {
4457
// TODO(Yoav): Define StorageStats trait and impl it here. Then, make the storage field
4558
// private.

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

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use std::collections::HashMap;
22

33
use starknet_api::hash::HashOutput;
4-
use starknet_patricia::patricia_merkle_tree::filled_tree::node::{FactDbFilledNode, FilledNode};
5-
use starknet_patricia::patricia_merkle_tree::filled_tree::node_serde::FactNodeDeserializationContext;
4+
use starknet_patricia::patricia_merkle_tree::filled_tree::node::FilledNode;
65
use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{
76
NodeData,
87
Preimage,
@@ -15,39 +14,36 @@ use starknet_patricia_storage::db_object::{DBObject, 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<'a, L>>(
27+
subtrees: &[Layout::SubTree],
2728
storage: &mut impl Storage,
2829
key_context: &<L as HasStaticPrefix>::KeyContext,
29-
) -> TraversalResult<Vec<FactDbFilledNode<L>>> {
30+
) -> TraversalResult<Vec<FilledNode<L, Layout::NodeData>>>
31+
where
32+
FilledNode<L, Layout::NodeData>: DBObject<DeserializeContext = Layout::DeserializationContext>,
33+
{
3034
let mut subtrees_roots = vec![];
3135
let db_keys: Vec<DbKey> = subtrees
3236
.iter()
3337
.map(|subtree| {
34-
create_db_key(
35-
subtree.get_root_prefix::<L>(key_context),
36-
&subtree.root_hash.0.to_bytes_be(),
37-
)
38+
create_db_key(subtree.get_root_prefix::<L>(key_context), &subtree.get_root_suffix())
3839
})
3940
.collect();
4041

4142
let db_vals = storage.mget(&db_keys.iter().collect::<Vec<&DbKey>>()).await?;
4243
for ((subtree, optional_val), db_key) in subtrees.iter().zip(db_vals.iter()).zip(db_keys) {
4344
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-
)?)
45+
let filled_node = DBObject::deserialize(val, &subtree.get_root_context())?;
46+
subtrees_roots.push(filled_node);
5147
}
5248
Ok(subtrees_roots)
5349
}
@@ -102,7 +98,8 @@ pub(crate) async fn fetch_patricia_paths_inner<'a, L: Leaf>(
10298
let mut next_subtrees = Vec::new();
10399
while !current_subtrees.is_empty() {
104100
let filled_roots =
105-
calculate_subtrees_roots::<L>(&current_subtrees, storage, key_context).await?;
101+
get_roots_from_storage::<L, FactsNodeLayout>(&current_subtrees, storage, key_context)
102+
.await?;
106103
for (filled_root, subtree) in filled_roots.into_iter().zip(current_subtrees.iter()) {
107104
match filled_root.data {
108105
// Binary node.

0 commit comments

Comments
 (0)