Skip to content

Commit 7851196

Browse files
committed
starknet_committer: make fetch nodes depend on db layout
1 parent a9f08a7 commit 7851196

File tree

5 files changed

+181
-72
lines changed

5 files changed

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

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

Lines changed: 123 additions & 54 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,72 @@ 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();
68+
let should_fetch_modified_leaves =
69+
config.compare_modified_leaves() || previous_leaves.is_some();
5670
while !current_subtrees.is_empty() {
57-
let should_fetch_modified_leaves =
58-
config.compare_modified_leaves() || previous_leaves.is_some();
5971
let filled_roots =
60-
calculate_subtrees_roots::<L>(&current_subtrees, storage, key_context).await?;
61-
for (filled_root, subtree) in filled_roots.into_iter().zip(current_subtrees.iter()) {
72+
get_roots_from_storage::<L, Layout>(&current_subtrees, storage, key_context).await?;
73+
for (filled_root, subtree) in filled_roots.into_iter().zip(current_subtrees.into_iter()) {
74+
if subtree.is_unmodified() {
75+
handle_unmodified_subtree(skeleton_tree, &mut next_subtrees, filled_root, subtree);
76+
continue;
77+
}
78+
let root_index = subtree.get_root_index();
6279
match filled_root.data {
6380
// Binary node.
6481
NodeData::Binary(BinaryData { left_data, right_data }) => {
65-
if subtree.is_unmodified() {
66-
skeleton_tree.nodes.insert(
67-
subtree.root_index,
68-
OriginalSkeletonNode::UnmodifiedSubTree(filled_root.hash),
69-
);
70-
continue;
71-
}
72-
skeleton_tree.nodes.insert(subtree.root_index, OriginalSkeletonNode::Binary);
82+
skeleton_tree.nodes.insert(root_index, OriginalSkeletonNode::Binary);
7383
let (left_subtree, right_subtree) =
7484
subtree.get_children_subtrees(left_data, right_data);
7585

76-
handle_subtree(
86+
handle_child_subtree(
7787
skeleton_tree,
7888
&mut next_subtrees,
7989
left_subtree,
90+
left_data,
8091
should_fetch_modified_leaves,
8192
);
82-
handle_subtree(
93+
handle_child_subtree(
8394
skeleton_tree,
8495
&mut next_subtrees,
8596
right_subtree,
97+
right_data,
8698
should_fetch_modified_leaves,
8799
)
88100
}
89101
// Edge node.
90102
NodeData::Edge(EdgeData { bottom_data, path_to_bottom }) => {
91103
skeleton_tree
92104
.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-
}
105+
.insert(root_index, OriginalSkeletonNode::Edge(path_to_bottom));
106+
101107
// Parse bottom.
102108
let (bottom_subtree, previously_empty_leaves_indices) =
103109
subtree.get_bottom_subtree(&path_to_bottom, bottom_data);
110+
104111
if let Some(ref mut leaves) = previous_leaves {
105112
leaves.extend(
106113
previously_empty_leaves_indices
@@ -115,28 +122,25 @@ async fn fetch_nodes<'a, L: Leaf>(
115122
config,
116123
)?;
117124

118-
handle_subtree(
125+
handle_child_subtree(
119126
skeleton_tree,
120127
&mut next_subtrees,
121128
bottom_subtree,
129+
bottom_data,
122130
should_fetch_modified_leaves,
123131
);
124132
}
125133
// Leaf node.
126134
NodeData::Leaf(previous_leaf) => {
127-
if subtree.is_unmodified() {
128-
warn!("Unexpectedly deserialized leaf sibling.")
129-
} else {
130-
// Modified leaf.
131-
if config.compare_modified_leaves()
132-
&& L::compare(leaf_modifications, &subtree.root_index, &previous_leaf)?
133-
{
134-
log_trivial_modification!(subtree.root_index, previous_leaf);
135-
}
136-
// If previous values of modified leaves are requested, add this leaf.
137-
if let Some(ref mut leaves) = previous_leaves {
138-
leaves.insert(subtree.root_index, previous_leaf);
139-
}
135+
// Modified leaf.
136+
if config.compare_modified_leaves()
137+
&& L::compare(leaf_modifications, &root_index, &previous_leaf)?
138+
{
139+
log_trivial_modification!(root_index, previous_leaf);
140+
}
141+
// If previous values of modified leaves are requested, add this leaf.
142+
if let Some(ref mut leaves) = previous_leaves {
143+
leaves.insert(root_index, previous_leaf);
140144
}
141145
}
142146
}
@@ -168,7 +172,7 @@ pub async fn create_original_skeleton_tree<'a, L: Leaf>(
168172
}
169173
let main_subtree = FactsSubTree::create(sorted_leaf_indices, NodeIndex::ROOT, root_hash);
170174
let mut skeleton_tree = OriginalSkeletonTreeImpl { nodes: HashMap::new(), sorted_leaf_indices };
171-
fetch_nodes::<L>(
175+
fetch_nodes::<L, FactsNodeLayout>(
172176
&mut skeleton_tree,
173177
vec![main_subtree],
174178
storage,
@@ -202,7 +206,7 @@ pub async fn create_original_skeleton_tree_and_get_previous_leaves<'a, L: Leaf>(
202206
let main_subtree = FactsSubTree::create(sorted_leaf_indices, NodeIndex::ROOT, root_hash);
203207
let mut skeleton_tree = OriginalSkeletonTreeImpl { nodes: HashMap::new(), sorted_leaf_indices };
204208
let mut leaves = HashMap::new();
205-
fetch_nodes::<L>(
209+
fetch_nodes::<L, FactsNodeLayout>(
206210
&mut skeleton_tree,
207211
vec![main_subtree],
208212
storage,
@@ -238,21 +242,86 @@ pub async fn get_leaves<'a, L: Leaf>(
238242
Ok(previous_leaves)
239243
}
240244

245+
/// Adds the subtree root to the skeleton tree. If the root is an edge node, and
246+
/// `should_traverse_unmodified_child` is `Skip`, then the corresponding bottom node is also
247+
/// added to the skeleton. Otherwise, the bottom subtree is added to the next subtrees.
248+
fn handle_unmodified_subtree<'a, L: Leaf, SubTree: SubTreeTrait<'a>>(
249+
skeleton_tree: &mut OriginalSkeletonTreeImpl<'a>,
250+
next_subtrees: &mut Vec<SubTree>,
251+
filled_root: FilledNode<L, SubTree::NodeData>,
252+
subtree: SubTree,
253+
) where
254+
SubTree::NodeData: Copy,
255+
{
256+
// Sanity check.
257+
assert!(subtree.is_unmodified(), "Called `handle_unmodified_subtree` for a modified subtree.");
258+
259+
let root_index = subtree.get_root_index();
260+
261+
match filled_root.data {
262+
NodeData::Edge(EdgeData { bottom_data, path_to_bottom }) => {
263+
// Even if a subtree rooted at an edge node is unmodified, we still need an
264+
// `OriginalSkeletonNode::Edge` node in the skeleton in case we need to manipulate it
265+
// later (e.g. unify the edge node with an ancestor edge node).
266+
skeleton_tree.nodes.insert(root_index, OriginalSkeletonNode::Edge(path_to_bottom));
267+
match SubTree::should_traverse_unmodified_child(bottom_data) {
268+
UnmodifiedChildTraversal::Traverse => {
269+
let (bottom_subtree, _) =
270+
subtree.get_bottom_subtree(&path_to_bottom, bottom_data);
271+
next_subtrees.push(bottom_subtree);
272+
}
273+
UnmodifiedChildTraversal::Skip(unmodified_child_hash) => {
274+
skeleton_tree.nodes.insert(
275+
path_to_bottom.bottom_index(root_index),
276+
OriginalSkeletonNode::UnmodifiedSubTree(unmodified_child_hash),
277+
);
278+
}
279+
}
280+
}
281+
NodeData::Binary(_) | NodeData::Leaf(_) => {
282+
skeleton_tree
283+
.nodes
284+
.insert(root_index, OriginalSkeletonNode::UnmodifiedSubTree(filled_root.hash));
285+
}
286+
}
287+
}
288+
241289
/// Handles a subtree referred by an edge or a binary node. Decides whether we deserialize the
242-
/// referred subtree or not.
243-
fn handle_subtree<'a>(
290+
/// referred subtree or not, and if we should continue traversing the child's direction.
291+
fn handle_child_subtree<'a, SubTree: SubTreeTrait<'a>>(
244292
skeleton_tree: &mut OriginalSkeletonTreeImpl<'a>,
245-
next_subtrees: &mut Vec<FactsSubTree<'a>>,
246-
subtree: FactsSubTree<'a>,
293+
next_subtrees: &mut Vec<SubTree>,
294+
subtree: SubTree,
295+
subtree_data: SubTree::NodeData,
247296
should_fetch_modified_leaves: bool,
248297
) {
249-
if !subtree.is_leaf() || (should_fetch_modified_leaves && !subtree.is_unmodified()) {
298+
let is_modified = !subtree.is_unmodified();
299+
300+
// Internal node → always traverse.
301+
if !subtree.is_leaf() {
250302
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));
303+
return;
304+
}
305+
306+
// Modified leaf.
307+
if is_modified {
308+
if should_fetch_modified_leaves {
309+
next_subtrees.push(subtree);
310+
}
311+
return;
312+
}
313+
314+
// Unmodified leaf sibling.
315+
match SubTree::should_traverse_unmodified_child(subtree_data) {
316+
UnmodifiedChildTraversal::Traverse => {
317+
next_subtrees.push(subtree);
318+
}
319+
UnmodifiedChildTraversal::Skip(unmodified_child_hash) => {
320+
skeleton_tree.nodes.insert(
321+
subtree.get_root_index(),
322+
OriginalSkeletonNode::UnmodifiedSubTree(unmodified_child_hash),
323+
);
324+
}
256325
}
257326
}
258327

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

Lines changed: 19 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,21 @@ use crate::patricia_merkle_tree::tree::{
4043
};
4144
use crate::patricia_merkle_tree::types::CompiledClassHash;
4245

46+
/// Facts DB node layout.
47+
///
48+
/// In a facts DB, the storage keys are node hashes and the values are preimages. In particular,
49+
/// each nodes holds its child node hashes. In this layout, only once the parent is traversed we
50+
/// have the db keys of its children.
51+
pub struct FactsNodeLayout {}
52+
53+
impl<'a, L: Leaf> NodeLayout<'a, L> for FactsNodeLayout {
54+
type NodeData = HashOutput;
55+
56+
type DeserializationContext = FactNodeDeserializationContext;
57+
58+
type SubTree = FactsSubTree<'a>;
59+
}
60+
4361
pub struct FactsDb<S: Storage> {
4462
// TODO(Yoav): Define StorageStats trait and impl it here. Then, make the storage field
4563
// private.

0 commit comments

Comments
 (0)