@@ -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` (this will allow verifying that G,
391+ /// which appears in the new edge A--G, indeed exists in the old tree).
392+ #[ tokio:: test]
393+ async fn test_bottom_of_new_edge_to_an_unmoidifed_subtree_is_present ( ) {
394+ // Set the two leftmost and the rightmost leaves.
395+ let class_hash_a = ClassHash ( 0_u64 . into ( ) ) ;
396+ let class_hash_b = ClassHash ( 1_u64 . into ( ) ) ;
397+ let class_hash_d = ClassHash ( PATRICIA_KEY_UPPER_BOUND_FELT - 1_u64 ) ;
398+
399+ let compiled_class_hash_a_felt = 100_u64 . into ( ) ;
400+ let compiled_class_hash_b_felt = 101_u64 . into ( ) ;
401+ let compiled_class_hash_d_felt = 102_u64 . into ( ) ;
402+
403+ let mut committer = new_test_committer ( ) . await ;
404+ let height_0 = 0 ;
405+ let height_1 = 1 ;
406+ let block_0_state_diff = ThinStateDiff {
407+ class_hash_to_compiled_class_hash : indexmap ! {
408+ class_hash_a => CompiledClassHash ( compiled_class_hash_a_felt) ,
409+ class_hash_b => CompiledClassHash ( compiled_class_hash_b_felt) ,
410+ class_hash_d => CompiledClassHash ( compiled_class_hash_d_felt) ,
411+ } ,
412+ ..Default :: default ( )
413+ } ;
414+ let block_1_state_diff = ThinStateDiff {
415+ class_hash_to_compiled_class_hash : indexmap ! {
416+ class_hash_d => CompiledClassHash ( 0_u64 . into( ) ) ,
417+ } ,
418+ ..Default :: default ( )
419+ } ;
420+ let accessed_keys = AccessedKeys {
421+ accessed_class_hashes : BTreeSet :: from ( [ class_hash_d] ) ,
422+ ..Default :: default ( )
423+ } ;
424+
425+ committer
426+ . commit_block ( CommitBlockRequest {
427+ state_diff : block_0_state_diff. clone ( ) ,
428+ state_diff_commitment : Some ( calculate_state_diff_hash ( & block_0_state_diff) ) ,
429+ height : BlockNumber ( height_0) ,
430+ } )
431+ . await
432+ . unwrap ( ) ;
433+
434+ let response = committer
435+ . read_paths_and_commit_block ( read_paths_and_commit_block_request (
436+ block_1_state_diff. clone ( ) ,
437+ Some ( calculate_state_diff_hash ( & block_1_state_diff) ) ,
438+ height_1,
439+ accessed_keys,
440+ ) )
441+ . await
442+ . unwrap ( ) ;
443+
444+ let leaf_a_hash = TreeHashFunctionImpl :: compute_leaf_hash ( & CommitterCompiledClassHash (
445+ compiled_class_hash_a_felt,
446+ ) ) ;
447+ let leaf_b_hash = TreeHashFunctionImpl :: compute_leaf_hash ( & CommitterCompiledClassHash (
448+ compiled_class_hash_b_felt,
449+ ) ) ;
450+ let node_g_hash = TreeHashFunctionImpl :: compute_node_hash ( & NodeData :: <
451+ CommitterCompiledClassHash ,
452+ HashOutput ,
453+ > :: Binary ( BinaryData {
454+ left_data : leaf_a_hash,
455+ right_data : leaf_b_hash,
456+ } ) ) ;
457+ // Keys 0 and 1 place G one level above the leaves; E is the root's left child.
458+ let path_e_to_g = PathToBottom :: new (
459+ EdgePath :: default ( ) ,
460+ EdgePathLength :: new ( SubTreeHeight :: ACTUAL_HEIGHT . 0 - 1 ) . expect ( "Illegal edge path length" ) ,
461+ )
462+ . expect ( "Illegal path from E to G" ) ;
463+ let node_e_hash = TreeHashFunctionImpl :: compute_node_hash ( & NodeData :: <
464+ CommitterCompiledClassHash ,
465+ HashOutput ,
466+ > :: Edge ( EdgeData {
467+ bottom_data : node_g_hash,
468+ path_to_bottom : path_e_to_g,
469+ } ) ) ;
470+
471+ assert ! (
472+ response. patricia_proofs. classes_trie_proof. contains_key( & node_e_hash) ,
473+ "missing node attesting to the topology of the new tree" ,
474+ ) ;
475+
476+ // Today we also include the bottom of every traversed edge node. This is not necessary, and is
477+ // expected to fail once we are more strict in the fetched preimages.
478+ assert ! (
479+ response. patricia_proofs. classes_trie_proof. contains_key( & node_g_hash) ,
480+ "missing bottom of a new edge node in a proof" ,
481+ ) ;
482+ }
483+
484+ /// Flow overview:
485+ /// 1. Commit block 0 with three class leaves that form this Patricia topology:
486+ /// ```text
487+ /// R
488+ /// |
489+ /// T
490+ /// / \
491+ /// E F
492+ /// / \ \
493+ /// A B D
494+ /// ```
495+ /// 2. Commit block 1 via [crate::committer::Committer::read_paths_and_commit_block], deleting `D`
496+ /// and requesting witnesses only for the deleted key.
497+ /// 3. Assert the returned classes-trie proof contains node `E` (this will allow verifying that the
498+ /// new edge's bottom is not an edge).
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