Skip to content

Commit e1eb84f

Browse files
committed
Revert "revert: "fix(engine): flatten storage cache (#18880)" (#19235)"
This reverts commit 1972ec0.
1 parent e8a75e7 commit e1eb84f

File tree

1 file changed

+54
-89
lines changed

1 file changed

+54
-89
lines changed

crates/engine/tree/src/tree/cached_state.rs

Lines changed: 54 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
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+
};
36
use metrics::Gauge;
47
use mini_moka::sync::CacheBuilder;
58
use 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;
1820
use std::{sync::Arc, time::Duration};
1921
use tracing::{debug_span, instrument, trace};
2022

@@ -300,65 +302,70 @@ 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

311313
impl 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+
#[instrument(level = "debug", target = "engine::caching", skip_all, fields(accounts = addresses.len()))]
358+
pub(crate) fn invalidate_storages(&self, addresses: HashSet<&Address>) {
359+
// NOTE: this must collect because the invalidate function should not be called while we
360+
// hold an iter for it
361+
let storage_entries = self
362+
.storage_cache
363+
.iter()
364+
.filter_map(|entry| addresses.contains(&entry.key().0).then_some(*entry.key()))
365+
.collect::<Vec<_>>();
366+
for key in storage_entries {
367+
self.storage_cache.invalidate(&key)
368+
}
362369
}
363370

