@@ -3,8 +3,10 @@ use std::collections::HashMap;
33use ethnum:: U256 ;
44use pretty_assertions:: assert_eq;
55use rstest:: rstest;
6+ use rstest_reuse:: { apply, template} ;
67use serde:: Deserialize ;
78use starknet_api:: hash:: HashOutput ;
9+ use starknet_patricia:: db_layout:: NodeLayout ;
810use 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} ;
2830use 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 } ;
3032use starknet_patricia_storage:: db_object:: EmptyKeyContext ;
3133use starknet_patricia_storage:: map_storage:: MapStorage ;
3234use starknet_patricia_storage:: storage_trait:: { DbHashMap , DbKey , DbValue } ;
3335use starknet_types_core:: felt:: Felt ;
3436use starknet_types_core:: hash:: Pedersen ;
3537
36- use super :: fetch_patricia_paths_inner;
3738use crate :: db:: facts_db:: types:: FactsSubTree ;
3839use 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
4045fn 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}
0 commit comments