Skip to content

Commit 4c3049b

Browse files
committed
starknet_committer: fetch patricia paths tests
1 parent 22de1c4 commit 4c3049b

4 files changed

Lines changed: 189 additions & 54 deletions

File tree

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

Lines changed: 177 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ use std::collections::HashMap;
33
use ethnum::U256;
44
use pretty_assertions::assert_eq;
55
use rstest::rstest;
6+
use rstest_reuse::{apply, template};
67
use serde::Deserialize;
78
use starknet_api::hash::HashOutput;
9+
use starknet_patricia::db_layout::NodeLayout;
810
use starknet_patricia::patricia_merkle_tree::external_test_utils::{
911
create_binary_entry,
1012
create_binary_entry_from_u128,
@@ -26,16 +28,19 @@ use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{
2628
PreimageMap,
2729
};
2830
use starknet_patricia::patricia_merkle_tree::traversal::SubTreeTrait;
29-
use starknet_patricia::patricia_merkle_tree::types::{SortedLeafIndices, SubTreeHeight};
31+
use starknet_patricia::patricia_merkle_tree::types::{NodeIndex, SortedLeafIndices, SubTreeHeight};
3032
use starknet_patricia_storage::db_object::EmptyKeyContext;
3133
use starknet_patricia_storage::map_storage::MapStorage;
3234
use starknet_patricia_storage::storage_trait::{DbHashMap, DbKey, DbValue};
3335
use starknet_types_core::felt::Felt;
3436
use starknet_types_core::hash::Pedersen;
3537

36-
use super::fetch_patricia_paths_inner;
3738
use crate::db::facts_db::types::FactsSubTree;
3839
use crate::db::facts_db::FactsNodeLayout;
40+
use crate::db::index_db::test_utils::traverse_and_convert;
41+
use crate::db::index_db::IndexNodeLayout;
42+
use crate::db::trie_traversal::fetch_patricia_paths_inner;
43+
use crate::hash_function::mock_hash::MockTreeHashFunction;
3944

4045
fn to_preimage_map(raw_preimages: HashMap<u32, Vec<u32>>) -> PreimageMap {
4146
raw_preimages
@@ -52,15 +57,12 @@ fn to_preimage_map(raw_preimages: HashMap<u32, Vec<u32>>) -> PreimageMap {
5257
.collect()
5358
}
5459

55-
async fn test_fetch_patricia_paths_inner_impl(
56-
storage: MapStorage,
57-
root_hash: HashOutput,
58-
leaf_indices: Vec<u128>,
60+
fn compute_expected_leaves(
61+
storage: &MapStorage,
62+
leaf_indices: &[u128],
5963
height: SubTreeHeight,
60-
expected_nodes: PreimageMap,
61-
) {
62-
let mut storage = storage;
63-
let expected_fetched_leaves = leaf_indices
64+
) -> HashMap<NodeIndex, MockLeaf> {
65+
leaf_indices
6466
.iter()
6567
.map(|&idx| {
6668
let leaf = if storage.0.contains_key(&create_leaf_patricia_key::<MockLeaf>(idx)) {
@@ -70,21 +72,57 @@ async fn test_fetch_patricia_paths_inner_impl(
7072
};
7173
(small_tree_index_to_full(U256::from(idx), height), leaf)
7274
})
73-
.collect::<HashMap<_, _>>();
75+
.collect()
76+
}
77+
78+
async fn convert_trie_to_index_storage(
79+
facts_storage: &mut MapStorage,
80+
root_hash: HashOutput,
81+
root_index: NodeIndex,
82+
) -> MapStorage {
83+
let mut index_storage = MapStorage(DbHashMap::new());
84+
let subtree = FactsSubTree::create(SortedLeafIndices::default(), root_index, root_hash);
85+
traverse_and_convert::<MockLeaf, MockLeaf, EmptyKeyContext>(
86+
facts_storage,
87+
&mut index_storage,
88+
subtree,
89+
&EmptyKeyContext,
90+
&mut None,
91+
false,
92+
)
93+
.await;
94+
index_storage
95+
}
96+
97+
/// Runs [`fetch_patricia_paths_inner`] on a small subtree of height `height`,
98+
/// then checks that every expected preimage appears in the result and that leaf values match.
99+
async fn fetch_patricia_paths_inner_tester<Layout>(
100+
mut storage: MapStorage,
101+
root_hash: HashOutput,
102+
leaf_indices: Vec<u128>,
103+
height: SubTreeHeight,
104+
expected_nodes: PreimageMap,
105+
expected_fetched_leaves: HashMap<NodeIndex, MockLeaf>,
106+
) where
107+
for<'a> Layout: NodeLayout<'a, MockLeaf>,
108+
{
109+
let root_index = small_tree_index_to_full(U256::ONE, height);
74110

75-
let mut leaf_indices = leaf_indices
111+
// map indices from a small tree to a height 251 tree
112+
let mut leaf_indices_full = leaf_indices
76113
.iter()
77114
.map(|&idx| small_tree_index_to_full(U256::from(idx), height))
78115
.collect::<Vec<_>>();
79-
let main_subtree = FactsSubTree::create(
80-
SortedLeafIndices::new(&mut leaf_indices),
81-
small_tree_index_to_full(U256::ONE, height),
82-
root_hash,
116+
117+
let main_subtree = <Layout as NodeLayout<'_, MockLeaf>>::SubTree::create(
118+
SortedLeafIndices::new(&mut leaf_indices_full),
119+
root_index,
120+
root_hash.into(),
83121
);
84122
let mut nodes = HashMap::new();
85123
let mut fetched_leaves = HashMap::new();
86124

87-
fetch_patricia_paths_inner::<MockLeaf, FactsNodeLayout>(
125+
fetch_patricia_paths_inner::<MockLeaf, Layout>(
88126
&mut storage,
89127
vec![main_subtree],
90128
&mut nodes,
@@ -106,7 +144,7 @@ async fn test_fetch_patricia_paths_inner_impl(
106144
assert_eq!(fetched_leaves, expected_fetched_leaves);
107145
}
108146

109-
#[tokio::test]
147+
#[template]
110148
#[rstest]
111149
// Some cases uses addition hash and others (generated in python) use pedersen hash.
112150
// For convenience, the leaves values are their NodeIndices.
@@ -547,15 +585,59 @@ async fn test_fetch_patricia_paths_inner_impl(
547585
})),
548586
]),
549587
)]
550-
async fn test_fetch_patricia_paths_inner(
588+
fn fetch_patricia_paths_inner_cases(
589+
#[case] _storage: MapStorage,
590+
#[case] _root_hash: HashOutput,
591+
#[case] _leaf_indices: Vec<u128>,
592+
#[case] _height: SubTreeHeight,
593+
#[case] _expected_nodes: PreimageMap,
594+
) {
595+
}
596+
597+
#[apply(fetch_patricia_paths_inner_cases)]
598+
#[rstest]
599+
#[tokio::test]
600+
async fn test_fetch_patricia_paths_inner_facts_layout(
551601
#[case] storage: MapStorage,
552602
#[case] root_hash: HashOutput,
553603
#[case] leaf_indices: Vec<u128>,
554604
#[case] height: SubTreeHeight,
555605
#[case] expected_nodes: PreimageMap,
556606
) {
557-
test_fetch_patricia_paths_inner_impl(storage, root_hash, leaf_indices, height, expected_nodes)
558-
.await;
607+
let expected_fetched_leaves = compute_expected_leaves(&storage, &leaf_indices, height);
608+
fetch_patricia_paths_inner_tester::<FactsNodeLayout>(
609+
storage,
610+
root_hash,
611+
leaf_indices,
612+
height,
613+
expected_nodes,
614+
expected_fetched_leaves,
615+
)
616+
.await;
617+
}
618+
619+
#[apply(fetch_patricia_paths_inner_cases)]
620+
#[rstest]
621+
#[tokio::test]
622+
async fn test_fetch_patricia_paths_inner_index_layout(
623+
#[case] mut storage: MapStorage,
624+
#[case] root_hash: HashOutput,
625+
#[case] leaf_indices: Vec<u128>,
626+
#[case] height: SubTreeHeight,
627+
#[case] expected_nodes: PreimageMap,
628+
) {
629+
let expected_fetched_leaves = compute_expected_leaves(&storage, &leaf_indices, height);
630+
let root_index = small_tree_index_to_full(U256::ONE, height);
631+
let index_storage = convert_trie_to_index_storage(&mut storage, root_hash, root_index).await;
632+
fetch_patricia_paths_inner_tester::<IndexNodeLayout<MockTreeHashFunction>>(
633+
index_storage,
634+
root_hash,
635+
leaf_indices,
636+
height,
637+
expected_nodes,
638+
expected_fetched_leaves,
639+
)
640+
.await;
559641
}
560642

561643
#[derive(Deserialize, Debug)]
@@ -568,21 +650,19 @@ struct TestPatriciaPathsInput {
568650
expected_nodes: HashMap<Felt, Vec<Felt>>,
569651
}
570652

571-
#[tokio::test]
572-
#[rstest]
573-
/// Test cases generated using Python `PatriciaTree.update()`.
574-
/// The files names indicate the tree height, number of initial leaves and number of modified
575-
/// leaves. The hash function used in the python tests is Pedersen.
576-
/// The leaves values are their NodeIndices.
577-
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_200_50.json"))]
578-
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_5_2.json"))]
579-
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_100_30.json"))]
580-
#[case(include_str!("../../../resources/fetch_patricia_paths_test_8_120_70.json"))]
581-
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_200_50.json"))]
582-
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_5_2.json"))]
583-
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_100_30.json"))]
584-
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_8_120_70.json"))]
585-
async fn test_fetch_patricia_paths_inner_from_json(#[case] input_data: &str) {
653+
/// Deserializes a Python-generated `PatriciaTree.update()`.
654+
///
655+
/// Returns, in order:
656+
/// 1. `MapStorage` — facts-db entries: inner nodes decoded from JSON preimages plus leaf rows for
657+
/// `initial_leaves`.
658+
/// 2. `Vec<u128>` — `modified_leaves_indices` shifted from Python bottom-layer-relative indices to
659+
/// absolute full-tree indices (each value plus `2^height`).
660+
/// 3. `HashOutput` — subtree root hash.
661+
/// 4. `SubTreeHeight` — tree height.
662+
/// 5. `PreimageMap` — expected fetched node preimages keyed by hash, for asserting traversal.
663+
fn parse_json_test_input(
664+
input_data: &str,
665+
) -> (MapStorage, Vec<u128>, HashOutput, SubTreeHeight, PreimageMap) {
586666
let input: TestPatriciaPathsInput = serde_json::from_str(input_data)
587667
.unwrap_or_else(|error| panic!("JSON was not well-formatted: {error:?}"));
588668

@@ -623,12 +703,71 @@ async fn test_fetch_patricia_paths_inner_from_json(#[case] input_data: &str) {
623703
})
624704
.collect();
625705

626-
test_fetch_patricia_paths_inner_impl(
706+
(
627707
MapStorage(DbHashMap::from(storage)),
628-
HashOutput(input.root_hash),
629708
leaf_indices,
709+
HashOutput(input.root_hash),
630710
SubTreeHeight::new(input.height),
631711
expected_nodes,
632712
)
713+
}
714+
715+
#[tokio::test]
716+
#[rstest]
717+
/// Test cases generated using Python `PatriciaTree.update()`.
718+
/// The files names indicate the tree height, number of initial leaves and number of modified
719+
/// leaves. The hash function used in the python tests is Pedersen.
720+
/// The leaves values are their NodeIndices.
721+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_200_50.json"))]
722+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_5_2.json"))]
723+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_100_30.json"))]
724+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_8_120_70.json"))]
725+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_200_50.json"))]
726+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_5_2.json"))]
727+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_100_30.json"))]
728+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_8_120_70.json"))]
729+
async fn test_fetch_patricia_paths_inner_from_json_facts_layout(#[case] input_data: &str) {
730+
let (storage, leaf_indices, root_hash, height, expected_nodes) =
731+
parse_json_test_input(input_data);
732+
let expected_fetched_leaves = compute_expected_leaves(&storage, &leaf_indices, height);
733+
fetch_patricia_paths_inner_tester::<FactsNodeLayout>(
734+
storage,
735+
root_hash,
736+
leaf_indices,
737+
height,
738+
expected_nodes,
739+
expected_fetched_leaves,
740+
)
741+
.await;
742+
}
743+
744+
#[tokio::test]
745+
#[rstest]
746+
/// Test cases generated using Python `PatriciaTree.update()`.
747+
/// The files names indicate the tree height, number of initial leaves and number of modified
748+
/// leaves. The hash function used in the python tests is Pedersen.
749+
/// The leaves values are their NodeIndices.
750+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_200_50.json"))]
751+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_5_2.json"))]
752+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_10_100_30.json"))]
753+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_8_120_70.json"))]
754+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_200_50.json"))]
755+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_5_2.json"))]
756+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_10_100_30.json"))]
757+
#[case(include_str!("../../../resources/fetch_patricia_paths_test_delete_leaves_8_120_70.json"))]
758+
async fn test_fetch_patricia_paths_inner_from_json_index_layout(#[case] input_data: &str) {
759+
let (mut storage, leaf_indices, root_hash, height, expected_nodes) =
760+
parse_json_test_input(input_data);
761+
let expected_fetched_leaves = compute_expected_leaves(&storage, &leaf_indices, height);
762+
let root_index = small_tree_index_to_full(U256::ONE, height);
763+
let index_storage = convert_trie_to_index_storage(&mut storage, root_hash, root_index).await;
764+
fetch_patricia_paths_inner_tester::<IndexNodeLayout<MockTreeHashFunction>>(
765+
index_storage,
766+
root_hash,
767+
leaf_indices,
768+
height,
769+
expected_nodes,
770+
expected_fetched_leaves,
771+
)
633772
.await;
634773
}

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
use rstest::rstest;
22
use rstest_reuse::apply;
3-
use starknet_api::hash::HashOutput;
43
use starknet_patricia::patricia_merkle_tree::external_test_utils::MockLeaf;
5-
use starknet_patricia::patricia_merkle_tree::node_data::inner_node::NodeData;
6-
use starknet_patricia::patricia_merkle_tree::updated_skeleton_tree::hash_function::TreeHashFunction;
74
use starknet_patricia_storage::db_object::EmptyKeyContext;
8-
use starknet_types_core::felt::Felt;
95

106
use crate::db::create_original_skeleton_tests::{
117
create_tree_cases,
@@ -16,16 +12,6 @@ use crate::db::index_db::test_utils::convert_facts_db_to_index_db;
1612
use crate::db::index_db::IndexNodeLayout;
1713
use crate::hash_function::mock_hash::MockTreeHashFunction;
1814

19-
impl TreeHashFunction<MockLeaf> for MockTreeHashFunction {
20-
fn compute_leaf_hash(leaf_data: &MockLeaf) -> HashOutput {
21-
HashOutput(leaf_data.0)
22-
}
23-
24-
fn compute_node_hash(_node_data: &NodeData<MockLeaf, HashOutput>) -> HashOutput {
25-
HashOutput(Felt::ZERO)
26-
}
27-
}
28-
2915
#[apply(create_tree_cases)]
3016
#[rstest]
3117
#[tokio::test]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ where
136136

137137
/// Recursively traverses a Facts-layout trie and converts each node to Index-layout.
138138
#[async_recursion]
139-
async fn traverse_and_convert<FactsLeaf, IndexLeaf, KeyContext>(
139+
pub(crate) async fn traverse_and_convert<FactsLeaf, IndexLeaf, KeyContext>(
140140
facts_storage: &mut MapStorage,
141141
index_layout_storage: &mut MapStorage,
142142
subtree: FactsSubTree<'async_recursion>,

crates/starknet_committer/src/hash_function/mock_hash.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use starknet_api::hash::HashOutput;
2-
use starknet_patricia::patricia_merkle_tree::external_test_utils::AdditionHash;
2+
use starknet_patricia::patricia_merkle_tree::external_test_utils::{AdditionHash, MockLeaf};
33
use starknet_patricia::patricia_merkle_tree::node_data::inner_node::NodeData;
44
use starknet_patricia::patricia_merkle_tree::updated_skeleton_tree::hash_function::TreeHashFunction;
55
use starknet_types_core::hash::StarkHash;
@@ -51,3 +51,13 @@ impl TreeHashFunction<IndexLayoutStarknetStorageValue> for MockTreeHashFunction
5151
Self::compute_node_hash_with_inner_hash_function::<PedersenHashFunction>(node_data)
5252
}
5353
}
54+
55+
impl TreeHashFunction<MockLeaf> for MockTreeHashFunction {
56+
fn compute_leaf_hash(leaf: &MockLeaf) -> HashOutput {
57+
HashOutput(leaf.0)
58+
}
59+
60+
fn compute_node_hash(node_data: &NodeData<MockLeaf, HashOutput>) -> HashOutput {
61+
Self::compute_node_hash_with_inner_hash_function::<PedersenHashFunction>(node_data)
62+
}
63+
}

0 commit comments

Comments
 (0)