@@ -427,13 +427,37 @@ impl<K: AsRef<AddedRemovedKeys>> HashBuilder<K> {
427427 /// masks in the database. We will use that when consuming the intermediate nodes
428428 /// from the database to efficiently build the trie.
429429 fn store_branch_node ( & mut self , current : & Nibbles , len : usize , children : Vec < B256 > ) {
430+ // Update the parent's hash_mask to indicate this child is a hashed branch node
430431 if len > 0 {
431432 let parent_index = len - 1 ;
432433 self . hash_masks [ parent_index] |=
433434 TrieMask :: from_nibble ( current. get_unchecked ( parent_index) ) ;
434435 }
435436
436- let store_in_db_trie = !self . tree_masks [ len] . is_empty ( ) || !self . hash_masks [ len] . is_empty ( ) ;
437+ // Determine if this branch node should be stored in the database.
438+ //
439+ // A branch node must be stored if:
440+ // 1. It is the root node (len == 0), since it's the entry point for trie traversal, OR
441+ // 2. It has children that are themselves branches (indicated by non-empty tree/hash masks),
442+ // meaning we need to store it to allow incremental updates, OR
443+ // 3. Its parent references it as a hashed child. Since we just set the parent's hash_mask
444+ // above, we check if this child's bit is now set in the parent's hash_mask.
445+ //
446+ // The key insight is that even a branch node with only leaf children must be stored
447+ // if its parent will reference it by hash (which is always the case for branch nodes
448+ // whose RLP encoding >= 32 bytes).
449+ let store_in_db_trie = if len == 0 {
450+ // Root node is always stored
451+ true
452+ } else {
453+ let parent_index = len - 1 ;
454+ let flag = TrieMask :: from_nibble ( current. get_unchecked ( parent_index) ) ;
455+ // Store if this node has branch children OR if the parent references this child
456+ !self . tree_masks [ len] . is_empty ( )
457+ || !self . hash_masks [ len] . is_empty ( )
458+ || ( self . hash_masks [ parent_index] & flag) != TrieMask :: default ( )
459+ } ;
460+
437461 if store_in_db_trie {
438462 if len > 0 {
439463 let parent_index = len - 1 ;
@@ -631,11 +655,8 @@ mod tests {
631655
632656 /// Verify that branch updates are complete for multi-leaf tries.
633657 ///
634- /// NOTE: This test currently fails due to a bug in `store_branch_node` where branch nodes
635- /// with only leaf children are not stored in updates. See:
636- /// <https://github.com/alloy-rs/trie/pull/124>
658+ /// For tries with 2+ leaves, there must be at least one branch node in updates.
637659 #[ test]
638- #[ ignore = "fails due to store_branch_node bug - see PR #124" ]
639660 #[ cfg( feature = "arbitrary" ) ]
640661 #[ cfg_attr( miri, ignore = "no proptest" ) ]
641662 fn arbitrary_branch_updates_complete ( ) {
@@ -796,7 +817,8 @@ mod tests {
796817 // `3`.
797818 // 2. Hash Mask: 0b0110. Of the above items, nibbles `1` and `2` correspond to children that
798819 // are branch nodes.
799- // 3. Tree Mask: 0b0000. None of the children are stored in the database (yet).
820+ // 3. Tree Mask: 0b0110. Children at nibbles `1` and `2` are branch nodes that are stored in
821+ // the database/updates (they are referenced by hash from this parent node).
800822 // 4. Hashes: Hashes of the 2 sub-branch roots, at nibbles `1` and `2`. Calculated by
801823 // hashing the 0th and 1st element for the branch at nibble `1` , and the 0th and 2nd
802824 // element for the branch at nibble `2`. This basically means that every
@@ -842,8 +864,8 @@ mod tests {
842864 let update = updates. get ( & Nibbles :: from_nibbles_unchecked ( hex ! ( "01" ) ) ) . unwrap ( ) ;
843865 // Nibbles 0, 1, 2, 3 have children
844866 assert_eq ! ( update. state_mask, TrieMask :: new( 0b1111 ) ) ;
845- // None of the children are stored in the database
846- assert_eq ! ( update. tree_mask, TrieMask :: new( 0b0000 ) ) ;
867+ // Children at nibbles 1 and 2 are branch nodes stored in updates
868+ assert_eq ! ( update. tree_mask, TrieMask :: new( 0b0110 ) ) ;
847869 // Children under nibbles `1` and `2` are branch nodes with `hashes`
848870 assert_eq ! ( update. hash_mask, TrieMask :: new( 0b0110 ) ) ;
849871 // Calculated when running the hash builder
@@ -916,12 +938,123 @@ mod tests {
916938 assert_eq ! ( hb2. root( ) , expected) ;
917939 }
918940
941+ /// Test that branch nodes are correctly added to updates even when they only have leaf
942+ /// children. This was a bug where branch nodes with only leaf children were not being
943+ /// stored in updates because the condition only checked if the node had branch children.
944+ #[ test]
945+ fn test_updates_root ( ) {
946+ let mut hb = HashBuilder :: default ( ) . with_updates ( true ) ;
947+ let account: Vec < u8 > = Vec :: new ( ) ;
948+
949+ hb. add_leaf (
950+ Nibbles :: unpack ( hex ! (
951+ "a711355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40"
952+ ) ) ,
953+ account. as_ref ( ) ,
954+ ) ;
955+ hb. add_leaf (
956+ Nibbles :: unpack ( hex ! (
957+ "a77d337781e762f3577784bab7491fcc43e291ce5a356b9bc517ac52eed3a37a"
958+ ) ) ,
959+ account. as_ref ( ) ,
960+ ) ;
961+ hb. add_leaf (
962+ Nibbles :: unpack ( hex ! (
963+ "a77d397a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3"
964+ ) ) ,
965+ account. as_ref ( ) ,
966+ ) ;
967+ hb. add_leaf (
968+ Nibbles :: unpack ( hex ! (
969+ "a7f936599f93b769acf90c7178fd2ddcac1b5b4bc9949ee5a04b7e0823c2446e"
970+ ) ) ,
971+ account. as_ref ( ) ,
972+ ) ;
973+
974+ let _root = hb. root ( ) ;
975+ let ( _, updates) = hb. split ( ) ;
976+
977+ // Previously this would return an empty map because branch nodes with only
978+ // leaf children were not being stored. Now all branch nodes should be stored.
979+ assert ! ( !updates. is_empty( ) , "updates should not be empty" ) ;
980+ }
981+
982+ /// Test the tree handling for various branch configurations.
983+ /// This tests that all branch nodes in a multi-level trie are correctly stored.
984+ #[ test]
985+ fn test_trie_updates_branch_nodes ( ) {
986+ let default_leaf = b"hello" ;
987+
988+ // Create a trie structure with multiple branch levels:
989+ // Root branch at 0x with children at nibbles 0, 1, 2, 3
990+ // Each of these has further structure creating nested branches
991+ let data = vec ! [
992+ ( hex!( "0000" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
993+ ( hex!( "0020" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
994+ ( hex!( "1000" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
995+ ( hex!( "2000" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
996+ ( hex!( "2111" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
997+ ( hex!( "2122" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
998+ ( hex!( "2123" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
999+ ( hex!( "3000" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
1000+ ( hex!( "3001" ) . to_vec( ) , default_leaf. to_vec( ) ) ,
1001+ ] ;
1002+
1003+ let mut hb = HashBuilder :: default ( ) . with_updates ( true ) ;
1004+ for ( key, val) in & data {
1005+ let nibbles = Nibbles :: unpack ( key) ;
1006+ hb. add_leaf ( nibbles, val. as_ref ( ) ) ;
1007+ }
1008+
1009+ let _root = hb. root ( ) ;
1010+ let ( _, updates) = hb. split ( ) ;
1011+
1012+ // The trie structure creates 6 branch nodes that should all be in updates:
1013+ // 1. Root branch (path: empty)
1014+ // 2. Branch at 0x0 (children at 0, 2)
1015+ // 3. Branch at 0x2 (children at 0, 1)
1016+ // 4. Branch at 0x21 (children at 1, 2)
1017+ // 5. Branch at 0x212 (children at 2, 3)
1018+ // 6. Branch at 0x300 (children at 0, 1)
1019+ assert_eq ! ( updates. len( ) , 6 , "expected 6 branch nodes in updates" ) ;
1020+ }
1021+
1022+ /// Test that small storage tries (like new contracts with few slots) correctly
1023+ /// store their branch nodes for incremental sync.
1024+ #[ test]
1025+ fn test_small_storage_trie_updates ( ) {
1026+ use alloy_primitives:: keccak256;
1027+
1028+ let mut hb = HashBuilder :: default ( ) . with_updates ( true ) ;
1029+
1030+ // Simulate a contract with 4 storage slots (common for simple contracts)
1031+ // Keys are keccak256(slot_index) as per Ethereum spec
1032+ let slots: BTreeMap < _ , _ > = ( 0u64 ..4 )
1033+ . map ( |i| {
1034+ let mut slot = [ 0u8 ; 32 ] ;
1035+ slot[ 31 ] = i as u8 ;
1036+ let key = keccak256 ( slot) ;
1037+ ( key, alloy_rlp:: encode ( U256 :: from ( i * 100 ) ) )
1038+ } )
1039+ . collect ( ) ;
1040+
1041+ for ( key, value) in & slots {
1042+ hb. add_leaf ( Nibbles :: unpack ( key) , value) ;
1043+ }
1044+
1045+ let _root = hb. root ( ) ;
1046+ let ( _, updates) = hb. split ( ) ;
1047+
1048+ // Even a small trie should have at least the root branch node stored
1049+ assert ! ( !updates. is_empty( ) , "small storage trie should have updates" ) ;
1050+
1051+ // Verify root is included (path = empty nibbles)
1052+ assert ! ( updates. contains_key( & Nibbles :: default ( ) ) , "root branch node should be in updates" ) ;
1053+ }
1054+
9191055 /// Test edge case: keys that diverge at the last nibble with empty values.
9201056 /// This creates very small leaf RLPs and deep branch structures.
921- ///
922- /// NOTE: Fails due to store_branch_node bug - see PR #124
9231057 #[ test]
924- #[ ignore = "fails due to store_branch_node bug - see PR #124" ]
9251058 fn test_deep_divergence_empty_values ( ) {
9261059 let mut hb = HashBuilder :: default ( ) . with_updates ( true ) ;
9271060
@@ -939,10 +1072,7 @@ mod tests {
9391072 }
9401073
9411074 /// Test: siblings at different depths to verify mask propagation.
942- ///
943- /// NOTE: Fails due to store_branch_node bug - see PR #124
9441075 #[ test]
945- #[ ignore = "fails due to store_branch_node bug - see PR #124" ]
9461076 fn test_mask_propagation_across_depths ( ) {
9471077 let mut hb = HashBuilder :: default ( ) . with_updates ( true ) ;
9481078
@@ -1322,11 +1452,8 @@ mod tests {
13221452 ///
13231453 /// This tests that mask arrays are properly cleared/scoped when building
13241454 /// multiple subtrees under the same parent in successive inserts.
1325- ///
1326- /// NOTE: Fails due to store_branch_node bug - see PR #124
13271455 #[ test]
13281456 #[ cfg( feature = "std" ) ]
1329- #[ ignore = "fails due to store_branch_node bug - see PR #124" ]
13301457 fn test_mask_isolation_successive_subtrees ( ) {
13311458 let mut hb = HashBuilder :: default ( ) . with_updates ( true ) ;
13321459
0 commit comments