@@ -10,7 +10,13 @@ use apollo_committer_types::committer_types::{
1010use indexmap:: indexmap;
1111use starknet_api:: block:: BlockNumber ;
1212use starknet_api:: block_hash:: state_diff_hash:: calculate_state_diff_hash;
13- use starknet_api:: core:: { ClassHash , CompiledClassHash , ContractAddress , StateDiffCommitment } ;
13+ use starknet_api:: core:: {
14+ ClassHash ,
15+ CompiledClassHash ,
16+ ContractAddress ,
17+ StateDiffCommitment ,
18+ PATRICIA_KEY_UPPER_BOUND_FELT ,
19+ } ;
1420use starknet_api:: hash:: HashOutput ;
1521use starknet_api:: state:: ThinStateDiff ;
1622use starknet_committer:: block_committer:: input:: {
@@ -30,9 +36,17 @@ use starknet_committer::patricia_merkle_tree::types::{
3036 CompiledClassHash as CommitterCompiledClassHash ,
3137 StarknetForestProofs ,
3238} ;
39+ use starknet_patricia:: patricia_merkle_tree:: node_data:: inner_node:: {
40+ BinaryData ,
41+ EdgeData ,
42+ EdgePath ,
43+ EdgePathLength ,
44+ NodeData ,
45+ PathToBottom ,
46+ } ;
3347use starknet_patricia:: patricia_merkle_tree:: node_data:: leaf:: Leaf ;
3448use starknet_patricia:: patricia_merkle_tree:: storage_proof_verification:: verify_patricia_proof;
35- use starknet_patricia:: patricia_merkle_tree:: types:: NodeIndex ;
49+ use starknet_patricia:: patricia_merkle_tree:: types:: { NodeIndex , SubTreeHeight } ;
3650use starknet_patricia:: patricia_merkle_tree:: updated_skeleton_tree:: hash_function:: TreeHashFunction ;
3751
3852use crate :: committer:: committer_test:: { new_test_committer, ApolloTestCommitter } ;
@@ -359,3 +373,196 @@ async fn revert_removes_witnesses_and_digest() {
359373 assert_witnesses_and_digest_absent ( & mut committer, BlockNumber ( height_1) ) . await ;
360374 assert_eq ! ( committer. offset, BlockNumber ( height_1) ) ;
361375}
376+
377+ /// Flow overview:
378+ /// 1. Commit block 0 with three class leaves that form this Patricia topology:
379+ /// ```text
380+ /// R
381+ /// / \
382+ /// E F
383+ /// | |
384+ /// G |
385+ /// / \ \
386+ /// A B D
387+ /// ```
388+ /// 2. Commit block 1 via [crate::committer::Committer::read_paths_and_commit_block], deleting `D`
389+ /// and requesting witnesses only for the deleted key.
390+ /// 3. Assert the returned classes-trie proof contains node `E`.
391+ #[ tokio:: test]
392+ async fn test_bottom_of_new_edge_to_an_unmoidifed_subtree_is_present ( ) {
393+ // Set the two leftmost and the rightmost leaves.
394+ let class_hash_a = ClassHash ( 0_u64 . into ( ) ) ;
395+ let class_hash_b = ClassHash ( 1_u64 . into ( ) ) ;
396+ let class_hash_d = ClassHash ( PATRICIA_KEY_UPPER_BOUND_FELT - 1_u64 ) ;
397+
398+ let compiled_class_hash_a_felt = 100_u64 . into ( ) ;
399+ let compiled_class_hash_b_felt = 101_u64 . into ( ) ;
400+ let compiled_class_hash_d_felt = 102_u64 . into ( ) ;
401+
402+ let mut committer = new_test_committer ( ) . await ;
403+ let height_0 = 0 ;
404+ let height_1 = 1 ;
405+ let block_0_state_diff = ThinStateDiff {
406+ class_hash_to_compiled_class_hash : indexmap ! {
407+ class_hash_a => CompiledClassHash ( compiled_class_hash_a_felt) ,
408+ class_hash_b => CompiledClassHash ( compiled_class_hash_b_felt) ,
409+ class_hash_d => CompiledClassHash ( compiled_class_hash_d_felt) ,
410+ } ,
411+ ..Default :: default ( )
412+ } ;
413+ let block_1_state_diff = ThinStateDiff {
414+ class_hash_to_compiled_class_hash : indexmap ! {
415+ class_hash_d => CompiledClassHash ( 0_u64 . into( ) ) ,
416+ } ,
417+ ..Default :: default ( )
418+ } ;
419+ let accessed_keys = AccessedKeys {
420+ accessed_class_hashes : BTreeSet :: from ( [ class_hash_d] ) ,
421+ ..Default :: default ( )
422+ } ;
423+
424+ committer
425+ . commit_block ( CommitBlockRequest {
426+ state_diff : block_0_state_diff. clone ( ) ,
427+ state_diff_commitment : Some ( calculate_state_diff_hash ( & block_0_state_diff) ) ,
428+ height : BlockNumber ( height_0) ,
429+ } )
430+ . await
431+ . unwrap ( ) ;
432+
433+ let response = committer
434+ . read_paths_and_commit_block ( read_paths_and_commit_block_request (
435+ block_1_state_diff. clone ( ) ,
436+ Some ( calculate_state_diff_hash ( & block_1_state_diff) ) ,
437+ height_1,
438+ accessed_keys,
439+ ) )
440+ . await
441+ . unwrap ( ) ;
442+
443+ let leaf_a_hash = TreeHashFunctionImpl :: compute_leaf_hash ( & CommitterCompiledClassHash (
444+ compiled_class_hash_a_felt,
445+ ) ) ;
446+ let leaf_b_hash = TreeHashFunctionImpl :: compute_leaf_hash ( & CommitterCompiledClassHash (
447+ compiled_class_hash_b_felt,
448+ ) ) ;
449+ let node_g_hash = TreeHashFunctionImpl :: compute_node_hash ( & NodeData :: <
450+ CommitterCompiledClassHash ,
451+ HashOutput ,
452+ > :: Binary ( BinaryData {
453+ left_data : leaf_a_hash,
454+ right_data : leaf_b_hash,
455+ } ) ) ;
456+ // Keys 0 and 1 place G one level above the leaves; E is the root's left child.
457+ let path_e_to_g = PathToBottom :: new (
458+ EdgePath :: default ( ) ,
459+ EdgePathLength :: new ( SubTreeHeight :: ACTUAL_HEIGHT . 0 - 1 ) . expect ( "Illegal edge path length" ) ,
460+ )
461+ . expect ( "Illegal path from E to G" ) ;
462+ let node_e_hash = TreeHashFunctionImpl :: compute_node_hash ( & NodeData :: <
463+ CommitterCompiledClassHash ,
464+ HashOutput ,
465+ > :: Edge ( EdgeData {
466+ bottom_data : node_g_hash,
467+ path_to_bottom : path_e_to_g,
468+ } ) ) ;
469+
470+ assert ! (
471+ response. patricia_proofs. classes_trie_proof. contains_key( & node_e_hash) ,
472+ "missing node attesting to the topology of the new tree" ,
473+ ) ;
474+
475+ // Today we also include the bottom of every traversed edge node. This is not necessary, and is
476+ // expected to fail once we are more strict in the fetched preimages.
477+ assert ! (
478+ response. patricia_proofs. classes_trie_proof. contains_key( & node_g_hash) ,
479+ "missing bottom of a new edge node in a proof" ,
480+ ) ;
481+ }
482+
483+ /// Flow overview:
484+ /// 1. Commit block 0 with three class leaves that form this Patricia topology:
485+ /// ```text
486+ /// R
487+ /// |
488+ /// T
489+ /// / \
490+ /// E F
491+ /// / \ \
492+ /// A B D
493+ /// ```
494+ /// 2. Commit block 1 via [crate::committer::Committer::read_paths_and_commit_block], deleting `D`
495+ /// and requesting witnesses only for the deleted key.
496+ /// 3. Assert the returned classes-trie proof contains node `E`. In this test, E is not the bottom
497+ /// of an existing edge, so it must be included in the proof to become convinced of the new
498+ /// tree's validity.
499+ #[ tokio:: test]
500+ async fn test_bottom_of_new_edge_to_a_binary_unmodified_subtree_is_present ( ) {
501+ let class_hash_a = ClassHash ( 0_u64 . into ( ) ) ;
502+ let class_hash_b = ClassHash ( 1_u64 . into ( ) ) ;
503+ let class_hash_d = ClassHash ( 3_u64 . into ( ) ) ;
504+
505+ let compiled_class_hash_a_felt = 100_u64 . into ( ) ;
506+ let compiled_class_hash_b_felt = 101_u64 . into ( ) ;
507+ let compiled_class_hash_d_felt = 102_u64 . into ( ) ;
508+
509+ let mut committer = new_test_committer ( ) . await ;
510+ let height_0 = 0 ;
511+ let height_1 = 1 ;
512+ let block_0_state_diff = ThinStateDiff {
513+ class_hash_to_compiled_class_hash : indexmap ! {
514+ class_hash_a => CompiledClassHash ( compiled_class_hash_a_felt) ,
515+ class_hash_b => CompiledClassHash ( compiled_class_hash_b_felt) ,
516+ class_hash_d => CompiledClassHash ( compiled_class_hash_d_felt) ,
517+ } ,
518+ ..Default :: default ( )
519+ } ;
520+ let block_1_state_diff = ThinStateDiff {
521+ class_hash_to_compiled_class_hash : indexmap ! {
522+ class_hash_d => CompiledClassHash ( 0_u64 . into( ) ) ,
523+ } ,
524+ ..Default :: default ( )
525+ } ;
526+ let accessed_keys = AccessedKeys {
527+ accessed_class_hashes : BTreeSet :: from ( [ class_hash_d] ) ,
528+ ..Default :: default ( )
529+ } ;
530+
531+ committer
532+ . commit_block ( CommitBlockRequest {
533+ state_diff : block_0_state_diff. clone ( ) ,
534+ state_diff_commitment : Some ( calculate_state_diff_hash ( & block_0_state_diff) ) ,
535+ height : BlockNumber ( height_0) ,
536+ } )
537+ . await
538+ . unwrap ( ) ;
539+
540+ let response = committer
541+ . read_paths_and_commit_block ( read_paths_and_commit_block_request (
542+ block_1_state_diff. clone ( ) ,
543+ Some ( calculate_state_diff_hash ( & block_1_state_diff) ) ,
544+ height_1,
545+ accessed_keys,
546+ ) )
547+ . await
548+ . unwrap ( ) ;
549+
550+ let leaf_a_hash = TreeHashFunctionImpl :: compute_leaf_hash ( & CommitterCompiledClassHash (
551+ compiled_class_hash_a_felt,
552+ ) ) ;
553+ let leaf_b_hash = TreeHashFunctionImpl :: compute_leaf_hash ( & CommitterCompiledClassHash (
554+ compiled_class_hash_b_felt,
555+ ) ) ;
556+ let node_e_hash = TreeHashFunctionImpl :: compute_node_hash ( & NodeData :: <
557+ CommitterCompiledClassHash ,
558+ HashOutput ,
559+ > :: Binary ( BinaryData {
560+ left_data : leaf_a_hash,
561+ right_data : leaf_b_hash,
562+ } ) ) ;
563+
564+ assert ! (
565+ response. patricia_proofs. classes_trie_proof. contains_key( & node_e_hash) ,
566+ "missing bottom of a new edge node in a proof" ,
567+ ) ;
568+ }
0 commit comments