11//! Execution cache implementation for block processing.
2- use alloy_primitives:: { Address , StorageKey , StorageValue , B256 } ;
2+ use alloy_primitives:: {
3+ map:: { DefaultHashBuilder , HashSet } ,
4+ Address , StorageKey , StorageValue , B256 ,
5+ } ;
36use metrics:: Gauge ;
47use mini_moka:: sync:: CacheBuilder ;
58use reth_errors:: ProviderResult ;
@@ -14,7 +17,6 @@ use reth_trie::{
1417 updates:: TrieUpdates , AccountProof , HashedPostState , HashedStorage , MultiProof ,
1518 MultiProofTargets , StorageMultiProof , StorageProof , TrieInput ,
1619} ;
17- use revm_primitives:: map:: DefaultHashBuilder ;
1820use std:: { sync:: Arc , time:: Duration } ;
1921use tracing:: trace;
2022
@@ -300,65 +302,69 @@ pub(crate) struct ExecutionCache {
300302 /// Cache for contract bytecode, keyed by code hash.
301303 code_cache : Cache < B256 , Option < Bytecode > > ,
302304
303- /// Per-account storage cache: outer cache keyed by Address, inner cache tracks that account’s
304- /// storage slots .
305- storage_cache : Cache < Address , AccountStorageCache > ,
305+ /// Flattened storage cache: composite key of (` Address`, `StorageKey`) maps directly to
306+ /// values .
307+ storage_cache : Cache < ( Address , StorageKey ) , Option < StorageValue > > ,
306308
307309 /// Cache for basic account information (nonce, balance, code hash).
308310 account_cache : Cache < Address , Option < Account > > ,
309311}
310312
311313impl ExecutionCache {
312- /// Get storage value from hierarchical cache.
314+ /// Get storage value from flattened cache.
313315 ///
314316 /// Returns a `SlotStatus` indicating whether:
315- /// - `NotCached`: The account's storage cache doesn't exist
316- /// - `Empty`: The slot exists in the account's cache but is empty
317+ /// - `NotCached`: The storage slot is not in the cache
318+ /// - `Empty`: The slot exists in the cache but is empty
317319 /// - `Value`: The slot exists and has a specific value
318320 pub ( crate ) fn get_storage ( & self , address : & Address , key : & StorageKey ) -> SlotStatus {
319- match self . storage_cache . get ( address) {
321+ match self . storage_cache . get ( & ( * address, * key ) ) {
320322 None => SlotStatus :: NotCached ,
321- Some ( account_cache) => account_cache. get_storage ( key) ,
323+ Some ( None ) => SlotStatus :: Empty ,
324+ Some ( Some ( value) ) => SlotStatus :: Value ( value) ,
322325 }
323326 }
324327
325- /// Insert storage value into hierarchical cache
328+ /// Insert storage value into flattened cache
326329 pub ( crate ) fn insert_storage (
327330 & self ,
328331 address : Address ,
329332 key : StorageKey ,
330333 value : Option < StorageValue > ,
331334 ) {
332- self . insert_storage_bulk ( address, [ ( key, value) ] ) ;
335+ self . storage_cache . insert ( ( address, key) , value) ;
333336 }
334337
335- /// Insert multiple storage values into hierarchical cache for a single account
338+ /// Insert multiple storage values into flattened cache for a single account
336339 ///
337- /// This method is optimized for inserting multiple storage values for the same address
338- /// by doing the account cache lookup only once instead of for each key-value pair .
340+ /// This method inserts multiple storage values for the same address directly
341+ /// into the flattened cache.
339342 pub ( crate ) fn insert_storage_bulk < I > ( & self , address : Address , storage_entries : I )
340343 where
341344 I : IntoIterator < Item = ( StorageKey , Option < StorageValue > ) > ,
342345 {
343- let account_cache = self . storage_cache . get ( & address) . unwrap_or_else ( || {
344- let account_cache = AccountStorageCache :: default ( ) ;
345- self . storage_cache . insert ( address, account_cache. clone ( ) ) ;
346- account_cache
347- } ) ;
348-
349346 for ( key, value) in storage_entries {
350- account_cache . insert_storage ( key, value) ;
347+ self . storage_cache . insert ( ( address , key) , value) ;
351348 }
352349 }
353350
354- /// Invalidate storage for specific account
355- pub ( crate ) fn invalidate_account_storage ( & self , address : & Address ) {
356- self . storage_cache . invalidate ( address) ;
357- }
358-
359351 /// Returns the total number of storage slots cached across all accounts
360352 pub ( crate ) fn total_storage_slots ( & self ) -> usize {
361- self . storage_cache . iter ( ) . map ( |addr| addr. len ( ) ) . sum ( )
353+ self . storage_cache . entry_count ( ) as usize
354+ }
355+
356+ /// Invalidates the storage for all addresses in the set
357+ pub ( crate ) fn invalidate_storages ( & self , addresses : HashSet < & Address > ) {
358+ // NOTE: this must collect because the invalidate function should not be called while we
359+ // hold an iter for it
360+ let storage_entries = self
361+ . storage_cache
362+ . iter ( )
363+ . filter_map ( |entry| addresses. contains ( & entry. key ( ) . 0 ) . then_some ( * entry. key ( ) ) )
364+ . collect :: < Vec < _ > > ( ) ;
365+ for key in storage_entries {
366+ self . storage_cache . invalidate ( & key)
367+ }
362368 }
363369
364370 /// Inserts the post-execution state changes into the cache.
@@ -385,6 +391,7 @@ impl ExecutionCache {
385391 self . code_cache . insert ( * code_hash, Some ( Bytecode ( bytecode. clone ( ) ) ) ) ;
386392 }
387393
394+ let mut invalidated_accounts = HashSet :: default ( ) ;
388395 for ( addr, account) in & state_updates. state {
389396 // If the account was not modified, as in not changed and not destroyed, then we have
390397 // nothing to do w.r.t. this particular account and can move on
@@ -397,7 +404,7 @@ impl ExecutionCache {
397404 // Invalidate the account cache entry if destroyed
398405 self . account_cache . invalidate ( addr) ;
399406
400- self . invalidate_account_storage ( addr) ;
407+ invalidated_accounts . insert ( addr) ;
401408 continue
402409 }
403410
@@ -424,6 +431,9 @@ impl ExecutionCache {
424431 self . account_cache . insert ( * addr, Some ( Account :: from ( account_info) ) ) ;
425432 }
426433
434+ // invalidate storage for all destroyed accounts
435+ self . invalidate_storages ( invalidated_accounts) ;
436+
427437 Ok ( ( ) )
428438 }
429439}
@@ -452,11 +462,11 @@ impl ExecutionCacheBuilder {
452462 const TIME_TO_IDLE : Duration = Duration :: from_secs ( 3600 ) ; // 1 hour
453463
454464 let storage_cache = CacheBuilder :: new ( self . storage_cache_entries )
455- . weigher ( |_key : & Address , value : & AccountStorageCache | -> u32 {
456- // values based on results from measure_storage_cache_overhead test
457- let base_weight = 39_000 ;
458- let slots_weight = value . len ( ) * 218 ;
459- ( base_weight + slots_weight ) as u32
465+ . weigher ( |_key : & ( Address , StorageKey ) , _value : & Option < StorageValue > | -> u32 {
466+ // Size of composite key (Address + StorageKey) + Option<StorageValue>
467+ // Address: 20 bytes, StorageKey: 32 bytes, Option<StorageValue>: 33 bytes
468+ // Plus some overhead for the hash map entry
469+ 120_u32
460470 } )
461471 . max_capacity ( storage_cache_size)
462472 . time_to_live ( EXPIRY_TIME )
@@ -573,56 +583,6 @@ impl SavedCache {
573583 }
574584}
575585
576- /// Cache for an individual account's storage slots.
577- ///
578- /// This represents the second level of the hierarchical storage cache.
579- /// Each account gets its own `AccountStorageCache` to store accessed storage slots.
580- #[ derive( Debug , Clone ) ]
581- pub ( crate ) struct AccountStorageCache {
582- /// Map of storage keys to their cached values.
583- slots : Cache < StorageKey , Option < StorageValue > > ,
584- }
585-
586- impl AccountStorageCache {
587- /// Create a new [`AccountStorageCache`]
588- pub ( crate ) fn new ( max_slots : u64 ) -> Self {
589- Self {
590- slots : CacheBuilder :: new ( max_slots) . build_with_hasher ( DefaultHashBuilder :: default ( ) ) ,
591- }
592- }
593-
594- /// Get a storage value from this account's cache.
595- /// - `NotCached`: The slot is not in the cache
596- /// - `Empty`: The slot is empty
597- /// - `Value`: The slot has a specific value
598- pub ( crate ) fn get_storage ( & self , key : & StorageKey ) -> SlotStatus {
599- match self . slots . get ( key) {
600- None => SlotStatus :: NotCached ,
601- Some ( None ) => SlotStatus :: Empty ,
602- Some ( Some ( value) ) => SlotStatus :: Value ( value) ,
603- }
604- }
605-
606- /// Insert a storage value
607- pub ( crate ) fn insert_storage ( & self , key : StorageKey , value : Option < StorageValue > ) {
608- self . slots . insert ( key, value) ;
609- }
610-
611- /// Returns the number of slots in the cache
612- pub ( crate ) fn len ( & self ) -> usize {
613- self . slots . entry_count ( ) as usize
614- }
615- }
616-
617- impl Default for AccountStorageCache {
618- fn default ( ) -> Self {
619- // With weigher and max_capacity in place, this number represents
620- // the maximum number of entries that can be stored, not the actual
621- // memory usage which is controlled by storage cache's max_capacity.
622- Self :: new ( 1_000_000 )
623- }
624- }
625-
626586#[ cfg( test) ]
627587mod tests {
628588 use super :: * ;
@@ -697,32 +657,36 @@ mod tests {
697657
698658 #[ test]
699659 fn measure_storage_cache_overhead ( ) {
700- let ( base_overhead, cache) = measure_allocation ( || AccountStorageCache :: new ( 1000 ) ) ;
701- println ! ( "Base AccountStorageCache overhead: {base_overhead} bytes" ) ;
660+ let ( base_overhead, cache) =
661+ measure_allocation ( || ExecutionCacheBuilder :: default ( ) . build_caches ( 1000 ) ) ;
662+ println ! ( "Base ExecutionCache overhead: {base_overhead} bytes" ) ;
702663 let mut rng = rand:: rng ( ) ;
703664
665+ let address = Address :: random ( ) ;
704666 let key = StorageKey :: random ( ) ;
705667 let value = StorageValue :: from ( rng. random :: < u128 > ( ) ) ;
706668 let ( first_slot, _) = measure_allocation ( || {
707- cache. insert_storage ( key, Some ( value) ) ;
669+ cache. insert_storage ( address , key, Some ( value) ) ;
708670 } ) ;
709671 println ! ( "First slot insertion overhead: {first_slot} bytes" ) ;
710672
711673 const TOTAL_SLOTS : usize = 10_000 ;
712674 let ( test_slots, _) = measure_allocation ( || {
713675 for _ in 0 ..TOTAL_SLOTS {
676+ let addr = Address :: random ( ) ;
714677 let key = StorageKey :: random ( ) ;
715678 let value = StorageValue :: from ( rng. random :: < u128 > ( ) ) ;
716- cache. insert_storage ( key, Some ( value) ) ;
679+ cache. insert_storage ( addr , key, Some ( value) ) ;
717680 }
718681 } ) ;
719682 println ! ( "Average overhead over {} slots: {} bytes" , TOTAL_SLOTS , test_slots / TOTAL_SLOTS ) ;
720683
721684 println ! ( "\n Theoretical sizes:" ) ;
685+ println ! ( "Address size: {} bytes" , size_of:: <Address >( ) ) ;
722686 println ! ( "StorageKey size: {} bytes" , size_of:: <StorageKey >( ) ) ;
723687 println ! ( "StorageValue size: {} bytes" , size_of:: <StorageValue >( ) ) ;
724688 println ! ( "Option<StorageValue> size: {} bytes" , size_of:: <Option <StorageValue >>( ) ) ;
725- println ! ( "Option<B256> size: {} bytes" , size_of:: <Option < B256 > >( ) ) ;
689+ println ! ( "(Address, StorageKey) size: {} bytes" , size_of:: <( Address , StorageKey ) >( ) ) ;
726690 }
727691
728692 #[ test]
0 commit comments