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)]
96pub struct CachedStateProvider<S, const PREWARM: bool = false> {
97 state_provider: S,
99
100 caches: ExecutionCache,
102
103 metrics: CachedStateMetrics,
105
106 cache_stats: Option<Arc<CacheStats>>,
109}
110
111impl<S> CachedStateProvider<S> {
112 pub const fn new(
115 state_provider: S,
116 caches: ExecutionCache,
117 metrics: CachedStateMetrics,
118 ) -> Self {
119 Self { state_provider, caches, metrics, cache_stats: None }
120 }
121}
122
123impl<S> CachedStateProvider<S, true> {
124 pub const fn new_prewarm(
126 state_provider: S,
127 caches: ExecutionCache,
128 metrics: CachedStateMetrics,
129 ) -> Self {
130 Self { state_provider, caches, metrics, cache_stats: None }
131 }
132}
133
134impl<S, const PREWARM: bool> CachedStateProvider<S, PREWARM> {
135 pub fn with_cache_stats(mut self, stats: Option<Arc<CacheStats>>) -> Self {
137 self.cache_stats = stats;
138 self
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
144pub enum CachedStatus<T> {
145 NotCached(T),
147 Cached(T),
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum CachedStateMetricsSource {
154 Engine,
156 Builder,
158 #[cfg(any(test, feature = "test-utils"))]
160 Test,
161}
162
163impl fmt::Display for CachedStateMetricsSource {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 Self::Engine => f.write_str("engine"),
167 Self::Builder => f.write_str("builder"),
168 #[cfg(any(test, feature = "test-utils"))]
169 Self::Test => f.write_str("test"),
170 }
171 }
172}
173
174#[derive(Metrics, Clone)]
176#[metrics(scope = "sync.caching")]
177pub struct CachedStateMetrics {
178 execution_cache_created_total: Counter,
180
181 execution_cache_creation_duration_seconds: Histogram,
183
184 code_cache_hits: Gauge,
186
187 code_cache_misses: Gauge,
189
190 code_cache_size: Gauge,
192
193 code_cache_capacity: Gauge,
195
196 code_cache_collisions: Gauge,
198
199 storage_cache_hits: Gauge,
201
202 storage_cache_misses: Gauge,
204
205 storage_cache_size: Gauge,
207
208 storage_cache_capacity: Gauge,
210
211 storage_cache_collisions: Gauge,
213
214 account_cache_hits: Gauge,
216
217 account_cache_misses: Gauge,
219
220 account_cache_size: Gauge,
222
223 account_cache_capacity: Gauge,
225
226 account_cache_collisions: Gauge,
228}
229
230impl CachedStateMetrics {
231 pub fn reset(&self) {
233 self.code_cache_hits.set(0);
235 self.code_cache_misses.set(0);
236 self.code_cache_collisions.set(0);
237
238 self.storage_cache_hits.set(0);
240 self.storage_cache_misses.set(0);
241 self.storage_cache_collisions.set(0);
242
243 self.account_cache_hits.set(0);
245 self.account_cache_misses.set(0);
246 self.account_cache_collisions.set(0);
247 }
248
249 pub fn zeroed(source: CachedStateMetricsSource) -> Self {
252 let zeroed = Self::new_with_labels(&[("source", source.to_string())]);
253 zeroed.reset();
254 zeroed
255 }
256
257 pub fn record_cache_creation(&self, duration: Duration) {
259 self.execution_cache_created_total.increment(1);
260 self.execution_cache_creation_duration_seconds.record(duration.as_secs_f64());
261 }
262}
263
264#[derive(Debug, Default)]
266pub struct CacheStats {
267 account_hits: AtomicUsize,
269 account_misses: AtomicUsize,
271 storage_hits: AtomicUsize,
273 storage_misses: AtomicUsize,
275 code_hits: AtomicUsize,
277 code_misses: AtomicUsize,
279}
280
281impl CacheStats {
282 pub fn record_account_hit(&self) {
284 self.account_hits.fetch_add(1, Ordering::Relaxed);
285 }
286
287 pub fn record_account_miss(&self) {
289 self.account_misses.fetch_add(1, Ordering::Relaxed);
290 }
291
292 pub fn account_hits(&self) -> usize {
294 self.account_hits.load(Ordering::Relaxed)
295 }
296
297 pub fn account_misses(&self) -> usize {
299 self.account_misses.load(Ordering::Relaxed)
300 }
301
302 pub fn record_storage_hit(&self) {
304 self.storage_hits.fetch_add(1, Ordering::Relaxed);
305 }
306
307 pub fn record_storage_miss(&self) {
309 self.storage_misses.fetch_add(1, Ordering::Relaxed);
310 }
311
312 pub fn storage_hits(&self) -> usize {
314 self.storage_hits.load(Ordering::Relaxed)
315 }
316
317 pub fn storage_misses(&self) -> usize {
319 self.storage_misses.load(Ordering::Relaxed)
320 }
321
322 pub fn record_code_hit(&self) {
324 self.code_hits.fetch_add(1, Ordering::Relaxed);
325 }
326
327 pub fn record_code_miss(&self) {
329 self.code_misses.fetch_add(1, Ordering::Relaxed);
330 }
331
332 pub fn code_hits(&self) -> usize {
334 self.code_hits.load(Ordering::Relaxed)
335 }
336
337 pub fn code_misses(&self) -> usize {
339 self.code_misses.load(Ordering::Relaxed)
340 }
341}
342
343#[derive(Debug)]
358pub struct CacheStatsHandler {
359 collisions: AtomicU64,
360 size: AtomicUsize,
361 capacity: usize,
362}
363
364impl CacheStatsHandler {
365 pub const fn new(capacity: usize) -> Self {
367 Self { collisions: AtomicU64::new(0), size: AtomicUsize::new(0), capacity }
368 }
369
370 pub fn collisions(&self) -> u64 {
372 self.collisions.load(Ordering::Relaxed)
373 }
374
375 pub fn size(&self) -> usize {
377 self.size.load(Ordering::Relaxed)
378 }
379
380 pub const fn capacity(&self) -> usize {
382 self.capacity
383 }
384
385 pub fn increment_size(&self) {
387 let _ = self.size.fetch_add(1, Ordering::Relaxed);
388 }
389
390 pub fn decrement_size(&self) {
392 let _ = self.size.fetch_sub(1, Ordering::Relaxed);
393 }
394
395 pub fn reset_size(&self) {
397 self.size.store(0, Ordering::Relaxed);
398 }
399
400 pub fn reset_stats(&self) {
402 self.collisions.store(0, Ordering::Relaxed);
403 }
404}
405
406impl<K: PartialEq, V> StatsHandler<K, V> for CacheStatsHandler {
407 fn on_hit(&self, _key: &K, _value: &V) {}
408
409 fn on_miss(&self, _key: AnyRef<'_>) {}
410
411 fn on_insert(&self, key: &K, _value: &V, evicted: Option<(&K, &V)>) {
412 match evicted {
413 None => {
414 self.increment_size();
416 }
417 Some((evicted_key, _)) if evicted_key != key => {
418 self.collisions.fetch_add(1, Ordering::Relaxed);
420 }
421 Some(_) => {
422 }
424 }
425 }
426
427 fn on_remove(&self, _key: &K, _value: &V) {
428 self.decrement_size();
429 }
430}
431
432impl<S: AccountReader, const PREWARM: bool> AccountReader for CachedStateProvider<S, PREWARM> {
433 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
434 if PREWARM {
435 match self.caches.get_or_try_insert_account_with(*address, || {
436 self.state_provider.basic_account(address)
437 })? {
438 CachedStatus::NotCached(value) => {
440 if let Some(stats) = &self.cache_stats {
441 stats.record_account_miss();
442 }
443 Ok(value)
444 }
445 CachedStatus::Cached(value) => {
446 if let Some(stats) = &self.cache_stats {
447 stats.record_account_hit();
448 }
449 Ok(value)
450 }
451 }
452 } else if let Some(account) = self.caches.0.account_cache.get(address) {
453 self.metrics.account_cache_hits.increment(1);
454 if let Some(stats) = &self.cache_stats {
455 stats.record_account_hit();
456 }
457 Ok(account)
458 } else {
459 self.metrics.account_cache_misses.increment(1);
460 if let Some(stats) = &self.cache_stats {
461 stats.record_account_miss();
462 }
463 self.state_provider.basic_account(address)
464 }
465 }
466}
467
468impl<S: StateProvider, const PREWARM: bool> StateProvider for CachedStateProvider<S, PREWARM> {
469 fn storage(
470 &self,
471 account: Address,
472 storage_key: StorageKey,
473 ) -> ProviderResult<Option<StorageValue>> {
474 if PREWARM {
475 match self.caches.get_or_try_insert_storage_with(account, storage_key, || {
476 self.state_provider.storage(account, storage_key).map(Option::unwrap_or_default)
477 })? {
478 CachedStatus::NotCached(value) => {
480 if let Some(stats) = &self.cache_stats {
481 stats.record_storage_miss();
482 }
483 Ok(Some(value).filter(|v| !v.is_zero()))
484 }
485 CachedStatus::Cached(value) => {
486 if let Some(stats) = &self.cache_stats {
487 stats.record_storage_hit();
488 }
489 Ok(Some(value).filter(|v| !v.is_zero()))
490 }
491 }
492 } else if let Some(value) = self.caches.0.storage_cache.get(&(account, storage_key)) {
493 self.metrics.storage_cache_hits.increment(1);
494 if let Some(stats) = &self.cache_stats {
495 stats.record_storage_hit();
496 }
497 Ok(Some(value).filter(|v| !v.is_zero()))
498 } else {
499 self.metrics.storage_cache_misses.increment(1);
500 if let Some(stats) = &self.cache_stats {
501 stats.record_storage_miss();
502 }
503 self.state_provider.storage(account, storage_key)
504 }
505 }
506}
507
508impl<S: BytecodeReader, const PREWARM: bool> BytecodeReader for CachedStateProvider<S, PREWARM> {
509 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
510 if PREWARM {
511 match self.caches.get_or_try_insert_code_with(*code_hash, || {
512 self.state_provider.bytecode_by_hash(code_hash)
513 })? {
514 CachedStatus::NotCached(code) => {
516 if let Some(stats) = &self.cache_stats {
517 stats.record_code_miss();
518 }
519 Ok(code)
520 }
521 CachedStatus::Cached(code) => {
522 if let Some(stats) = &self.cache_stats {
523 stats.record_code_hit();
524 }
525 Ok(code)
526 }
527 }
528 } else if let Some(code) = self.caches.0.code_cache.get(code_hash) {
529 self.metrics.code_cache_hits.increment(1);
530 if let Some(stats) = &self.cache_stats {
531 stats.record_code_hit();
532 }
533 Ok(code)
534 } else {
535 self.metrics.code_cache_misses.increment(1);
536 if let Some(stats) = &self.cache_stats {
537 stats.record_code_miss();
538 }
539 self.state_provider.bytecode_by_hash(code_hash)
540 }
541 }
542}
543
544impl<S: StateRootProvider, const PREWARM: bool> StateRootProvider
545 for CachedStateProvider<S, PREWARM>
546{
547 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
548 self.state_provider.state_root(hashed_state)
549 }
550
551 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
552 self.state_provider.state_root_from_nodes(input)
553 }
554
555 fn state_root_with_updates(
556 &self,
557 hashed_state: HashedPostState,
558 ) -> ProviderResult<(B256, TrieUpdates)> {
559 self.state_provider.state_root_with_updates(hashed_state)
560 }
561
562 fn state_root_from_nodes_with_updates(
563 &self,
564 input: TrieInput,
565 ) -> ProviderResult<(B256, TrieUpdates)> {
566 self.state_provider.state_root_from_nodes_with_updates(input)
567 }
568}
569
570impl<S: StateProofProvider, const PREWARM: bool> StateProofProvider
571 for CachedStateProvider<S, PREWARM>
572{
573 fn proof(
574 &self,
575 input: TrieInput,
576 address: Address,
577 slots: &[B256],
578 ) -> ProviderResult<AccountProof> {
579 self.state_provider.proof(input, address, slots)
580 }
581
582 fn multiproof(
583 &self,
584 input: TrieInput,
585 targets: MultiProofTargets,
586 ) -> ProviderResult<MultiProof> {
587 self.state_provider.multiproof(input, targets)
588 }
589
590 fn witness(
591 &self,
592 input: TrieInput,
593 target: HashedPostState,
594 mode: reth_trie::ExecutionWitnessMode,
595 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
596 self.state_provider.witness(input, target, mode)
597 }
598}
599
600impl<S: StorageRootProvider, const PREWARM: bool> StorageRootProvider
601 for CachedStateProvider<S, PREWARM>
602{
603 fn storage_root(
604 &self,
605 address: Address,
606 hashed_storage: HashedStorage,
607 ) -> ProviderResult<B256> {
608 self.state_provider.storage_root(address, hashed_storage)
609 }
610
611 fn storage_proof(
612 &self,
613 address: Address,
614 slot: B256,
615 hashed_storage: HashedStorage,
616 ) -> ProviderResult<StorageProof> {
617 self.state_provider.storage_proof(address, slot, hashed_storage)
618 }
619
620 fn storage_multiproof(
621 &self,
622 address: Address,
623 slots: &[B256],
624 hashed_storage: HashedStorage,
625 ) -> ProviderResult<StorageMultiProof> {
626 self.state_provider.storage_multiproof(address, slots, hashed_storage)
627 }
628}
629
630impl<S: BlockHashReader, const PREWARM: bool> BlockHashReader for CachedStateProvider<S, PREWARM> {
631 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
632 self.state_provider.block_hash(number)
633 }
634
635 fn canonical_hashes_range(
636 &self,
637 start: alloy_primitives::BlockNumber,
638 end: alloy_primitives::BlockNumber,
639 ) -> ProviderResult<Vec<B256>> {
640 self.state_provider.canonical_hashes_range(start, end)
641 }
642}
643
644impl<S: HashedPostStateProvider, const PREWARM: bool> HashedPostStateProvider
645 for CachedStateProvider<S, PREWARM>
646{
647 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
648 self.state_provider.hashed_post_state(bundle_state)
649 }
650}
651
652#[derive(Debug, Clone)]
663pub struct ExecutionCache(Arc<ExecutionCacheInner>);
664
665#[derive(Debug)]
667struct ExecutionCacheInner {
668 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
670
671 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
673
674 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
676
677 code_stats: Arc<CacheStatsHandler>,
679
680 storage_stats: Arc<CacheStatsHandler>,
682
683 account_stats: Arc<CacheStatsHandler>,
685
686 selfdestruct_encountered: Once,
688}
689
690impl ExecutionCache {
691 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
700 let entries = size_bytes / entry_size;
701 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
703 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
705 Self::MIN_CACHE_SIZE_WITH_EPOCHS
706 } else {
707 rounded
708 }
709 }
710
711 pub fn new(total_cache_size: usize) -> Self {
713 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);
718 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
719 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
720
721 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
722 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
723 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
724
725 Self(Arc::new(ExecutionCacheInner {
726 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
727 .with_stats(Some(Stats::new(code_stats.clone()))),
728 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
729 .with_stats(Some(Stats::new(storage_stats.clone()))),
730 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
731 .with_stats(Some(Stats::new(account_stats.clone()))),
732 code_stats,
733 storage_stats,
734 account_stats,
735 selfdestruct_encountered: Once::new(),
736 }))
737 }
738
739 pub fn get_or_try_insert_code_with<E>(
741 &self,
742 hash: B256,
743 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
744 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
745 let mut miss = false;
746 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
747 miss = true;
748 f()
749 })?;
750
751 if miss {
752 Ok(CachedStatus::NotCached(result))
753 } else {
754 Ok(CachedStatus::Cached(result))
755 }
756 }
757
758 pub fn get_or_try_insert_storage_with<E>(
760 &self,
761 address: Address,
762 key: StorageKey,
763 f: impl FnOnce() -> Result<StorageValue, E>,
764 ) -> Result<CachedStatus<StorageValue>, E> {
765 let mut miss = false;
766 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
767 miss = true;
768 f()
769 })?;
770
771 if miss {
772 Ok(CachedStatus::NotCached(result))
773 } else {
774 Ok(CachedStatus::Cached(result))
775 }
776 }
777
778 pub fn get_or_try_insert_account_with<E>(
780 &self,
781 address: Address,
782 f: impl FnOnce() -> Result<Option<Account>, E>,
783 ) -> Result<CachedStatus<Option<Account>>, E> {
784 let mut miss = false;
785 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
786 miss = true;
787 f()
788 })?;
789
790 if miss {
791 Ok(CachedStatus::NotCached(result))
792 } else {
793 Ok(CachedStatus::Cached(result))
794 }
795 }
796
797 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
799 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
800 }
801
802 pub fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
804 self.0.code_cache.insert(hash, code);
805 }
806
807 pub fn insert_account(&self, address: Address, account: Option<Account>) {
809 self.0.account_cache.insert(address, account);
810 }
811
812 #[instrument(level = "debug", target = "engine::caching", skip_all)]
831 #[expect(clippy::result_unit_err)]
832 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
833 let _enter =
834 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
835 .entered();
836 for (code_hash, bytecode) in &state_updates.contracts {
838 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
839 }
840 drop(_enter);
841
842 let _enter = debug_span!(
843 target: "engine::tree",
844 "accounts",
845 accounts = state_updates.state.len(),
846 storages =
847 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
848 )
849 .entered();
850 for (addr, account) in &state_updates.state {
851 if account.status.is_not_modified() {
854 continue
855 }
856
857 if account.was_destroyed() {
864 let had_code =
865 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
866 if had_code {
867 self.0.selfdestruct_encountered.call_once(|| {
868 warn!(
869 target: "engine::caching",
870 address = ?addr,
871 info = ?account.info,
872 original_info = ?account.original_info,
873 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
874 );
875 });
876 self.clear();
877 return Ok(())
878 }
879
880 self.0.account_cache.remove(addr);
881 continue;
882 }
883
884 let Some(ref account_info) = account.info else {
888 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
889 return Err(())
890 };
891
892 for (key, slot) in &account.storage {
894 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
895 }
896
897 self.insert_account(*addr, Some(Account::from(account_info)));
900 }
901
902 Ok(())
903 }
904
905 pub fn clear(&self) {
910 self.0.storage_cache.clear();
911 self.0.account_cache.clear();
912
913 self.0.storage_stats.reset_size();
914 self.0.account_stats.reset_size();
915 }
916
917 pub fn update_metrics(&self, metrics: &CachedStateMetrics) {
920 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
921 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
922 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
923 self.0.code_stats.reset_stats();
924
925 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
926 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
927 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
928 self.0.storage_stats.reset_stats();
929
930 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
931 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
932 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
933 self.0.account_stats.reset_stats();
934 }
935}
936
937#[derive(Debug, Clone)]
940pub struct SavedCache {
941 hash: B256,
943
944 caches: ExecutionCache,
946
947 usage_guard: Arc<()>,
950}
951
952impl SavedCache {
953 pub fn new(hash: B256, caches: ExecutionCache) -> Self {
955 Self { hash, caches, usage_guard: Arc::new(()) }
956 }
957
958 pub const fn executed_block_hash(&self) -> B256 {
960 self.hash
961 }
962
963 pub fn is_available(&self) -> bool {
965 Arc::strong_count(&self.usage_guard) == 1
966 }
967
968 pub fn usage_count(&self) -> usize {
970 Arc::strong_count(&self.usage_guard)
971 }
972
973 pub const fn cache(&self) -> &ExecutionCache {
975 &self.caches
976 }
977
978 pub fn update_metrics(&self, metrics: Option<&CachedStateMetrics>) {
980 if let Some(metrics) = metrics {
981 self.caches.update_metrics(metrics);
982 }
983 }
984
985 pub fn clear_with_hash(&mut self, hash: B256) {
988 self.hash = hash;
989 self.caches.clear();
990 }
991}
992
993#[cfg(any(test, feature = "test-utils"))]
994impl SavedCache {
995 pub fn clone_guard_for_test(&self) -> Arc<()> {
997 self.usage_guard.clone()
998 }
999}
1000
1001#[cfg(test)]
1002mod tests {
1003 use super::*;
1004 use alloy_primitives::{map::HashMap, U256};
1005 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1006 use reth_revm::db::{AccountStatus, BundleAccount};
1007 use revm_state::AccountInfo;
1008
1009 #[test]
1010 fn test_empty_storage_cached_state_provider() {
1011 let address = Address::random();
1012 let storage_key = StorageKey::random();
1013 let account = ExtendedAccount::new(0, U256::ZERO);
1014
1015 let provider = MockEthProvider::default();
1016 provider.extend_accounts(vec![(address, account)]);
1017
1018 let caches = ExecutionCache::new(1000);
1019 let state_provider = CachedStateProvider::new(
1020 provider,
1021 caches,
1022 CachedStateMetrics::zeroed(CachedStateMetricsSource::Test),
1023 );
1024
1025 let res = state_provider.storage(address, storage_key);
1026 assert!(res.is_ok());
1027 assert_eq!(res.unwrap(), None);
1028 }
1029
1030 #[test]
1031 fn test_uncached_storage_cached_state_provider() {
1032 let address = Address::random();
1033 let storage_key = StorageKey::random();
1034 let storage_value = U256::from(1);
1035 let account =
1036 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1037
1038 let provider = MockEthProvider::default();
1039 provider.extend_accounts(vec![(address, account)]);
1040
1041 let caches = ExecutionCache::new(1000);
1042 let state_provider = CachedStateProvider::new(
1043 provider,
1044 caches,
1045 CachedStateMetrics::zeroed(CachedStateMetricsSource::Test),
1046 );
1047
1048 let res = state_provider.storage(address, storage_key);
1049 assert!(res.is_ok());
1050 assert_eq!(res.unwrap(), Some(storage_value));
1051 }
1052
1053 #[test]
1054 fn test_get_storage_populated() {
1055 let address = Address::random();
1056 let storage_key = StorageKey::random();
1057 let storage_value = U256::from(1);
1058
1059 let caches = ExecutionCache::new(1000);
1060 caches.insert_storage(address, storage_key, Some(storage_value));
1061
1062 let result = caches
1063 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1064 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1065 }
1066
1067 #[test]
1068 fn test_get_storage_empty() {
1069 let address = Address::random();
1070 let storage_key = StorageKey::random();
1071
1072 let caches = ExecutionCache::new(1000);
1073 caches.insert_storage(address, storage_key, None);
1074
1075 let result = caches
1076 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1077 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1078 }
1079
1080 #[test]
1081 fn test_saved_cache_is_available() {
1082 let execution_cache = ExecutionCache::new(1000);
1083 let cache = SavedCache::new(B256::ZERO, execution_cache);
1084
1085 assert!(cache.is_available(), "Cache should be available initially");
1086
1087 let _guard = cache.clone_guard_for_test();
1088
1089 assert!(!cache.is_available(), "Cache should not be available with active guard");
1090 }
1091
1092 #[test]
1093 fn test_saved_cache_multiple_references() {
1094 let execution_cache = ExecutionCache::new(1000);
1095 let cache = SavedCache::new(B256::from([2u8; 32]), execution_cache);
1096
1097 let guard1 = cache.clone_guard_for_test();
1098 let guard2 = cache.clone_guard_for_test();
1099 let guard3 = guard1.clone();
1100
1101 assert!(!cache.is_available());
1102
1103 drop(guard1);
1104 assert!(!cache.is_available());
1105
1106 drop(guard2);
1107 assert!(!cache.is_available());
1108
1109 drop(guard3);
1110 assert!(cache.is_available());
1111 }
1112
1113 #[test]
1114 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1115 let caches = ExecutionCache::new(1000);
1116
1117 let addr1 = Address::random();
1119 let addr2 = Address::random();
1120 let storage_key = StorageKey::random();
1121 caches.insert_account(addr1, Some(Account::default()));
1122 caches.insert_account(addr2, Some(Account::default()));
1123 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1124
1125 assert!(caches.0.account_cache.get(&addr1).is_some());
1127 assert!(caches.0.account_cache.get(&addr2).is_some());
1128 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1129
1130 let bundle = BundleState {
1131 state: HashMap::from_iter([(
1133 Address::random(),
1134 BundleAccount::new(
1135 Some(AccountInfo {
1136 balance: U256::ZERO,
1137 nonce: 1,
1138 code_hash: B256::random(), code: None,
1140 account_id: None,
1141 }),
1142 None, Default::default(),
1144 AccountStatus::Destroyed,
1145 ),
1146 )]),
1147 contracts: Default::default(),
1148 reverts: Default::default(),
1149 state_size: 0,
1150 reverts_size: 0,
1151 };
1152
1153 let result = caches.insert_state(&bundle);
1155 assert!(result.is_ok());
1156
1157 assert!(caches.0.account_cache.get(&addr1).is_none());
1159 assert!(caches.0.account_cache.get(&addr2).is_none());
1160 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1161 }
1162
1163 #[test]
1164 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1165 let caches = ExecutionCache::new(1000);
1166
1167 let addr1 = Address::random();
1169 let addr2 = Address::random();
1170 let storage_key = StorageKey::random();
1171 caches.insert_account(addr1, Some(Account::default()));
1172 caches.insert_account(addr2, Some(Account::default()));
1173 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1174
1175 let bundle = BundleState {
1176 state: HashMap::from_iter([(
1178 addr1,
1179 BundleAccount::new(
1180 Some(AccountInfo {
1181 balance: U256::from(100),
1182 nonce: 1,
1183 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1185 account_id: None,
1186 }),
1187 None, Default::default(),
1189 AccountStatus::Destroyed,
1190 ),
1191 )]),
1192 contracts: Default::default(),
1193 reverts: Default::default(),
1194 state_size: 0,
1195 reverts_size: 0,
1196 };
1197
1198 assert!(caches.insert_state(&bundle).is_ok());
1200
1201 assert!(caches.0.account_cache.get(&addr1).is_none());
1203 assert!(caches.0.account_cache.get(&addr2).is_some());
1204 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1205 }
1206
1207 #[test]
1208 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1209 let caches = ExecutionCache::new(1000);
1210
1211 let addr1 = Address::random();
1213 let addr2 = Address::random();
1214 caches.insert_account(addr1, Some(Account::default()));
1215 caches.insert_account(addr2, Some(Account::default()));
1216
1217 let bundle = BundleState {
1218 state: HashMap::from_iter([(
1220 addr1,
1221 BundleAccount::new(
1222 None, None, Default::default(),
1225 AccountStatus::Destroyed,
1226 ),
1227 )]),
1228 contracts: Default::default(),
1229 reverts: Default::default(),
1230 state_size: 0,
1231 reverts_size: 0,
1232 };
1233
1234 assert!(caches.insert_state(&bundle).is_ok());
1236
1237 assert!(caches.0.account_cache.get(&addr1).is_none());
1239 assert!(caches.0.account_cache.get(&addr2).is_some());
1240 }
1241
1242 #[test]
1243 fn test_insert_state_destroyed_uncached_account_keeps_size_zero() {
1244 let caches = ExecutionCache::new(1000);
1245 assert_eq!(caches.0.account_stats.size(), 0);
1246
1247 let addr = Address::random();
1248 let bundle = BundleState {
1249 state: HashMap::from_iter([(
1250 addr,
1251 BundleAccount::new(
1252 None, None, Default::default(),
1255 AccountStatus::Destroyed,
1256 ),
1257 )]),
1258 contracts: Default::default(),
1259 reverts: Default::default(),
1260 state_size: 0,
1261 reverts_size: 0,
1262 };
1263
1264 assert!(caches.insert_state(&bundle).is_ok());
1265 assert_eq!(caches.0.account_stats.size(), 0);
1266 assert!(caches.0.account_cache.get(&addr).is_none());
1267 }
1268
1269 #[test]
1270 fn test_code_cache_capacity_with_default_budget() {
1271 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);
1276
1277 assert_eq!(
1280 capacity, 16384,
1281 "code cache should have 16384 entries with default 4 GB budget"
1282 );
1283 }
1284}