1use alloy_primitives::{
3 map::{DefaultHashBuilder, FbBuildHasher},
4 Address, StorageKey, StorageValue, B256,
5};
6use fixed_cache::{AnyRef, CacheConfig, Stats, StatsHandler};
7use metrics::{Counter, Gauge, Histogram};
8use parking_lot::Once;
9use reth_errors::ProviderResult;
10use reth_metrics::Metrics;
11use reth_primitives_traits::{Account, Bytecode};
12use reth_provider::{
13 AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
14 StateProvider, StateRootProvider, StorageRootProvider,
15};
16use reth_revm::db::BundleState;
17use reth_trie::{
18 updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
19 MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
20};
21use std::{
22 sync::{
23 atomic::{AtomicU64, AtomicUsize, Ordering},
24 Arc,
25 },
26 time::Duration,
27};
28use tracing::{debug_span, instrument, trace, warn};
29
30const FIXED_CACHE_ALIGNMENT: usize = 128;
35
36const FIXED_CACHE_ENTRY_OVERHEAD: usize = size_of::<usize>();
38
39const fn fixed_cache_entry_size<K, V>() -> usize {
44 fixed_cache_key_size_with_value::<K>(size_of::<V>())
45}
46
47const fn fixed_cache_key_size_with_value<K>(value: usize) -> usize {
52 let raw_size = FIXED_CACHE_ENTRY_OVERHEAD + size_of::<K>() + value;
53 raw_size.div_ceil(FIXED_CACHE_ALIGNMENT) * FIXED_CACHE_ALIGNMENT
55}
56
57const ESTIMATED_AVG_CODE_SIZE: usize = 8 * 1024;
64
65const CODE_CACHE_ENTRY_SIZE: usize =
67 fixed_cache_key_size_with_value::<Address>(ESTIMATED_AVG_CODE_SIZE);
68
69const STORAGE_CACHE_ENTRY_SIZE: usize =
71 fixed_cache_entry_size::<(Address, StorageKey), StorageValue>();
72
73const ACCOUNT_CACHE_ENTRY_SIZE: usize = fixed_cache_entry_size::<Address, Option<Account>>();
75
76struct EpochCacheConfig;
78impl CacheConfig for EpochCacheConfig {
79 const EPOCHS: bool = true;
80}
81
82type FixedCache<K, V, H = DefaultHashBuilder> = fixed_cache::Cache<K, V, H, EpochCacheConfig>;
84
85#[derive(Debug)]
95pub struct CachedStateProvider<S, const PREWARM: bool = false> {
96 state_provider: S,
98
99 caches: ExecutionCache,
101
102 metrics: CachedStateMetrics,
104
105 cache_stats: Option<Arc<CacheStats>>,
108}
109
110impl<S> CachedStateProvider<S> {
111 pub const fn new(
114 state_provider: S,
115 caches: ExecutionCache,
116 metrics: CachedStateMetrics,
117 ) -> Self {
118 Self { state_provider, caches, metrics, cache_stats: None }
119 }
120}
121
122impl<S> CachedStateProvider<S, true> {
123 pub const fn new_prewarm(
125 state_provider: S,
126 caches: ExecutionCache,
127 metrics: CachedStateMetrics,
128 ) -> Self {
129 Self { state_provider, caches, metrics, cache_stats: None }
130 }
131}
132
133impl<S, const PREWARM: bool> CachedStateProvider<S, PREWARM> {
134 pub fn with_cache_stats(mut self, stats: Option<Arc<CacheStats>>) -> Self {
136 self.cache_stats = stats;
137 self
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum CachedStatus<T> {
144 NotCached(T),
146 Cached(T),
148}
149
150#[derive(Metrics, Clone)]
152#[metrics(scope = "sync.caching")]
153pub struct CachedStateMetrics {
154 execution_cache_created_total: Counter,
156
157 execution_cache_creation_duration_seconds: Histogram,
159
160 code_cache_hits: Gauge,
162
163 code_cache_misses: Gauge,
165
166 code_cache_size: Gauge,
168
169 code_cache_capacity: Gauge,
171
172 code_cache_collisions: Gauge,
174
175 storage_cache_hits: Gauge,
177
178 storage_cache_misses: Gauge,
180
181 storage_cache_size: Gauge,
183
184 storage_cache_capacity: Gauge,
186
187 storage_cache_collisions: Gauge,
189
190 account_cache_hits: Gauge,
192
193 account_cache_misses: Gauge,
195
196 account_cache_size: Gauge,
198
199 account_cache_capacity: Gauge,
201
202 account_cache_collisions: Gauge,
204}
205
206impl CachedStateMetrics {
207 pub fn reset(&self) {
209 self.code_cache_hits.set(0);
211 self.code_cache_misses.set(0);
212 self.code_cache_collisions.set(0);
213
214 self.storage_cache_hits.set(0);
216 self.storage_cache_misses.set(0);
217 self.storage_cache_collisions.set(0);
218
219 self.account_cache_hits.set(0);
221 self.account_cache_misses.set(0);
222 self.account_cache_collisions.set(0);
223 }
224
225 pub fn zeroed() -> Self {
227 let zeroed = Self::default();
228 zeroed.reset();
229 zeroed
230 }
231
232 pub(crate) fn record_cache_creation(&self, duration: Duration) {
234 self.execution_cache_created_total.increment(1);
235 self.execution_cache_creation_duration_seconds.record(duration.as_secs_f64());
236 }
237}
238
239#[derive(Debug, Default)]
241pub struct CacheStats {
242 account_hits: AtomicUsize,
244 account_misses: AtomicUsize,
246 storage_hits: AtomicUsize,
248 storage_misses: AtomicUsize,
250 code_hits: AtomicUsize,
252 code_misses: AtomicUsize,
254}
255
256impl CacheStats {
257 pub(crate) fn record_account_hit(&self) {
258 self.account_hits.fetch_add(1, Ordering::Relaxed);
259 }
260
261 pub(crate) fn record_account_miss(&self) {
262 self.account_misses.fetch_add(1, Ordering::Relaxed);
263 }
264
265 pub(crate) fn account_hits(&self) -> usize {
266 self.account_hits.load(Ordering::Relaxed)
267 }
268
269 pub(crate) fn account_misses(&self) -> usize {
270 self.account_misses.load(Ordering::Relaxed)
271 }
272
273 pub(crate) fn record_storage_hit(&self) {
274 self.storage_hits.fetch_add(1, Ordering::Relaxed);
275 }
276
277 pub(crate) fn record_storage_miss(&self) {
278 self.storage_misses.fetch_add(1, Ordering::Relaxed);
279 }
280
281 pub(crate) fn storage_hits(&self) -> usize {
282 self.storage_hits.load(Ordering::Relaxed)
283 }
284
285 pub(crate) fn storage_misses(&self) -> usize {
286 self.storage_misses.load(Ordering::Relaxed)
287 }
288
289 pub(crate) fn record_code_hit(&self) {
290 self.code_hits.fetch_add(1, Ordering::Relaxed);
291 }
292
293 pub(crate) fn record_code_miss(&self) {
294 self.code_misses.fetch_add(1, Ordering::Relaxed);
295 }
296
297 pub(crate) fn code_hits(&self) -> usize {
298 self.code_hits.load(Ordering::Relaxed)
299 }
300
301 pub(crate) fn code_misses(&self) -> usize {
302 self.code_misses.load(Ordering::Relaxed)
303 }
304}
305
306#[derive(Debug)]
321pub(crate) struct CacheStatsHandler {
322 collisions: AtomicU64,
323 size: AtomicUsize,
324 capacity: usize,
325}
326
327impl CacheStatsHandler {
328 pub(crate) const fn new(capacity: usize) -> Self {
330 Self { collisions: AtomicU64::new(0), size: AtomicUsize::new(0), capacity }
331 }
332
333 pub(crate) fn collisions(&self) -> u64 {
335 self.collisions.load(Ordering::Relaxed)
336 }
337
338 pub(crate) fn size(&self) -> usize {
340 self.size.load(Ordering::Relaxed)
341 }
342
343 pub(crate) const fn capacity(&self) -> usize {
345 self.capacity
346 }
347
348 pub(crate) fn increment_size(&self) {
350 let _ = self.size.fetch_add(1, Ordering::Relaxed);
351 }
352
353 pub(crate) fn decrement_size(&self) {
355 let _ = self.size.fetch_sub(1, Ordering::Relaxed);
356 }
357
358 pub(crate) fn reset_size(&self) {
360 self.size.store(0, Ordering::Relaxed);
361 }
362
363 pub(crate) fn reset_stats(&self) {
365 self.collisions.store(0, Ordering::Relaxed);
366 }
367}
368
369impl<K: PartialEq, V> StatsHandler<K, V> for CacheStatsHandler {
370 fn on_hit(&self, _key: &K, _value: &V) {}
371
372 fn on_miss(&self, _key: AnyRef<'_>) {}
373
374 fn on_insert(&self, key: &K, _value: &V, evicted: Option<(&K, &V)>) {
375 match evicted {
376 None => {
377 self.increment_size();
379 }
380 Some((evicted_key, _)) if evicted_key != key => {
381 self.collisions.fetch_add(1, Ordering::Relaxed);
383 }
384 Some(_) => {
385 }
387 }
388 }
389
390 fn on_remove(&self, _key: &K, _value: &V) {
391 self.decrement_size();
392 }
393}
394
395impl<S: AccountReader, const PREWARM: bool> AccountReader for CachedStateProvider<S, PREWARM> {
396 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
397 if PREWARM {
398 match self.caches.get_or_try_insert_account_with(*address, || {
399 self.state_provider.basic_account(address)
400 })? {
401 CachedStatus::NotCached(value) => {
403 if let Some(stats) = &self.cache_stats {
404 stats.record_account_miss();
405 }
406 Ok(value)
407 }
408 CachedStatus::Cached(value) => {
409 if let Some(stats) = &self.cache_stats {
410 stats.record_account_hit();
411 }
412 Ok(value)
413 }
414 }
415 } else if let Some(account) = self.caches.0.account_cache.get(address) {
416 self.metrics.account_cache_hits.increment(1);
417 if let Some(stats) = &self.cache_stats {
418 stats.record_account_hit();
419 }
420 Ok(account)
421 } else {
422 self.metrics.account_cache_misses.increment(1);
423 if let Some(stats) = &self.cache_stats {
424 stats.record_account_miss();
425 }
426 self.state_provider.basic_account(address)
427 }
428 }
429}
430
431impl<S: StateProvider, const PREWARM: bool> StateProvider for CachedStateProvider<S, PREWARM> {
432 fn storage(
433 &self,
434 account: Address,
435 storage_key: StorageKey,
436 ) -> ProviderResult<Option<StorageValue>> {
437 if PREWARM {
438 match self.caches.get_or_try_insert_storage_with(account, storage_key, || {
439 self.state_provider.storage(account, storage_key).map(Option::unwrap_or_default)
440 })? {
441 CachedStatus::NotCached(value) => {
443 if let Some(stats) = &self.cache_stats {
444 stats.record_storage_miss();
445 }
446 Ok(Some(value).filter(|v| !v.is_zero()))
447 }
448 CachedStatus::Cached(value) => {
449 if let Some(stats) = &self.cache_stats {
450 stats.record_storage_hit();
451 }
452 Ok(Some(value).filter(|v| !v.is_zero()))
453 }
454 }
455 } else if let Some(value) = self.caches.0.storage_cache.get(&(account, storage_key)) {
456 self.metrics.storage_cache_hits.increment(1);
457 if let Some(stats) = &self.cache_stats {
458 stats.record_storage_hit();
459 }
460 Ok(Some(value).filter(|v| !v.is_zero()))
461 } else {
462 self.metrics.storage_cache_misses.increment(1);
463 if let Some(stats) = &self.cache_stats {
464 stats.record_storage_miss();
465 }
466 self.state_provider.storage(account, storage_key)
467 }
468 }
469}
470
471impl<S: BytecodeReader, const PREWARM: bool> BytecodeReader for CachedStateProvider<S, PREWARM> {
472 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
473 if PREWARM {
474 match self.caches.get_or_try_insert_code_with(*code_hash, || {
475 self.state_provider.bytecode_by_hash(code_hash)
476 })? {
477 CachedStatus::NotCached(code) => {
479 if let Some(stats) = &self.cache_stats {
480 stats.record_code_miss();
481 }
482 Ok(code)
483 }
484 CachedStatus::Cached(code) => {
485 if let Some(stats) = &self.cache_stats {
486 stats.record_code_hit();
487 }
488 Ok(code)
489 }
490 }
491 } else if let Some(code) = self.caches.0.code_cache.get(code_hash) {
492 self.metrics.code_cache_hits.increment(1);
493 if let Some(stats) = &self.cache_stats {
494 stats.record_code_hit();
495 }
496 Ok(code)
497 } else {
498 self.metrics.code_cache_misses.increment(1);
499 if let Some(stats) = &self.cache_stats {
500 stats.record_code_miss();
501 }
502 self.state_provider.bytecode_by_hash(code_hash)
503 }
504 }
505}
506
507impl<S: StateRootProvider, const PREWARM: bool> StateRootProvider
508 for CachedStateProvider<S, PREWARM>
509{
510 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
511 self.state_provider.state_root(hashed_state)
512 }
513
514 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
515 self.state_provider.state_root_from_nodes(input)
516 }
517
518 fn state_root_with_updates(
519 &self,
520 hashed_state: HashedPostState,
521 ) -> ProviderResult<(B256, TrieUpdates)> {
522 self.state_provider.state_root_with_updates(hashed_state)
523 }
524
525 fn state_root_from_nodes_with_updates(
526 &self,
527 input: TrieInput,
528 ) -> ProviderResult<(B256, TrieUpdates)> {
529 self.state_provider.state_root_from_nodes_with_updates(input)
530 }
531}
532
533impl<S: StateProofProvider, const PREWARM: bool> StateProofProvider
534 for CachedStateProvider<S, PREWARM>
535{
536 fn proof(
537 &self,
538 input: TrieInput,
539 address: Address,
540 slots: &[B256],
541 ) -> ProviderResult<AccountProof> {
542 self.state_provider.proof(input, address, slots)
543 }
544
545 fn multiproof(
546 &self,
547 input: TrieInput,
548 targets: MultiProofTargets,
549 ) -> ProviderResult<MultiProof> {
550 self.state_provider.multiproof(input, targets)
551 }
552
553 fn witness(
554 &self,
555 input: TrieInput,
556 target: HashedPostState,
557 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
558 self.state_provider.witness(input, target)
559 }
560}
561
562impl<S: StorageRootProvider, const PREWARM: bool> StorageRootProvider
563 for CachedStateProvider<S, PREWARM>
564{
565 fn storage_root(
566 &self,
567 address: Address,
568 hashed_storage: HashedStorage,
569 ) -> ProviderResult<B256> {
570 self.state_provider.storage_root(address, hashed_storage)
571 }
572
573 fn storage_proof(
574 &self,
575 address: Address,
576 slot: B256,
577 hashed_storage: HashedStorage,
578 ) -> ProviderResult<StorageProof> {
579 self.state_provider.storage_proof(address, slot, hashed_storage)
580 }
581
582 fn storage_multiproof(
583 &self,
584 address: Address,
585 slots: &[B256],
586 hashed_storage: HashedStorage,
587 ) -> ProviderResult<StorageMultiProof> {
588 self.state_provider.storage_multiproof(address, slots, hashed_storage)
589 }
590}
591
592impl<S: BlockHashReader, const PREWARM: bool> BlockHashReader for CachedStateProvider<S, PREWARM> {
593 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
594 self.state_provider.block_hash(number)
595 }
596
597 fn canonical_hashes_range(
598 &self,
599 start: alloy_primitives::BlockNumber,
600 end: alloy_primitives::BlockNumber,
601 ) -> ProviderResult<Vec<B256>> {
602 self.state_provider.canonical_hashes_range(start, end)
603 }
604}
605
606impl<S: HashedPostStateProvider, const PREWARM: bool> HashedPostStateProvider
607 for CachedStateProvider<S, PREWARM>
608{
609 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
610 self.state_provider.hashed_post_state(bundle_state)
611 }
612}
613
614#[derive(Debug, Clone)]
625pub struct ExecutionCache(Arc<ExecutionCacheInner>);
626
627#[derive(Debug)]
629struct ExecutionCacheInner {
630 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
632
633 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
635
636 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
638
639 code_stats: Arc<CacheStatsHandler>,
641
642 storage_stats: Arc<CacheStatsHandler>,
644
645 account_stats: Arc<CacheStatsHandler>,
647
648 selfdestruct_encountered: Once,
650}
651
652impl ExecutionCache {
653 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
662 let entries = size_bytes / entry_size;
663 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
665 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
667 Self::MIN_CACHE_SIZE_WITH_EPOCHS
668 } else {
669 rounded
670 }
671 }
672
673 pub fn new(total_cache_size: usize) -> Self {
675 let code_cache_size = (total_cache_size * 556) / 10000; let storage_cache_size = (total_cache_size * 8888) / 10000; let account_cache_size = (total_cache_size * 556) / 10000; let code_capacity = Self::bytes_to_entries(code_cache_size, CODE_CACHE_ENTRY_SIZE);
680 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
681 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
682
683 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
684 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
685 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
686
687 Self(Arc::new(ExecutionCacheInner {
688 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
689 .with_stats(Some(Stats::new(code_stats.clone()))),
690 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
691 .with_stats(Some(Stats::new(storage_stats.clone()))),
692 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
693 .with_stats(Some(Stats::new(account_stats.clone()))),
694 code_stats,
695 storage_stats,
696 account_stats,
697 selfdestruct_encountered: Once::new(),
698 }))
699 }
700
701 pub fn get_or_try_insert_code_with<E>(
703 &self,
704 hash: B256,
705 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
706 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
707 let mut miss = false;
708 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
709 miss = true;
710 f()
711 })?;
712
713 if miss {
714 Ok(CachedStatus::NotCached(result))
715 } else {
716 Ok(CachedStatus::Cached(result))
717 }
718 }
719
720 pub fn get_or_try_insert_storage_with<E>(
722 &self,
723 address: Address,
724 key: StorageKey,
725 f: impl FnOnce() -> Result<StorageValue, E>,
726 ) -> Result<CachedStatus<StorageValue>, E> {
727 let mut miss = false;
728 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
729 miss = true;
730 f()
731 })?;
732
733 if miss {
734 Ok(CachedStatus::NotCached(result))
735 } else {
736 Ok(CachedStatus::Cached(result))
737 }
738 }
739
740 pub fn get_or_try_insert_account_with<E>(
742 &self,
743 address: Address,
744 f: impl FnOnce() -> Result<Option<Account>, E>,
745 ) -> Result<CachedStatus<Option<Account>>, E> {
746 let mut miss = false;
747 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
748 miss = true;
749 f()
750 })?;
751
752 if miss {
753 Ok(CachedStatus::NotCached(result))
754 } else {
755 Ok(CachedStatus::Cached(result))
756 }
757 }
758
759 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
761 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
762 }
763
764 fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
766 self.0.code_cache.insert(hash, code);
767 }
768
769 fn insert_account(&self, address: Address, account: Option<Account>) {
771 self.0.account_cache.insert(address, account);
772 }
773
774 #[instrument(level = "debug", target = "engine::caching", skip_all)]
793 #[expect(clippy::result_unit_err)]
794 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
795 let _enter =
796 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
797 .entered();
798 for (code_hash, bytecode) in &state_updates.contracts {
800 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
801 }
802 drop(_enter);
803
804 let _enter = debug_span!(
805 target: "engine::tree",
806 "accounts",
807 accounts = state_updates.state.len(),
808 storages =
809 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
810 )
811 .entered();
812 for (addr, account) in &state_updates.state {
813 if account.status.is_not_modified() {
816 continue
817 }
818
819 if account.was_destroyed() {
826 let had_code =
827 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
828 if had_code {
829 self.0.selfdestruct_encountered.call_once(|| {
830 warn!(
831 target: "engine::caching",
832 address = ?addr,
833 info = ?account.info,
834 original_info = ?account.original_info,
835 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
836 );
837 });
838 self.clear();
839 return Ok(())
840 }
841
842 self.0.account_cache.remove(addr);
843 self.0.account_stats.decrement_size();
844 continue;
845 }
846
847 let Some(ref account_info) = account.info else {
851 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
852 return Err(())
853 };
854
855 for (key, slot) in &account.storage {
857 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
858 }
859
860 self.insert_account(*addr, Some(Account::from(account_info)));
863 }
864
865 Ok(())
866 }
867
868 pub(crate) fn clear(&self) {
873 self.0.storage_cache.clear();
874 self.0.account_cache.clear();
875
876 self.0.storage_stats.reset_size();
877 self.0.account_stats.reset_size();
878 }
879
880 pub(crate) fn update_metrics(&self, metrics: &CachedStateMetrics) {
883 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
884 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
885 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
886 self.0.code_stats.reset_stats();
887
888 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
889 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
890 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
891 self.0.storage_stats.reset_stats();
892
893 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
894 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
895 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
896 self.0.account_stats.reset_stats();
897 }
898}
899
900#[derive(Debug, Clone)]
903pub struct SavedCache {
904 hash: B256,
906
907 caches: ExecutionCache,
909
910 metrics: CachedStateMetrics,
912
913 usage_guard: Arc<()>,
916
917 disable_cache_metrics: bool,
919}
920
921impl SavedCache {
922 pub fn new(hash: B256, caches: ExecutionCache, metrics: CachedStateMetrics) -> Self {
924 Self { hash, caches, metrics, usage_guard: Arc::new(()), disable_cache_metrics: false }
925 }
926
927 pub const fn with_disable_cache_metrics(mut self, disable: bool) -> Self {
929 self.disable_cache_metrics = disable;
930 self
931 }
932
933 pub const fn executed_block_hash(&self) -> B256 {
935 self.hash
936 }
937
938 pub fn split(self) -> (ExecutionCache, CachedStateMetrics, bool) {
940 (self.caches, self.metrics, self.disable_cache_metrics)
941 }
942
943 pub fn is_available(&self) -> bool {
945 Arc::strong_count(&self.usage_guard) == 1
946 }
947
948 pub fn usage_count(&self) -> usize {
950 Arc::strong_count(&self.usage_guard)
951 }
952
953 pub const fn cache(&self) -> &ExecutionCache {
955 &self.caches
956 }
957
958 pub const fn metrics(&self) -> &CachedStateMetrics {
960 &self.metrics
961 }
962
963 pub(crate) fn update_metrics(&self) {
968 if self.disable_cache_metrics {
969 return
970 }
971 self.caches.update_metrics(&self.metrics);
972 }
973
974 pub(crate) fn clear_with_hash(&mut self, hash: B256) {
977 self.hash = hash;
978 self.caches.clear();
979 }
980}
981
982#[cfg(test)]
983impl SavedCache {
984 fn clone_guard_for_test(&self) -> Arc<()> {
985 self.usage_guard.clone()
986 }
987}
988
989#[cfg(test)]
990mod tests {
991 use super::*;
992 use alloy_primitives::{map::HashMap, U256};
993 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
994 use reth_revm::db::{AccountStatus, BundleAccount};
995 use revm_state::AccountInfo;
996
997 #[test]
998 fn test_empty_storage_cached_state_provider() {
999 let address = Address::random();
1000 let storage_key = StorageKey::random();
1001 let account = ExtendedAccount::new(0, U256::ZERO);
1002
1003 let provider = MockEthProvider::default();
1004 provider.extend_accounts(vec![(address, account)]);
1005
1006 let caches = ExecutionCache::new(1000);
1007 let state_provider =
1008 CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
1009
1010 let res = state_provider.storage(address, storage_key);
1011 assert!(res.is_ok());
1012 assert_eq!(res.unwrap(), None);
1013 }
1014
1015 #[test]
1016 fn test_uncached_storage_cached_state_provider() {
1017 let address = Address::random();
1018 let storage_key = StorageKey::random();
1019 let storage_value = U256::from(1);
1020 let account =
1021 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1022
1023 let provider = MockEthProvider::default();
1024 provider.extend_accounts(vec![(address, account)]);
1025
1026 let caches = ExecutionCache::new(1000);
1027 let state_provider =
1028 CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
1029
1030 let res = state_provider.storage(address, storage_key);
1031 assert!(res.is_ok());
1032 assert_eq!(res.unwrap(), Some(storage_value));
1033 }
1034
1035 #[test]
1036 fn test_get_storage_populated() {
1037 let address = Address::random();
1038 let storage_key = StorageKey::random();
1039 let storage_value = U256::from(1);
1040
1041 let caches = ExecutionCache::new(1000);
1042 caches.insert_storage(address, storage_key, Some(storage_value));
1043
1044 let result = caches
1045 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1046 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1047 }
1048
1049 #[test]
1050 fn test_get_storage_empty() {
1051 let address = Address::random();
1052 let storage_key = StorageKey::random();
1053
1054 let caches = ExecutionCache::new(1000);
1055 caches.insert_storage(address, storage_key, None);
1056
1057 let result = caches
1058 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1059 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1060 }
1061
1062 #[test]
1063 fn test_saved_cache_is_available() {
1064 let execution_cache = ExecutionCache::new(1000);
1065 let cache = SavedCache::new(B256::ZERO, execution_cache, CachedStateMetrics::zeroed());
1066
1067 assert!(cache.is_available(), "Cache should be available initially");
1068
1069 let _guard = cache.clone_guard_for_test();
1070
1071 assert!(!cache.is_available(), "Cache should not be available with active guard");
1072 }
1073
1074 #[test]
1075 fn test_saved_cache_multiple_references() {
1076 let execution_cache = ExecutionCache::new(1000);
1077 let cache =
1078 SavedCache::new(B256::from([2u8; 32]), execution_cache, CachedStateMetrics::zeroed());
1079
1080 let guard1 = cache.clone_guard_for_test();
1081 let guard2 = cache.clone_guard_for_test();
1082 let guard3 = guard1.clone();
1083
1084 assert!(!cache.is_available());
1085
1086 drop(guard1);
1087 assert!(!cache.is_available());
1088
1089 drop(guard2);
1090 assert!(!cache.is_available());
1091
1092 drop(guard3);
1093 assert!(cache.is_available());
1094 }
1095
1096 #[test]
1097 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1098 let caches = ExecutionCache::new(1000);
1099
1100 let addr1 = Address::random();
1102 let addr2 = Address::random();
1103 let storage_key = StorageKey::random();
1104 caches.insert_account(addr1, Some(Account::default()));
1105 caches.insert_account(addr2, Some(Account::default()));
1106 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1107
1108 assert!(caches.0.account_cache.get(&addr1).is_some());
1110 assert!(caches.0.account_cache.get(&addr2).is_some());
1111 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1112
1113 let bundle = BundleState {
1114 state: HashMap::from_iter([(
1116 Address::random(),
1117 BundleAccount::new(
1118 Some(AccountInfo {
1119 balance: U256::ZERO,
1120 nonce: 1,
1121 code_hash: B256::random(), code: None,
1123 account_id: None,
1124 }),
1125 None, Default::default(),
1127 AccountStatus::Destroyed,
1128 ),
1129 )]),
1130 contracts: Default::default(),
1131 reverts: Default::default(),
1132 state_size: 0,
1133 reverts_size: 0,
1134 };
1135
1136 let result = caches.insert_state(&bundle);
1138 assert!(result.is_ok());
1139
1140 assert!(caches.0.account_cache.get(&addr1).is_none());
1142 assert!(caches.0.account_cache.get(&addr2).is_none());
1143 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1144 }
1145
1146 #[test]
1147 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1148 let caches = ExecutionCache::new(1000);
1149
1150 let addr1 = Address::random();
1152 let addr2 = Address::random();
1153 let storage_key = StorageKey::random();
1154 caches.insert_account(addr1, Some(Account::default()));
1155 caches.insert_account(addr2, Some(Account::default()));
1156 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1157
1158 let bundle = BundleState {
1159 state: HashMap::from_iter([(
1161 addr1,
1162 BundleAccount::new(
1163 Some(AccountInfo {
1164 balance: U256::from(100),
1165 nonce: 1,
1166 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1168 account_id: None,
1169 }),
1170 None, Default::default(),
1172 AccountStatus::Destroyed,
1173 ),
1174 )]),
1175 contracts: Default::default(),
1176 reverts: Default::default(),
1177 state_size: 0,
1178 reverts_size: 0,
1179 };
1180
1181 assert!(caches.insert_state(&bundle).is_ok());
1183
1184 assert!(caches.0.account_cache.get(&addr1).is_none());
1186 assert!(caches.0.account_cache.get(&addr2).is_some());
1187 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1188 }
1189
1190 #[test]
1191 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1192 let caches = ExecutionCache::new(1000);
1193
1194 let addr1 = Address::random();
1196 let addr2 = Address::random();
1197 caches.insert_account(addr1, Some(Account::default()));
1198 caches.insert_account(addr2, Some(Account::default()));
1199
1200 let bundle = BundleState {
1201 state: HashMap::from_iter([(
1203 addr1,
1204 BundleAccount::new(
1205 None, None, Default::default(),
1208 AccountStatus::Destroyed,
1209 ),
1210 )]),
1211 contracts: Default::default(),
1212 reverts: Default::default(),
1213 state_size: 0,
1214 reverts_size: 0,
1215 };
1216
1217 assert!(caches.insert_state(&bundle).is_ok());
1219
1220 assert!(caches.0.account_cache.get(&addr1).is_none());
1222 assert!(caches.0.account_cache.get(&addr2).is_some());
1223 }
1224
1225 #[test]
1226 fn test_code_cache_capacity_with_default_budget() {
1227 let total_cache_size = 4 * 1024 * 1024 * 1024; let code_budget = (total_cache_size * 556) / 10000; let capacity = ExecutionCache::bytes_to_entries(code_budget, CODE_CACHE_ENTRY_SIZE);
1232
1233 assert_eq!(
1236 capacity, 16384,
1237 "code cache should have 16384 entries with default 4 GB budget"
1238 );
1239 }
1240}