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 fmt,
23 sync::{
24 atomic::{AtomicU64, AtomicUsize, Ordering},
25 Arc,
26 },
27 time::Duration,
28};
29use tracing::{debug_span, instrument, trace, warn};
30
31const FIXED_CACHE_ALIGNMENT: usize = 128;
36
37const FIXED_CACHE_ENTRY_OVERHEAD: usize = size_of::<usize>();
39
40const fn fixed_cache_entry_size<K, V>() -> usize {
45 fixed_cache_key_size_with_value::<K>(size_of::<V>())
46}
47
48const fn fixed_cache_key_size_with_value<K>(value: usize) -> usize {
53 let raw_size = FIXED_CACHE_ENTRY_OVERHEAD + size_of::<K>() + value;
54 raw_size.div_ceil(FIXED_CACHE_ALIGNMENT) * FIXED_CACHE_ALIGNMENT
56}
57
58const ESTIMATED_AVG_CODE_SIZE: usize = 8 * 1024;
65
66const CODE_CACHE_ENTRY_SIZE: usize =
68 fixed_cache_key_size_with_value::<Address>(ESTIMATED_AVG_CODE_SIZE);
69
70const STORAGE_CACHE_ENTRY_SIZE: usize =
72 fixed_cache_entry_size::<(Address, StorageKey), StorageValue>();
73
74const ACCOUNT_CACHE_ENTRY_SIZE: usize = fixed_cache_entry_size::<Address, Option<Account>>();
76
77struct EpochCacheConfig;
79impl CacheConfig for EpochCacheConfig {
80 const EPOCHS: bool = true;
81}
82
83type FixedCache<K, V, H = DefaultHashBuilder> = fixed_cache::Cache<K, V, H, EpochCacheConfig>;
85
86#[derive(Debug)]
97pub struct CachedStateProvider<S> {
98 state_provider: S,
100
101 caches: ExecutionCache,
103
104 metrics: Option<CachedStateMetrics>,
106
107 fill_mode: CacheFillMode,
109
110 cache_stats: Option<Arc<CacheStats>>,
113}
114
115impl<S> CachedStateProvider<S> {
116 pub const fn new(
119 state_provider: S,
120 caches: ExecutionCache,
121 metrics: Option<CachedStateMetrics>,
122 ) -> Self {
123 Self::new_with_mode(state_provider, caches, CacheFillMode::LookupOnly, metrics, None)
124 }
125
126 pub const fn new_prewarm(state_provider: S, caches: ExecutionCache) -> Self {
130 Self::new_with_mode(state_provider, caches, CacheFillMode::FillOnMiss, None, None)
131 }
132
133 pub const fn new_with_mode(
136 state_provider: S,
137 caches: ExecutionCache,
138 fill_mode: CacheFillMode,
139 metrics: Option<CachedStateMetrics>,
140 cache_stats: Option<Arc<CacheStats>>,
141 ) -> Self {
142 Self { state_provider, caches, metrics, fill_mode, cache_stats }
143 }
144
145 fn record_account_hit(&self) {
146 if let Some(metrics) = &self.metrics {
147 metrics.account_cache_hits.increment(1);
148 }
149 if let Some(stats) = &self.cache_stats {
150 stats.record_account_hit();
151 }
152 }
153
154 fn record_account_miss(&self) {
155 if let Some(metrics) = &self.metrics {
156 metrics.account_cache_misses.increment(1);
157 }
158 if let Some(stats) = &self.cache_stats {
159 stats.record_account_miss();
160 }
161 }
162
163 fn record_storage_hit(&self) {
164 if let Some(metrics) = &self.metrics {
165 metrics.storage_cache_hits.increment(1);
166 }
167 if let Some(stats) = &self.cache_stats {
168 stats.record_storage_hit();
169 }
170 }
171
172 fn record_storage_miss(&self) {
173 if let Some(metrics) = &self.metrics {
174 metrics.storage_cache_misses.increment(1);
175 }
176 if let Some(stats) = &self.cache_stats {
177 stats.record_storage_miss();
178 }
179 }
180
181 fn record_code_hit(&self) {
182 if let Some(metrics) = &self.metrics {
183 metrics.code_cache_hits.increment(1);
184 }
185 if let Some(stats) = &self.cache_stats {
186 stats.record_code_hit();
187 }
188 }
189
190 fn record_code_miss(&self) {
191 if let Some(metrics) = &self.metrics {
192 metrics.code_cache_misses.increment(1);
193 }
194 if let Some(stats) = &self.cache_stats {
195 stats.record_code_miss();
196 }
197 }
198
199 const fn should_fill_on_miss(&self) -> bool {
200 matches!(self.fill_mode, CacheFillMode::FillOnMiss)
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum CacheFillMode {
207 LookupOnly,
209 FillOnMiss,
211}
212
213#[derive(Debug, Clone, PartialEq, Eq)]
215pub enum CachedStatus<T> {
216 NotCached(T),
218 Cached(T),
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
224pub enum CachedStateMetricsSource {
225 Engine,
227 Builder,
229 #[cfg(any(test, feature = "test-utils"))]
231 Test,
232}
233
234impl fmt::Display for CachedStateMetricsSource {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 match self {
237 Self::Engine => f.write_str("engine"),
238 Self::Builder => f.write_str("builder"),
239 #[cfg(any(test, feature = "test-utils"))]
240 Self::Test => f.write_str("test"),
241 }
242 }
243}
244
245#[derive(Metrics, Clone)]
247#[metrics(scope = "sync.caching")]
248pub struct CachedStateMetrics {
249 execution_cache_created_total: Counter,
251
252 execution_cache_creation_duration_seconds: Histogram,
254
255 code_cache_hits: Gauge,
257
258 code_cache_misses: Gauge,
260
261 storage_cache_hits: Gauge,
263
264 storage_cache_misses: Gauge,
266
267 account_cache_hits: Gauge,
269
270 account_cache_misses: Gauge,
272}
273
274#[derive(Metrics, Clone)]
276#[metrics(scope = "sync.caching")]
277pub struct CachedStateCacheMetrics {
278 code_cache_size: Gauge,
280
281 code_cache_capacity: Gauge,
283
284 code_cache_collisions: Gauge,
286
287 storage_cache_size: Gauge,
289
290 storage_cache_capacity: Gauge,
292
293 storage_cache_collisions: Gauge,
295
296 account_cache_size: Gauge,
298
299 account_cache_capacity: Gauge,
301
302 account_cache_collisions: Gauge,
304}
305
306impl CachedStateMetrics {
307 pub fn reset(&self) {
309 self.code_cache_hits.set(0);
311 self.code_cache_misses.set(0);
312
313 self.storage_cache_hits.set(0);
315 self.storage_cache_misses.set(0);
316
317 self.account_cache_hits.set(0);
319 self.account_cache_misses.set(0);
320 }
321
322 pub fn zeroed(source: CachedStateMetricsSource) -> Self {
325 let zeroed = Self::new_with_labels(&[("source", source.to_string())]);
326 zeroed.reset();
327 zeroed
328 }
329
330 pub fn record_cache_creation(&self, duration: Duration) {
332 self.execution_cache_created_total.increment(1);
333 self.execution_cache_creation_duration_seconds.record(duration.as_secs_f64());
334 }
335}
336
337#[derive(Debug, Default)]
339pub struct CacheStats {
340 account_hits: AtomicUsize,
342 account_misses: AtomicUsize,
344 storage_hits: AtomicUsize,
346 storage_misses: AtomicUsize,
348 code_hits: AtomicUsize,
350 code_misses: AtomicUsize,
352}
353
354impl CacheStats {
355 pub fn record_account_hit(&self) {
357 self.account_hits.fetch_add(1, Ordering::Relaxed);
358 }
359
360 pub fn record_account_miss(&self) {
362 self.account_misses.fetch_add(1, Ordering::Relaxed);
363 }
364
365 pub fn account_hits(&self) -> usize {
367 self.account_hits.load(Ordering::Relaxed)
368 }
369
370 pub fn account_misses(&self) -> usize {
372 self.account_misses.load(Ordering::Relaxed)
373 }
374
375 pub fn record_storage_hit(&self) {
377 self.storage_hits.fetch_add(1, Ordering::Relaxed);
378 }
379
380 pub fn record_storage_miss(&self) {
382 self.storage_misses.fetch_add(1, Ordering::Relaxed);
383 }
384
385 pub fn storage_hits(&self) -> usize {
387 self.storage_hits.load(Ordering::Relaxed)
388 }
389
390 pub fn storage_misses(&self) -> usize {
392 self.storage_misses.load(Ordering::Relaxed)
393 }
394
395 pub fn record_code_hit(&self) {
397 self.code_hits.fetch_add(1, Ordering::Relaxed);
398 }
399
400 pub fn record_code_miss(&self) {
402 self.code_misses.fetch_add(1, Ordering::Relaxed);
403 }
404
405 pub fn code_hits(&self) -> usize {
407 self.code_hits.load(Ordering::Relaxed)
408 }
409
410 pub fn code_misses(&self) -> usize {
412 self.code_misses.load(Ordering::Relaxed)
413 }
414}
415
416#[derive(Debug)]
431pub struct CacheStatsHandler {
432 collisions: AtomicU64,
433 size: AtomicUsize,
434 capacity: usize,
435}
436
437impl CacheStatsHandler {
438 pub const fn new(capacity: usize) -> Self {
440 Self { collisions: AtomicU64::new(0), size: AtomicUsize::new(0), capacity }
441 }
442
443 pub fn collisions(&self) -> u64 {
445 self.collisions.load(Ordering::Relaxed)
446 }
447
448 pub fn size(&self) -> usize {
450 self.size.load(Ordering::Relaxed)
451 }
452
453 pub const fn capacity(&self) -> usize {
455 self.capacity
456 }
457
458 pub fn increment_size(&self) {
460 let _ = self.size.fetch_add(1, Ordering::Relaxed);
461 }
462
463 pub fn decrement_size(&self) {
465 let _ = self.size.fetch_sub(1, Ordering::Relaxed);
466 }
467
468 pub fn reset_size(&self) {
470 self.size.store(0, Ordering::Relaxed);
471 }
472
473 pub fn reset_stats(&self) {
475 self.collisions.store(0, Ordering::Relaxed);
476 }
477}
478
479impl<K: PartialEq, V> StatsHandler<K, V> for CacheStatsHandler {
480 fn on_hit(&self, _key: &K, _value: &V) {}
481
482 fn on_miss(&self, _key: AnyRef<'_>) {}
483
484 fn on_insert(&self, key: &K, _value: &V, evicted: Option<(&K, &V)>) {
485 match evicted {
486 None => {
487 self.increment_size();
489 }
490 Some((evicted_key, _)) if evicted_key != key => {
491 self.collisions.fetch_add(1, Ordering::Relaxed);
493 }
494 Some(_) => {
495 }
497 }
498 }
499
500 fn on_remove(&self, _key: &K, _value: &V) {
501 self.decrement_size();
502 }
503}
504
505impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
506 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
507 if self.should_fill_on_miss() {
508 match self.caches.get_or_try_insert_account_with(*address, || {
509 self.state_provider.basic_account(address)
510 })? {
511 CachedStatus::NotCached(value) => {
512 self.record_account_miss();
513 Ok(value)
514 }
515 CachedStatus::Cached(value) => {
516 self.record_account_hit();
517 Ok(value)
518 }
519 }
520 } else if let Some(account) = self.caches.0.account_cache.get(address) {
521 self.record_account_hit();
522 Ok(account)
523 } else {
524 self.record_account_miss();
525 self.state_provider.basic_account(address)
526 }
527 }
528}
529
530#[inline]
531fn nonzero_storage_value(value: StorageValue) -> Option<StorageValue> {
532 if value.is_zero() {
533 None
534 } else {
535 Some(value)
536 }
537}
538
539impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
540 fn storage(
541 &self,
542 account: Address,
543 storage_key: StorageKey,
544 ) -> ProviderResult<Option<StorageValue>> {
545 if self.should_fill_on_miss() {
546 match self.caches.get_or_try_insert_storage_with(account, storage_key, || {
547 self.state_provider.storage(account, storage_key).map(Option::unwrap_or_default)
548 })? {
549 CachedStatus::NotCached(value) => {
550 self.record_storage_miss();
551 Ok(nonzero_storage_value(value))
552 }
553 CachedStatus::Cached(value) => {
554 self.record_storage_hit();
555 Ok(nonzero_storage_value(value))
556 }
557 }
558 } else if let Some(value) = self.caches.0.storage_cache.get(&(account, storage_key)) {
559 self.record_storage_hit();
560 Ok(nonzero_storage_value(value))
561 } else {
562 self.record_storage_miss();
563 self.state_provider.storage(account, storage_key)
564 }
565 }
566}
567
568impl<S: BytecodeReader> BytecodeReader for CachedStateProvider<S> {
569 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
570 if self.should_fill_on_miss() {
571 match self.caches.get_or_try_insert_code_with(*code_hash, || {
572 self.state_provider.bytecode_by_hash(code_hash)
573 })? {
574 CachedStatus::NotCached(code) => {
575 self.record_code_miss();
576 Ok(code)
577 }
578 CachedStatus::Cached(code) => {
579 self.record_code_hit();
580 Ok(code)
581 }
582 }
583 } else if let Some(code) = self.caches.0.code_cache.get(code_hash) {
584 self.record_code_hit();
585 Ok(code)
586 } else {
587 self.record_code_miss();
588 self.state_provider.bytecode_by_hash(code_hash)
589 }
590 }
591}
592
593impl<S: StateRootProvider> StateRootProvider for CachedStateProvider<S> {
594 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
595 self.state_provider.state_root(hashed_state)
596 }
597
598 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
599 self.state_provider.state_root_from_nodes(input)
600 }
601
602 fn state_root_with_updates(
603 &self,
604 hashed_state: HashedPostState,
605 ) -> ProviderResult<(B256, TrieUpdates)> {
606 self.state_provider.state_root_with_updates(hashed_state)
607 }
608
609 fn state_root_from_nodes_with_updates(
610 &self,
611 input: TrieInput,
612 ) -> ProviderResult<(B256, TrieUpdates)> {
613 self.state_provider.state_root_from_nodes_with_updates(input)
614 }
615}
616
617impl<S: StateProofProvider> StateProofProvider for CachedStateProvider<S> {
618 fn proof(
619 &self,
620 input: TrieInput,
621 address: Address,
622 slots: &[B256],
623 ) -> ProviderResult<AccountProof> {
624 self.state_provider.proof(input, address, slots)
625 }
626
627 fn multiproof(
628 &self,
629 input: TrieInput,
630 targets: MultiProofTargets,
631 ) -> ProviderResult<MultiProof> {
632 self.state_provider.multiproof(input, targets)
633 }
634
635 fn witness(
636 &self,
637 input: TrieInput,
638 target: HashedPostState,
639 mode: reth_trie::ExecutionWitnessMode,
640 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
641 self.state_provider.witness(input, target, mode)
642 }
643}
644
645impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
646 fn storage_root(
647 &self,
648 address: Address,
649 hashed_storage: HashedStorage,
650 ) -> ProviderResult<B256> {
651 self.state_provider.storage_root(address, hashed_storage)
652 }
653
654 fn storage_proof(
655 &self,
656 address: Address,
657 slot: B256,
658 hashed_storage: HashedStorage,
659 ) -> ProviderResult<StorageProof> {
660 self.state_provider.storage_proof(address, slot, hashed_storage)
661 }
662
663 fn storage_multiproof(
664 &self,
665 address: Address,
666 slots: &[B256],
667 hashed_storage: HashedStorage,
668 ) -> ProviderResult<StorageMultiProof> {
669 self.state_provider.storage_multiproof(address, slots, hashed_storage)
670 }
671}
672
673impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
674 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
675 self.state_provider.block_hash(number)
676 }
677
678 fn canonical_hashes_range(
679 &self,
680 start: alloy_primitives::BlockNumber,
681 end: alloy_primitives::BlockNumber,
682 ) -> ProviderResult<Vec<B256>> {
683 self.state_provider.canonical_hashes_range(start, end)
684 }
685}
686
687impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
688 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
689 self.state_provider.hashed_post_state(bundle_state)
690 }
691}
692
693#[derive(Debug, Clone)]
704pub struct ExecutionCache(Arc<ExecutionCacheInner>);
705
706#[derive(Debug)]
708struct ExecutionCacheInner {
709 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
711
712 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
714
715 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
717
718 code_stats: Arc<CacheStatsHandler>,
720
721 storage_stats: Arc<CacheStatsHandler>,
723
724 account_stats: Arc<CacheStatsHandler>,
726
727 selfdestruct_encountered: Once,
729}
730
731impl ExecutionCache {
732 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
741 let entries = size_bytes / entry_size;
742 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
744 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
746 Self::MIN_CACHE_SIZE_WITH_EPOCHS
747 } else {
748 rounded
749 }
750 }
751
752 pub fn new(total_cache_size: usize) -> Self {
754 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);
759 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
760 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
761
762 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
763 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
764 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
765
766 Self(Arc::new(ExecutionCacheInner {
767 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
768 .with_stats(Some(Stats::new(code_stats.clone()))),
769 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
770 .with_stats(Some(Stats::new(storage_stats.clone()))),
771 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
772 .with_stats(Some(Stats::new(account_stats.clone()))),
773 code_stats,
774 storage_stats,
775 account_stats,
776 selfdestruct_encountered: Once::new(),
777 }))
778 }
779
780 pub fn get_or_try_insert_code_with<E>(
782 &self,
783 hash: B256,
784 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
785 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
786 let mut miss = false;
787 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
788 miss = true;
789 f()
790 })?;
791
792 if miss {
793 Ok(CachedStatus::NotCached(result))
794 } else {
795 Ok(CachedStatus::Cached(result))
796 }
797 }
798
799 pub fn get_or_try_insert_storage_with<E>(
801 &self,
802 address: Address,
803 key: StorageKey,
804 f: impl FnOnce() -> Result<StorageValue, E>,
805 ) -> Result<CachedStatus<StorageValue>, E> {
806 let mut miss = false;
807 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
808 miss = true;
809 f()
810 })?;
811
812 if miss {
813 Ok(CachedStatus::NotCached(result))
814 } else {
815 Ok(CachedStatus::Cached(result))
816 }
817 }
818
819 pub fn get_or_try_insert_account_with<E>(
821 &self,
822 address: Address,
823 f: impl FnOnce() -> Result<Option<Account>, E>,
824 ) -> Result<CachedStatus<Option<Account>>, E> {
825 let mut miss = false;
826 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
827 miss = true;
828 f()
829 })?;
830
831 if miss {
832 Ok(CachedStatus::NotCached(result))
833 } else {
834 Ok(CachedStatus::Cached(result))
835 }
836 }
837
838 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
840 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
841 }
842
843 pub fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
845 self.0.code_cache.insert(hash, code);
846 }
847
848 pub fn insert_account(&self, address: Address, account: Option<Account>) {
850 self.0.account_cache.insert(address, account);
851 }
852
853 #[instrument(level = "debug", target = "engine::caching", skip_all)]
872 #[expect(clippy::result_unit_err)]
873 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
874 let _enter =
875 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
876 .entered();
877 for (code_hash, bytecode) in &state_updates.contracts {
879 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
880 }
881 drop(_enter);
882
883 let _enter = debug_span!(
884 target: "engine::tree",
885 "accounts",
886 accounts = state_updates.state.len(),
887 storages =
888 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
889 )
890 .entered();
891 for (addr, account) in &state_updates.state {
892 if account.status.is_not_modified() {
895 continue
896 }
897
898 if account.was_destroyed() {
905 let had_code =
906 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
907 if had_code {
908 self.0.selfdestruct_encountered.call_once(|| {
909 warn!(
910 target: "engine::caching",
911 address = ?addr,
912 info = ?account.info,
913 original_info = ?account.original_info,
914 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
915 );
916 });
917 self.clear();
918 return Ok(())
919 }
920
921 self.0.account_cache.remove(addr);
922 continue;
923 }
924
925 let Some(ref account_info) = account.info else {
929 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
930 return Err(())
931 };
932
933 for (key, slot) in &account.storage {
935 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
936 }
937
938 self.insert_account(*addr, Some(Account::from(account_info)));
941 }
942
943 Ok(())
944 }
945
946 pub fn clear(&self) {
951 self.0.storage_cache.clear();
952 self.0.account_cache.clear();
953
954 self.0.storage_stats.reset_size();
955 self.0.account_stats.reset_size();
956 }
957
958 pub fn update_metrics(&self, metrics: &CachedStateCacheMetrics) {
961 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
962 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
963 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
964 self.0.code_stats.reset_stats();
965
966 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
967 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
968 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
969 self.0.storage_stats.reset_stats();
970
971 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
972 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
973 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
974 self.0.account_stats.reset_stats();
975 }
976}
977
978#[derive(Debug, Clone)]
981pub struct SavedCache {
982 hash: B256,
984
985 caches: ExecutionCache,
987
988 usage_guard: Arc<()>,
991}
992
993impl SavedCache {
994 pub fn new(hash: B256, caches: ExecutionCache) -> Self {
996 Self { hash, caches, usage_guard: Arc::new(()) }
997 }
998
999 pub const fn executed_block_hash(&self) -> B256 {
1001 self.hash
1002 }
1003
1004 pub fn is_available(&self) -> bool {
1006 Arc::strong_count(&self.usage_guard) == 1
1007 }
1008
1009 pub fn usage_count(&self) -> usize {
1011 Arc::strong_count(&self.usage_guard)
1012 }
1013
1014 pub const fn cache(&self) -> &ExecutionCache {
1016 &self.caches
1017 }
1018
1019 pub fn update_metrics(&self, metrics: Option<&CachedStateCacheMetrics>) {
1021 if let Some(metrics) = metrics {
1022 self.caches.update_metrics(metrics);
1023 }
1024 }
1025
1026 pub fn clear_with_hash(&mut self, hash: B256) {
1029 self.hash = hash;
1030 self.caches.clear();
1031 }
1032}
1033
1034#[cfg(any(test, feature = "test-utils"))]
1035impl SavedCache {
1036 pub fn clone_guard_for_test(&self) -> Arc<()> {
1038 self.usage_guard.clone()
1039 }
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044 use super::*;
1045 use alloy_primitives::{map::HashMap, U256};
1046 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1047 use reth_revm::db::{AccountStatus, BundleAccount};
1048 use revm_state::AccountInfo;
1049
1050 #[test]
1051 fn test_empty_storage_cached_state_provider() {
1052 let address = Address::random();
1053 let storage_key = StorageKey::random();
1054 let account = ExtendedAccount::new(0, U256::ZERO);
1055
1056 let provider = MockEthProvider::default();
1057 provider.extend_accounts(vec![(address, account)]);
1058
1059 let caches = ExecutionCache::new(1000);
1060 let state_provider = CachedStateProvider::new(
1061 provider,
1062 caches,
1063 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1064 );
1065
1066 let res = state_provider.storage(address, storage_key);
1067 assert!(res.is_ok());
1068 assert_eq!(res.unwrap(), None);
1069 }
1070
1071 #[test]
1072 fn test_uncached_storage_cached_state_provider() {
1073 let address = Address::random();
1074 let storage_key = StorageKey::random();
1075 let storage_value = U256::from(1);
1076 let account =
1077 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1078
1079 let provider = MockEthProvider::default();
1080 provider.extend_accounts(vec![(address, account)]);
1081
1082 let caches = ExecutionCache::new(1000);
1083 let state_provider = CachedStateProvider::new(
1084 provider,
1085 caches,
1086 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1087 );
1088
1089 let res = state_provider.storage(address, storage_key);
1090 assert!(res.is_ok());
1091 assert_eq!(res.unwrap(), Some(storage_value));
1092 }
1093
1094 #[test]
1095 fn test_get_storage_populated() {
1096 let address = Address::random();
1097 let storage_key = StorageKey::random();
1098 let storage_value = U256::from(1);
1099
1100 let caches = ExecutionCache::new(1000);
1101 caches.insert_storage(address, storage_key, Some(storage_value));
1102
1103 let result = caches
1104 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1105 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1106 }
1107
1108 #[test]
1109 fn test_get_storage_empty() {
1110 let address = Address::random();
1111 let storage_key = StorageKey::random();
1112
1113 let caches = ExecutionCache::new(1000);
1114 caches.insert_storage(address, storage_key, None);
1115
1116 let result = caches
1117 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1118 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1119 }
1120
1121 #[test]
1122 fn test_saved_cache_is_available() {
1123 let execution_cache = ExecutionCache::new(1000);
1124 let cache = SavedCache::new(B256::ZERO, execution_cache);
1125
1126 assert!(cache.is_available(), "Cache should be available initially");
1127
1128 let _guard = cache.clone_guard_for_test();
1129
1130 assert!(!cache.is_available(), "Cache should not be available with active guard");
1131 }
1132
1133 #[test]
1134 fn test_saved_cache_multiple_references() {
1135 let execution_cache = ExecutionCache::new(1000);
1136 let cache = SavedCache::new(B256::from([2u8; 32]), execution_cache);
1137
1138 let guard1 = cache.clone_guard_for_test();
1139 let guard2 = cache.clone_guard_for_test();
1140 let guard3 = guard1.clone();
1141
1142 assert!(!cache.is_available());
1143
1144 drop(guard1);
1145 assert!(!cache.is_available());
1146
1147 drop(guard2);
1148 assert!(!cache.is_available());
1149
1150 drop(guard3);
1151 assert!(cache.is_available());
1152 }
1153
1154 #[test]
1155 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1156 let caches = ExecutionCache::new(1000);
1157
1158 let addr1 = Address::random();
1160 let addr2 = Address::random();
1161 let storage_key = StorageKey::random();
1162 caches.insert_account(addr1, Some(Account::default()));
1163 caches.insert_account(addr2, Some(Account::default()));
1164 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1165
1166 assert!(caches.0.account_cache.get(&addr1).is_some());
1168 assert!(caches.0.account_cache.get(&addr2).is_some());
1169 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1170
1171 let bundle = BundleState {
1172 state: HashMap::from_iter([(
1174 Address::random(),
1175 BundleAccount::new(
1176 Some(AccountInfo {
1177 balance: U256::ZERO,
1178 nonce: 1,
1179 code_hash: B256::random(), code: None,
1181 account_id: None,
1182 }),
1183 None, Default::default(),
1185 AccountStatus::Destroyed,
1186 ),
1187 )]),
1188 contracts: Default::default(),
1189 reverts: Default::default(),
1190 state_size: 0,
1191 reverts_size: 0,
1192 };
1193
1194 let result = caches.insert_state(&bundle);
1196 assert!(result.is_ok());
1197
1198 assert!(caches.0.account_cache.get(&addr1).is_none());
1200 assert!(caches.0.account_cache.get(&addr2).is_none());
1201 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1202 }
1203
1204 #[test]
1205 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1206 let caches = ExecutionCache::new(1000);
1207
1208 let addr1 = Address::random();
1210 let addr2 = Address::random();
1211 let storage_key = StorageKey::random();
1212 caches.insert_account(addr1, Some(Account::default()));
1213 caches.insert_account(addr2, Some(Account::default()));
1214 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1215
1216 let bundle = BundleState {
1217 state: HashMap::from_iter([(
1219 addr1,
1220 BundleAccount::new(
1221 Some(AccountInfo {
1222 balance: U256::from(100),
1223 nonce: 1,
1224 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1226 account_id: None,
1227 }),
1228 None, Default::default(),
1230 AccountStatus::Destroyed,
1231 ),
1232 )]),
1233 contracts: Default::default(),
1234 reverts: Default::default(),
1235 state_size: 0,
1236 reverts_size: 0,
1237 };
1238
1239 assert!(caches.insert_state(&bundle).is_ok());
1241
1242 assert!(caches.0.account_cache.get(&addr1).is_none());
1244 assert!(caches.0.account_cache.get(&addr2).is_some());
1245 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1246 }
1247
1248 #[test]
1249 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1250 let caches = ExecutionCache::new(1000);
1251
1252 let addr1 = Address::random();
1254 let addr2 = Address::random();
1255 caches.insert_account(addr1, Some(Account::default()));
1256 caches.insert_account(addr2, Some(Account::default()));
1257
1258 let bundle = BundleState {
1259 state: HashMap::from_iter([(
1261 addr1,
1262 BundleAccount::new(
1263 None, None, Default::default(),
1266 AccountStatus::Destroyed,
1267 ),
1268 )]),
1269 contracts: Default::default(),
1270 reverts: Default::default(),
1271 state_size: 0,
1272 reverts_size: 0,
1273 };
1274
1275 assert!(caches.insert_state(&bundle).is_ok());
1277
1278 assert!(caches.0.account_cache.get(&addr1).is_none());
1280 assert!(caches.0.account_cache.get(&addr2).is_some());
1281 }
1282
1283 #[test]
1284 fn test_insert_state_destroyed_uncached_account_keeps_size_zero() {
1285 let caches = ExecutionCache::new(1000);
1286 assert_eq!(caches.0.account_stats.size(), 0);
1287
1288 let addr = Address::random();
1289 let bundle = BundleState {
1290 state: HashMap::from_iter([(
1291 addr,
1292 BundleAccount::new(
1293 None, None, Default::default(),
1296 AccountStatus::Destroyed,
1297 ),
1298 )]),
1299 contracts: Default::default(),
1300 reverts: Default::default(),
1301 state_size: 0,
1302 reverts_size: 0,
1303 };
1304
1305 assert!(caches.insert_state(&bundle).is_ok());
1306 assert_eq!(caches.0.account_stats.size(), 0);
1307 assert!(caches.0.account_cache.get(&addr).is_none());
1308 }
1309
1310 #[test]
1311 fn test_code_cache_capacity_with_default_budget() {
1312 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);
1317
1318 assert_eq!(
1321 capacity, 16384,
1322 "code cache should have 16384 entries with default 4 GB budget"
1323 );
1324 }
1325}