364371
/// Inserts the post-execution state changes into the cache.
@@ -398,6 +405,7 @@ impl ExecutionCache {
398405
state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
399406
)
400407
.entered();
408+
let mut invalidated_accounts = HashSet::default();
401409
for (addr, account) in &state_updates.state {
402410
// If the account was not modified, as in not changed and not destroyed, then we have
403411
// nothing to do w.r.t. this particular account and can move on
@@ -410,7 +418,7 @@ impl ExecutionCache {
410418
// Invalidate the account cache entry if destroyed
411419
self.account_cache.invalidate(addr);
412420

413-
self.invalidate_account_storage(addr);
421+
invalidated_accounts.insert(addr);
414422
continue
415423
}
416424

@@ -437,6 +445,9 @@ impl ExecutionCache {
437445
self.account_cache.insert(*addr, Some(Account::from(account_info)));
438446
}
439447

448+
// invalidate storage for all destroyed accounts
449+
self.invalidate_storages(invalidated_accounts);
450+
440451
Ok(())
441452
}
442453
}
@@ -465,11 +476,11 @@ impl ExecutionCacheBuilder {
465476
const TIME_TO_IDLE: Duration = Duration::from_secs(3600); // 1 hour
466477

467478
let storage_cache = CacheBuilder::new(self.storage_cache_entries)
468-
.weigher(|_key: &Address, value: &AccountStorageCache| -> u32 {
469-
// values based on results from measure_storage_cache_overhead test
470-
let base_weight = 39_000;
471-
let slots_weight = value.len() * 218;
472-
(base_weight + slots_weight) as u32
479+
.weigher(|_key: &(Address, StorageKey), _value: &Option<StorageValue>| -> u32 {
480+
// Size of composite key (Address + StorageKey) + Option<StorageValue>
481+
// Address: 20 bytes, StorageKey: 32 bytes, Option<StorageValue>: 33 bytes
482+
// Plus some overhead for the hash map entry
483+
120_u32
473484
})
474485
.max_capacity(storage_cache_size)
475486
.time_to_live(EXPIRY_TIME)
@@ -592,56 +603,6 @@ impl SavedCache {
592603
}
593604
}
594605

595-
/// Cache for an individual account's storage slots.
596-
///
597-
/// This represents the second level of the hierarchical storage cache.
598-
/// Each account gets its own `AccountStorageCache` to store accessed storage slots.
599-
#[derive(Debug, Clone)]
600-
pub(crate) struct AccountStorageCache {
601-
/// Map of storage keys to their cached values.
602-
slots: Cache<StorageKey, Option<StorageValue>>,
603-
}
604-
605-
impl AccountStorageCache {
606-
/// Create a new [`AccountStorageCache`]
607-
pub(crate) fn new(max_slots: u64) -> Self {
608-
Self {
609-
slots: CacheBuilder::new(max_slots).build_with_hasher(DefaultHashBuilder::default()),
610-
}
611-
}
612-
613-
/// Get a storage value from this account's cache.
614-
/// - `NotCached`: The slot is not in the cache
615-
/// - `Empty`: The slot is empty
616-
/// - `Value`: The slot has a specific value
617-
pub(crate) fn get_storage(&self, key: &StorageKey) -> SlotStatus {
618-
match self.slots.get(key) {
619-
None => SlotStatus::NotCached,
620-
Some(None) => SlotStatus::Empty,
621-
Some(Some(value)) => SlotStatus::Value(value),
622-
}
623-
}
624-
625-
/// Insert a storage value
626-
pub(crate) fn insert_storage(&self, key: StorageKey, value: Option<StorageValue>) {
627-
self.slots.insert(key, value);
628-
}
629-
630-
/// Returns the number of slots in the cache
631-
pub(crate) fn len(&self) -> usize {
632-
self.slots.entry_count() as usize
633-
}
634-
}
635-
636-
impl Default for AccountStorageCache {
637-
fn default() -> Self {
638-
// With weigher and max_capacity in place, this number represents
639-
// the maximum number of entries that can be stored, not the actual
640-
// memory usage which is controlled by storage cache's max_capacity.
641-
Self::new(1_000_000)
642-
}
643-
}
644-
645606
#[cfg(test)]
646607
mod tests {
647608
use super::*;
@@ -716,32 +677,36 @@ mod tests {
716677

717678
#[test]
718679
fn measure_storage_cache_overhead() {
719-
let (base_overhead, cache) = measure_allocation(|| AccountStorageCache::new(1000));
720-
println!("Base AccountStorageCache overhead: {base_overhead} bytes");
680+
let (base_overhead, cache) =
681+
measure_allocation(|| ExecutionCacheBuilder::default().build_caches(1000));
682+
println!("Base ExecutionCache overhead: {base_overhead} bytes");
721683
let mut rng = rand::rng();
722684

685+
let address = Address::random();
723686
let key = StorageKey::random();
724687
let value = StorageValue::from(rng.random::<u128>());
725688
let (first_slot, _) = measure_allocation(|| {
726-
cache.insert_storage(key, Some(value));
689+
cache.insert_storage(address, key, Some(value));
727690
});
728691
println!("First slot insertion overhead: {first_slot} bytes");
729692

730693
const TOTAL_SLOTS: usize = 10_000;
731694
let (test_slots, _) = measure_allocation(|| {
732695
for _ in 0..TOTAL_SLOTS {
696+
let addr = Address::random();
733697
let key = StorageKey::random();
734698
let value = StorageValue::from(rng.random::<u128>());
735-
cache.insert_storage(key, Some(value));
699+
cache.insert_storage(addr, key, Some(value));
736700
}
737701
});
738702
println!("Average overhead over {} slots: {} bytes", TOTAL_SLOTS, test_slots / TOTAL_SLOTS);
739703

740704
println!("\nTheoretical sizes:");
705+
println!("Address size: {} bytes", size_of::<Address>());
741706
println!("StorageKey size: {} bytes", size_of::<StorageKey>());
742707
println!("StorageValue size: {} bytes", size_of::<StorageValue>());
743708
println!("Option<StorageValue> size: {} bytes", size_of::<Option<StorageValue>>());
744-
println!("Option<B256> size: {} bytes", size_of::<Option<B256>>());
709+
println!("(Address, StorageKey) size: {} bytes", size_of::<(Address, StorageKey)>());
745710
}
746711

747712
#[test]

0 commit comments

Comments
 (0)