66//!
77//! Tree depth is fixed at 256 (matching SHA-256 key space). Each key is a
88//! 256-bit address; the i-th bit selects left (0) or right (1) at depth i.
9+ //!
10+ //! Performance notes:
11+ //! - Uses FxHashMap (rustc-hash) for all internal maps. Since keys are
12+ //! already cryptographic hashes, SipHash protection is unnecessary and
13+ //! FxHash's multiply-xor is ~3x faster for lookups.
14+ //! - Path updates short-circuit when both current and sibling are empty
15+ //! at a level, avoiding redundant SHA-256 computations.
16+ //! - Batch operations sort keys by prefix for cache locality.
917
1018use crate :: hasher:: { self , Hash , DEFAULT_EMPTY } ;
19+ use rustc_hash:: FxHashMap ;
1120use serde:: { Deserialize , Serialize } ;
1221use std:: collections:: HashMap ;
1322use std:: sync:: LazyLock ;
@@ -66,12 +75,14 @@ pub struct BatchMutationResult {
6675/// Stores only populated leaves and caches internal node hashes along
6776/// paths that have been touched. Empty subtrees are represented implicitly
6877/// using precomputed per-level empty hashes.
78+ ///
79+ /// Uses FxHashMap for O(1) lookups without SipHash overhead on pre-hashed keys.
6980pub struct SparseMerkleTree {
7081 /// Populated leaf entries: key -> data
71- leaves : HashMap < Hash , LeafData > ,
82+ leaves : FxHashMap < Hash , LeafData > ,
7283 /// Cached internal node hashes keyed by (depth_from_root, prefix)
7384 /// where prefix has bits below that depth zeroed.
74- nodes : HashMap < ( u16 , Hash ) , Hash > ,
85+ nodes : FxHashMap < ( u16 , Hash ) , Hash > ,
7586 /// Current root hash
7687 root : Hash ,
7788}
@@ -80,19 +91,17 @@ impl SparseMerkleTree {
8091 /// Create an empty tree.
8192 pub fn new ( ) -> Self {
8293 Self {
83- leaves : HashMap :: new ( ) ,
84- nodes : HashMap :: new ( ) ,
94+ leaves : FxHashMap :: default ( ) ,
95+ nodes : FxHashMap :: default ( ) ,
8596 root : EMPTY_HASHES [ TREE_DEPTH ] ,
8697 }
8798 }
8899
89100 /// Create a tree with pre-allocated capacity for `n` expected leaves.
90101 pub fn with_capacity ( n : usize ) -> Self {
91102 Self {
92- leaves : HashMap :: with_capacity ( n) ,
93- // Each leaf touches ~256 internal nodes, but many share prefixes.
94- // Heuristic: n * 64 is a reasonable initial capacity.
95- nodes : HashMap :: with_capacity ( n * 64 ) ,
103+ leaves : FxHashMap :: with_capacity_and_hasher ( n, Default :: default ( ) ) ,
104+ nodes : FxHashMap :: with_capacity_and_hasher ( n * 64 , Default :: default ( ) ) ,
96105 root : EMPTY_HASHES [ TREE_DEPTH ] ,
97106 }
98107 }
@@ -142,8 +151,10 @@ impl SparseMerkleTree {
142151 }
143152
144153 /// Insert multiple key-value pairs in batch.
145- /// More efficient than individual inserts when adding many entries,
146- /// as the path updates can share intermediate computations.
154+ ///
155+ /// Sorts keys by first 8 bytes to maximize cache-line reuse when
156+ /// updating overlapping Merkle paths. Keys sharing long prefixes will
157+ /// be adjacent, so their path updates reuse recently-cached sibling hashes.
147158 pub fn insert_batch (
148159 & mut self ,
149160 entries : Vec < ( Hash , Vec < u8 > , Option < String > ) > ,
@@ -153,7 +164,7 @@ impl SparseMerkleTree {
153164 let count = entries. len ( ) ;
154165
155166 // Insert all leaves first
156- let keys: Vec < Hash > = entries
167+ let mut keys: Vec < Hash > = entries
157168 . into_iter ( )
158169 . map ( |( key, value, tag) | {
159170 self . leaves . insert (
@@ -168,6 +179,9 @@ impl SparseMerkleTree {
168179 } )
169180 . collect ( ) ;
170181
182+ // Sort by prefix for cache locality during path updates
183+ keys. sort_unstable ( ) ;
184+
171185 // Update paths for all modified keys
172186 for key in & keys {
173187 self . update_path ( key) ;
@@ -200,7 +214,11 @@ impl SparseMerkleTree {
200214 for key in keys {
201215 self . leaves . remove ( key) ;
202216 }
203- for key in keys {
217+
218+ // Sort for cache locality
219+ let mut sorted = keys. to_vec ( ) ;
220+ sorted. sort_unstable ( ) ;
221+ for key in & sorted {
204222 self . update_path ( key) ;
205223 }
206224
@@ -294,6 +312,11 @@ impl SparseMerkleTree {
294312 }
295313
296314 /// Update all cached internal nodes along the path from a leaf to the root.
315+ ///
316+ /// Optimization: when both current and sibling are the level-appropriate
317+ /// empty hash, the parent is known to be `EMPTY_HASHES[levels_up + 1]`
318+ /// without computing a SHA-256. We still cache it (for consistency) but
319+ /// skip the expensive hash_internal call.
297320 fn update_path ( & mut self , key : & Hash ) {
298321 let mut current = match self . leaves . get ( key) {
299322 Some ( leaf) => hasher:: hash_leaf ( key, & leaf. value ) ,
@@ -306,14 +329,19 @@ impl SparseMerkleTree {
306329
307330 let sibling = self . get_sibling_hash ( key, depth_from_root, levels_up) ;
308331
309- let parent = if bit == 0 {
332+ // Early termination: if both children are their level's empty hash,
333+ // the parent is the next level's empty hash. Skip SHA-256.
334+ let parent = if current == EMPTY_HASHES [ levels_up]
335+ && sibling == EMPTY_HASHES [ levels_up]
336+ {
337+ EMPTY_HASHES [ levels_up + 1 ]
338+ } else if bit == 0 {
310339 hasher:: hash_internal ( & current, & sibling)
311340 } else {
312341 hasher:: hash_internal ( & sibling, & current)
313342 } ;
314343
315- // Cache the parent at depth_from_root. The parent is the root of
316- // the subtree spanning both current and sibling.
344+ // Cache the parent at depth_from_root
317345 let cache_k = Self :: make_cache_key ( key, depth_from_root) ;
318346 self . nodes . insert ( cache_k, parent) ;
319347
@@ -341,9 +369,14 @@ impl SparseMerkleTree {
341369 prefix[ boundary_byte] &= !( ( 1u8 << ( 8 - bit_in_byte) ) - 1 ) ;
342370 }
343371
344- // Zero all complete bytes after the boundary
345- for b in prefix. iter_mut ( ) . skip ( boundary_byte + 1 ) {
346- * b = 0 ;
372+ // Zero all complete bytes after the boundary using ptr::write_bytes
373+ // for bulk memset instead of a per-byte loop
374+ let start = boundary_byte + 1 ;
375+ if start < 32 {
376+ // SAFETY: prefix is [u8; 32], start..32 is within bounds
377+ unsafe {
378+ std:: ptr:: write_bytes ( prefix. as_mut_ptr ( ) . add ( start) , 0 , 32 - start) ;
379+ }
347380 }
348381
349382 ( depth_from_root as u16 , prefix)
@@ -370,22 +403,26 @@ impl SparseMerkleTree {
370403 self . leaves . keys ( ) . copied ( ) . collect ( )
371404 }
372405
373- /// Export the full tree state as a serializable snapshot
406+ /// Export the full tree state as a serializable snapshot.
407+ /// The snapshot uses std HashMap for serde compatibility.
374408 pub fn snapshot ( & self ) -> TreeSnapshot {
375409 TreeSnapshot {
376410 root : self . root ,
377- leaves : self . leaves . clone ( ) ,
411+ leaves : self . leaves . iter ( ) . map ( | ( k , v ) | ( * k , v . clone ( ) ) ) . collect ( ) ,
378412 leaf_count : self . leaves . len ( ) ,
379413 }
380414 }
381415
382416 /// Restore a tree from a snapshot, rebuilding the internal node cache.
383417 pub fn from_snapshot ( snapshot : TreeSnapshot ) -> Self {
384418 let mut tree = Self :: with_capacity ( snapshot. leaves . len ( ) ) ;
385- tree. leaves = snapshot. leaves ;
419+ for ( k, v) in snapshot. leaves {
420+ tree. leaves . insert ( k, v) ;
421+ }
386422
387- // Rebuild all paths
388- let keys: Vec < Hash > = tree. leaves . keys ( ) . copied ( ) . collect ( ) ;
423+ // Rebuild all paths (sorted for cache locality)
424+ let mut keys: Vec < Hash > = tree. leaves . keys ( ) . copied ( ) . collect ( ) ;
425+ keys. sort_unstable ( ) ;
389426 for key in & keys {
390427 tree. update_path ( key) ;
391428 }
@@ -482,7 +519,6 @@ mod tests {
482519 let tree = SparseMerkleTree :: new ( ) ;
483520 assert ! ( tree. is_empty( ) ) ;
484521 assert_eq ! ( tree. len( ) , 0 ) ;
485- // Root should be the precomputed empty root
486522 assert_eq ! ( tree. root( ) , EMPTY_HASHES [ TREE_DEPTH ] ) ;
487523 }
488524
@@ -692,4 +728,15 @@ mod tests {
692728 assert ! ( stats. cached_node_count > 0 ) ;
693729 assert ! ( stats. estimated_total_bytes > 0 ) ;
694730 }
731+
732+ #[ test]
733+ fn test_early_termination_on_remove ( ) {
734+ // After removing the only leaf, the tree should short-circuit
735+ // most of the 256-level path update via empty hash detection
736+ let mut tree = SparseMerkleTree :: new ( ) ;
737+ let key = hasher:: compute_key ( b"only_leaf" ) ;
738+ tree. insert ( key, b"val" . to_vec ( ) , None ) ;
739+ tree. remove ( & key) ;
740+ assert_eq ! ( tree. root( ) , EMPTY_HASHES [ TREE_DEPTH ] ) ;
741+ }
695742}
0 commit comments