Skip to content

Commit e91d422

Browse files
committed
starknet_committer,starknet_patricia: layout-dependent FilledTree serialization
1 parent a3bd7c3 commit e91d422

File tree

9 files changed

+117
-80
lines changed

9 files changed

+117
-80
lines changed

crates/starknet_committer/src/db/external_test_utils.rs

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

33
use serde_json::json;
44
use starknet_api::hash::HashOutput;
5+
use starknet_patricia::db_layout::NodeLayoutFor;
56
use starknet_patricia::patricia_merkle_tree::filled_tree::tree::{FilledTree, FilledTreeImpl};
67
use starknet_patricia::patricia_merkle_tree::node_data::leaf::{
78
Leaf,
@@ -18,29 +19,28 @@ use starknet_patricia::patricia_merkle_tree::updated_skeleton_tree::tree::{
1819
use starknet_patricia_storage::db_object::HasStaticPrefix;
1920
use starknet_patricia_storage::map_storage::MapStorage;
2021

21-
use crate::db::facts_db::db::FactsNodeLayout;
2222
use crate::db::trie_traversal::create_original_skeleton_tree;
2323

24-
// TODO(Ariel, 14/12/2025): make this generic over the layout.
25-
pub async fn tree_computation_flow<L, TH>(
24+
pub async fn tree_computation_flow<L, Layout, TH>(
2625
leaf_modifications: LeafModifications<L>,
2726
storage: &mut MapStorage,
2827
root_hash: HashOutput,
2928
config: impl OriginalSkeletonTreeConfig,
30-
key_context: &<L as HasStaticPrefix>::KeyContext,
29+
key_context: &<Layout::DbLeaf as HasStaticPrefix>::KeyContext,
3130
) -> FilledTreeImpl<L>
3231
where
3332
TH: TreeHashFunction<L> + 'static,
3433
L: Leaf + 'static,
34+
Layout: NodeLayoutFor<L> + 'static,
3535
{
3636
let mut sorted_leaf_indices: Vec<NodeIndex> = leaf_modifications.keys().copied().collect();
3737
let sorted_leaf_indices = SortedLeafIndices::new(&mut sorted_leaf_indices);
38-
let mut original_skeleton = create_original_skeleton_tree::<L, FactsNodeLayout>(
38+
let mut original_skeleton = create_original_skeleton_tree::<Layout::DbLeaf, Layout>(
3939
storage,
4040
root_hash,
4141
sorted_leaf_indices,
4242
&config,
43-
&leaf_modifications,
43+
&leaf_modifications.iter().map(|(k, v)| (*k, v.clone().into())).collect(),
4444
None,
4545
key_context,
4646
)
@@ -64,27 +64,36 @@ where
6464
)
6565
.expect("Failed to create the updated skeleton tree");
6666

67-
FilledTreeImpl::<L>::create_with_existing_leaves::<TH>(updated_skeleton, leaf_modifications)
67+
FilledTreeImpl::create_with_existing_leaves::<TH>(updated_skeleton, leaf_modifications)
6868
.await
6969
.expect("Failed to create the filled tree")
7070
}
7171

72-
pub async fn single_tree_flow_test<L: Leaf + 'static, TH: TreeHashFunction<L> + 'static>(
72+
pub async fn single_tree_flow_test<
73+
L: Leaf + 'static,
74+
Layout: for<'a> NodeLayoutFor<L> + 'static,
75+
TH: TreeHashFunction<L> + 'static,
76+
>(
7377
leaf_modifications: LeafModifications<L>,
7478
storage: &mut MapStorage,
7579
root_hash: HashOutput,
7680
config: impl OriginalSkeletonTreeConfig,
77-
key_context: &<L as HasStaticPrefix>::KeyContext,
81+
key_context: &<Layout::DbLeaf as HasStaticPrefix>::KeyContext,
7882
) -> String {
7983
// Move from leaf number to actual index.
8084
let leaf_modifications = leaf_modifications
8185
.into_iter()
8286
.map(|(k, v)| (NodeIndex::FIRST_LEAF + k, v))
83-
.collect::<LeafModifications<L>>();
87+
.collect::<LeafModifications<_>>();
8488

85-
let filled_tree =
86-
tree_computation_flow::<L, TH>(leaf_modifications, storage, root_hash, config, key_context)
87-
.await;
89+
let filled_tree: FilledTreeImpl<L> = tree_computation_flow::<L, Layout, TH>(
90+
leaf_modifications,
91+
storage,
92+
root_hash,
93+
config,
94+
key_context,
95+
)
96+
.await;
8897

8998
let hash_result = filled_tree.get_root_hash();
9099

@@ -93,7 +102,7 @@ pub async fn single_tree_flow_test<L: Leaf + 'static, TH: TreeHashFunction<L> +
93102
let json_hash = &json!(hash_result.0.to_hex_string());
94103
result_map.insert("root_hash", json_hash);
95104
// Serlialize the storage modifications.
96-
let json_storage = &json!(filled_tree.serialize(key_context).unwrap());
105+
let json_storage = &json!(filled_tree.serialize::<Layout>(key_context).unwrap());
97106
result_map.insert("storage_changes", json_storage);
98107
serde_json::to_string(&result_map).expect("serialization failed")
99108
}

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

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,16 @@ use starknet_api::hash::HashOutput;
66
use starknet_patricia::db_layout::{NodeLayout, NodeLayoutFor};
77
use starknet_patricia::patricia_merkle_tree::filled_tree::node::{FactDbFilledNode, FilledNode};
88
use starknet_patricia::patricia_merkle_tree::filled_tree::node_serde::FactNodeDeserializationContext;
9-
use starknet_patricia::patricia_merkle_tree::filled_tree::tree::FilledTree;
109
use starknet_patricia::patricia_merkle_tree::node_data::leaf::{Leaf, LeafModifications};
1110
use starknet_patricia::patricia_merkle_tree::types::NodeIndex;
12-
use starknet_patricia_storage::db_object::{DBObject, EmptyKeyContext, HasStaticPrefix};
11+
use starknet_patricia_storage::db_object::{DBObject, HasStaticPrefix};
1312
use starknet_patricia_storage::errors::SerializationResult;
1413
use starknet_patricia_storage::map_storage::MapStorage;
1514
use starknet_patricia_storage::storage_trait::{DbHashMap, DbKey, Storage};
1615

1716
use crate::block_committer::input::{ReaderConfig, StarknetStorageValue};
1817
use crate::db::facts_db::types::{FactsDbInitialRead, FactsSubTree};
19-
use crate::db::forest_trait::{read_forest, ForestReader, ForestWriter};
18+
use crate::db::forest_trait::{read_forest, serialize_forest, ForestReader, ForestWriter};
2019
use crate::forest::filled_forest::FilledForest;
2120
use crate::forest::forest_errors::ForestResult;
2221
use crate::forest::original_skeleton_forest::{ForestSortedIndices, OriginalSkeletonForest};
@@ -111,18 +110,7 @@ impl<S: Storage> ForestReader<FactsDbInitialRead> for FactsDb<S> {
111110
#[async_trait]
112111
impl<S: Storage> ForestWriter for FactsDb<S> {
113112
fn serialize_forest(filled_forest: &FilledForest) -> SerializationResult<DbHashMap> {
114-
let mut serialized_forest = DbHashMap::new();
115-
116-
// Storage tries.
117-
for (contract_address, tree) in &filled_forest.storage_tries {
118-
serialized_forest.extend(tree.serialize(contract_address)?);
119-
}
120-
121-
// Contracts and classes tries.
122-
serialized_forest.extend(filled_forest.contracts_trie.serialize(&EmptyKeyContext)?);
123-
serialized_forest.extend(filled_forest.classes_trie.serialize(&EmptyKeyContext)?);
124-
125-
Ok(serialized_forest)
113+
serialize_forest::<FactsNodeLayout>(filled_forest)
126114
}
127115

128116
async fn write_updates(&mut self, updates: DbHashMap) -> usize {

crates/starknet_committer/src/db/forest_trait.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use async_trait::async_trait;
44
use serde::{Deserialize, Serialize};
55
use starknet_api::core::ContractAddress;
66
use starknet_patricia::db_layout::NodeLayoutFor;
7+
use starknet_patricia::patricia_merkle_tree::filled_tree::tree::FilledTree;
78
use starknet_patricia::patricia_merkle_tree::node_data::leaf::LeafModifications;
89
use starknet_patricia::patricia_merkle_tree::types::NodeIndex;
910
use starknet_patricia_storage::db_object::{EmptyKeyContext, HasStaticPrefix};
@@ -118,6 +119,36 @@ where
118119
))
119120
}
120121

122+
/// Helper function containing layout-common write logic.
123+
pub(crate) fn serialize_forest<Layout>(
124+
filled_forest: &FilledForest,
125+
) -> SerializationResult<DbHashMap>
126+
where
127+
Layout: NodeLayoutFor<StarknetStorageValue>
128+
+ NodeLayoutFor<ContractState>
129+
+ NodeLayoutFor<CompiledClassHash>,
130+
<Layout as NodeLayoutFor<StarknetStorageValue>>::DbLeaf:
131+
HasStaticPrefix<KeyContext = ContractAddress>,
132+
<Layout as NodeLayoutFor<ContractState>>::DbLeaf: HasStaticPrefix<KeyContext = EmptyKeyContext>,
133+
<Layout as NodeLayoutFor<CompiledClassHash>>::DbLeaf:
134+
HasStaticPrefix<KeyContext = EmptyKeyContext>,
135+
{
136+
let mut serialized_forest = DbHashMap::new();
137+
138+
// Storage tries.
139+
for (contract_address, tree) in &filled_forest.storage_tries {
140+
serialized_forest.extend(tree.serialize::<Layout>(contract_address)?);
141+
}
142+
143+
// Contracts trie.
144+
serialized_forest.extend(filled_forest.contracts_trie.serialize::<Layout>(&EmptyKeyContext)?);
145+
146+
// Classes trie.
147+
serialized_forest.extend(filled_forest.classes_trie.serialize::<Layout>(&EmptyKeyContext)?);
148+
149+
Ok(serialized_forest)
150+
}
151+
121152
#[async_trait]
122153
pub trait ForestWriter: Send {
123154
/// Serializes a filled forest into a hash map.

crates/starknet_committer/src/db/index_db/db.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@ use starknet_api::core::ContractAddress;
55
use starknet_api::hash::HashOutput;
66
use starknet_patricia::db_layout::{NodeLayout, NodeLayoutFor};
77
use starknet_patricia::patricia_merkle_tree::filled_tree::node::FilledNode;
8-
use starknet_patricia::patricia_merkle_tree::filled_tree::tree::FilledTree;
98
use starknet_patricia::patricia_merkle_tree::node_data::leaf::{Leaf, LeafModifications};
109
use starknet_patricia::patricia_merkle_tree::types::NodeIndex;
1110
use starknet_patricia::patricia_merkle_tree::updated_skeleton_tree::hash_function::TreeHashFunction;
12-
use starknet_patricia_storage::db_object::{DBObject, EmptyKeyContext, HasStaticPrefix};
11+
use starknet_patricia_storage::db_object::{DBObject, HasStaticPrefix};
1312
use starknet_patricia_storage::errors::SerializationResult;
1413
use starknet_patricia_storage::storage_trait::{DbHashMap, DbKey, Storage};
1514

1615
use crate::block_committer::input::{ReaderConfig, StarknetStorageValue};
1716
use crate::db::facts_db::types::FactsDbInitialRead;
18-
use crate::db::forest_trait::{read_forest, ForestReader, ForestWriter};
17+
use crate::db::forest_trait::{read_forest, serialize_forest, ForestReader, ForestWriter};
1918
use crate::db::index_db::leaves::{
2019
IndexLayoutCompiledClassHash,
2120
IndexLayoutContractState,
@@ -114,17 +113,7 @@ impl<S: Storage> ForestReader<FactsDbInitialRead> for IndexDb<S> {
114113
#[async_trait]
115114
impl<S: Storage> ForestWriter for IndexDb<S> {
116115
fn serialize_forest(filled_forest: &FilledForest) -> SerializationResult<DbHashMap> {
117-
let mut serialized_forest = DbHashMap::new();
118-
119-
for (contract_address, tree) in &filled_forest.storage_tries {
120-
serialized_forest.extend(tree.serialize(contract_address)?);
121-
}
122-
123-
// Contracts and classes tries.
124-
serialized_forest.extend(filled_forest.contracts_trie.serialize(&EmptyKeyContext)?);
125-
serialized_forest.extend(filled_forest.classes_trie.serialize(&EmptyKeyContext)?);
126-
127-
Ok(serialized_forest)
116+
serialize_forest::<IndexNodeLayout>(filled_forest)
128117
}
129118

130119
async fn write_updates(&mut self, updates: DbHashMap) -> usize {

crates/starknet_committer_and_os_cli/benches/main.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
1313
use starknet_api::core::ContractAddress;
1414
use starknet_committer::block_committer::input::StarknetStorageValue;
1515
use starknet_committer::db::external_test_utils::tree_computation_flow;
16+
use starknet_committer::db::facts_db::db::FactsNodeLayout;
1617
use starknet_committer::hash_function::hash::TreeHashFunctionImpl;
1718
use starknet_committer::patricia_merkle_tree::tree::OriginalSkeletonTrieConfig;
1819
use starknet_committer_and_os_cli::committer_cli::commands::commit;
@@ -46,15 +47,17 @@ pub fn single_tree_flow_benchmark(criterion: &mut Criterion) {
4647
benchmark.iter_batched(
4748
|| leaf_modifications.clone(),
4849
|leaf_modifications_input| {
49-
runtime.block_on(
50-
tree_computation_flow::<StarknetStorageValue, TreeHashFunctionImpl>(
51-
leaf_modifications_input,
52-
&mut storage,
53-
root_hash,
54-
OriginalSkeletonTrieConfig::new_for_classes_or_storage_trie(false),
55-
&dummy_contract_address,
56-
),
57-
);
50+
runtime.block_on(tree_computation_flow::<
51+
StarknetStorageValue,
52+
FactsNodeLayout,
53+
TreeHashFunctionImpl,
54+
>(
55+
leaf_modifications_input,
56+
&mut storage,
57+
root_hash,
58+
OriginalSkeletonTrieConfig::new_for_classes_or_storage_trie(false),
59+
&dummy_contract_address,
60+
));
5861
},
5962
BatchSize::LargeInput,
6063
)

crates/starknet_committer_and_os_cli/src/committer_cli/tests/python_tests.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use starknet_committer::block_committer::input::{
1212
};
1313
use starknet_committer::block_committer::random_structs::DummyRandomValue;
1414
use starknet_committer::db::external_test_utils::single_tree_flow_test;
15+
use starknet_committer::db::facts_db::db::FactsNodeLayout;
1516
use starknet_committer::forest::filled_forest::FilledForest;
1617
use starknet_committer::hash_function::hash::{
1718
TreeHashFunctionImpl,
@@ -164,7 +165,11 @@ impl PythonTestRunner for CommitterPythonTestRunner {
164165
serde_json::from_str(Self::non_optional_input(input)?)?;
165166
// 2. Run the test.
166167
let dummy_contract_address = ContractAddress::from(0_u128);
167-
let output = single_tree_flow_test::<StarknetStorageValue, TreeHashFunctionImpl>(
168+
let output = single_tree_flow_test::<
169+
StarknetStorageValue,
170+
FactsNodeLayout,
171+
TreeHashFunctionImpl,
172+
>(
168173
leaf_modifications,
169174
&mut storage,
170175
root_hash,

crates/starknet_committer_and_os_cli/src/committer_cli/tests/regression_tests.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use serde_json::{Map, Value};
77
use starknet_api::core::ContractAddress;
88
use starknet_committer::block_committer::input::StarknetStorageValue;
99
use starknet_committer::db::external_test_utils::single_tree_flow_test;
10+
use starknet_committer::db::facts_db::db::FactsNodeLayout;
1011
use starknet_committer::hash_function::hash::TreeHashFunctionImpl;
1112
use starknet_committer::patricia_merkle_tree::tree::OriginalSkeletonTrieConfig;
1213
use tempfile::NamedTempFile;
@@ -108,14 +109,15 @@ pub async fn test_regression_single_tree() {
108109
let start = std::time::Instant::now();
109110
// Benchmark the single tree flow test.
110111
let dummy_contract_address = ContractAddress::from(0_u128);
111-
let output = single_tree_flow_test::<StarknetStorageValue, TreeHashFunctionImpl>(
112-
leaf_modifications,
113-
&mut storage,
114-
root_hash,
115-
OriginalSkeletonTrieConfig::new_for_classes_or_storage_trie(false),
116-
&dummy_contract_address,
117-
)
118-
.await;
112+
let output =
113+
single_tree_flow_test::<StarknetStorageValue, FactsNodeLayout, TreeHashFunctionImpl>(
114+
leaf_modifications,
115+
&mut storage,
116+
root_hash,
117+
OriginalSkeletonTrieConfig::new_for_classes_or_storage_trie(false),
118+
&dummy_contract_address,
119+
)
120+
.await;
119121
let execution_time = std::time::Instant::now() - start;
120122

121123
// Assert correctness of the output of the single tree flow test.

crates/starknet_patricia/src/patricia_merkle_tree/filled_tree/node.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ pub struct FilledNode<L: Leaf, ChildData> {
1414
// starknet_committer. This can happen after serialization of FilledTree is made generic in the
1515
// layout.
1616
pub type FactDbFilledNode<L> = FilledNode<L, HashOutput>;
17+
18+
/// A node in a filled tree, where all the hashes were computed. Used in the `FilledTree` trait.
19+
///
20+
/// While the same underlying type as `FactDbFilledNode`, this alias is not used in the context of
21+
/// the DB-representation of the node.
22+
pub type HashFilledNode<L> = FilledNode<L, HashOutput>;

0 commit comments

Comments
 (0)