1use alloy_primitives::{Address, StorageKey, StorageValue, B256};
3use metrics::Gauge;
4use mini_moka::sync::CacheBuilder;
5use reth_errors::ProviderResult;
6use reth_metrics::Metrics;
7use reth_primitives_traits::{Account, Bytecode};
8use reth_provider::{
9 AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
10 StateProvider, StateRootProvider, StorageRootProvider,
11};
12use reth_revm::db::BundleState;
13use reth_trie::{
14 updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
15 MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
16};
17use revm_primitives::map::DefaultHashBuilder;
18use std::{sync::Arc, time::Duration};
19use tracing::trace;
20
21pub(crate) type Cache<K, V> =
22 mini_moka::sync::Cache<K, V, alloy_primitives::map::DefaultHashBuilder>;
23
24pub(crate) struct CachedStateProvider<S> {
26 state_provider: S,
28
29 caches: ExecutionCache,
31
32 metrics: CachedStateMetrics,
34}
35
36impl<S> CachedStateProvider<S>
37where
38 S: StateProvider,
39{
40 pub(crate) const fn new_with_caches(
43 state_provider: S,
44 caches: ExecutionCache,
45 metrics: CachedStateMetrics,
46 ) -> Self {
47 Self { state_provider, caches, metrics }
48 }
49}
50
51#[derive(Metrics, Clone)]
53#[metrics(scope = "sync.caching")]
54pub(crate) struct CachedStateMetrics {
55 code_cache_hits: Gauge,
57
58 code_cache_misses: Gauge,
60
61 code_cache_size: Gauge,
66
67 storage_cache_hits: Gauge,
69
70 storage_cache_misses: Gauge,
72
73 storage_cache_size: Gauge,
78
79 account_cache_hits: Gauge,
81
82 account_cache_misses: Gauge,
84
85 account_cache_size: Gauge,
90}
91
92impl CachedStateMetrics {
93 pub(crate) fn reset(&self) {
95 self.code_cache_hits.set(0);
97 self.code_cache_misses.set(0);
98
99 self.storage_cache_hits.set(0);
101 self.storage_cache_misses.set(0);
102
103 self.account_cache_hits.set(0);
105 self.account_cache_misses.set(0);
106 }
107
108 pub(crate) fn zeroed() -> Self {
110 let zeroed = Self::default();
111 zeroed.reset();
112 zeroed
113 }
114}
115
116impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
117 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
118 if let Some(res) = self.caches.account_cache.get(address) {
119 self.metrics.account_cache_hits.increment(1);
120 return Ok(res)
121 }
122
123 self.metrics.account_cache_misses.increment(1);
124
125 let res = self.state_provider.basic_account(address)?;
126 self.caches.account_cache.insert(*address, res);
127 Ok(res)
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
133pub(crate) enum SlotStatus {
134 NotCached,
136 Empty,
138 Value(StorageValue),
140}
141
142impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
143 fn storage(
144 &self,
145 account: Address,
146 storage_key: StorageKey,
147 ) -> ProviderResult<Option<StorageValue>> {
148 match self.caches.get_storage(&account, &storage_key) {
149 SlotStatus::NotCached => {
150 self.metrics.storage_cache_misses.increment(1);
151 let final_res = self.state_provider.storage(account, storage_key)?;
152 self.caches.insert_storage(account, storage_key, final_res);
153 Ok(final_res)
154 }
155 SlotStatus::Empty => {
156 self.metrics.storage_cache_hits.increment(1);
157 Ok(None)
158 }
159 SlotStatus::Value(value) => {
160 self.metrics.storage_cache_hits.increment(1);
161 Ok(Some(value))
162 }
163 }
164 }
165}
166
167impl<S: BytecodeReader> BytecodeReader for CachedStateProvider<S> {
168 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
169 if let Some(res) = self.caches.code_cache.get(code_hash) {
170 self.metrics.code_cache_hits.increment(1);
171 return Ok(res)
172 }
173
174 self.metrics.code_cache_misses.increment(1);
175
176 let final_res = self.state_provider.bytecode_by_hash(code_hash)?;
177 self.caches.code_cache.insert(*code_hash, final_res.clone());
178 Ok(final_res)
179 }
180}
181
182impl<S: StateRootProvider> StateRootProvider for CachedStateProvider<S> {
183 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
184 self.state_provider.state_root(hashed_state)
185 }
186
187 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
188 self.state_provider.state_root_from_nodes(input)
189 }
190
191 fn state_root_with_updates(
192 &self,
193 hashed_state: HashedPostState,
194 ) -> ProviderResult<(B256, TrieUpdates)> {
195 self.state_provider.state_root_with_updates(hashed_state)
196 }
197
198 fn state_root_from_nodes_with_updates(
199 &self,
200 input: TrieInput,
201 ) -> ProviderResult<(B256, TrieUpdates)> {
202 self.state_provider.state_root_from_nodes_with_updates(input)
203 }
204}
205
206impl<S: StateProofProvider> StateProofProvider for CachedStateProvider<S> {
207 fn proof(
208 &self,
209 input: TrieInput,
210 address: Address,
211 slots: &[B256],
212 ) -> ProviderResult<AccountProof> {
213 self.state_provider.proof(input, address, slots)
214 }
215
216 fn multiproof(
217 &self,
218 input: TrieInput,
219 targets: MultiProofTargets,
220 ) -> ProviderResult<MultiProof> {
221 self.state_provider.multiproof(input, targets)
222 }
223
224 fn witness(
225 &self,
226 input: TrieInput,
227 target: HashedPostState,
228 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
229 self.state_provider.witness(input, target)
230 }
231}
232
233impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
234 fn storage_root(
235 &self,
236 address: Address,
237 hashed_storage: HashedStorage,
238 ) -> ProviderResult<B256> {
239 self.state_provider.storage_root(address, hashed_storage)
240 }
241
242 fn storage_proof(
243 &self,
244 address: Address,
245 slot: B256,
246 hashed_storage: HashedStorage,
247 ) -> ProviderResult<StorageProof> {
248 self.state_provider.storage_proof(address, slot, hashed_storage)
249 }
250
251 fn storage_multiproof(
264 &self,
265 address: Address,
266 slots: &[B256],
267 hashed_storage: HashedStorage,
268 ) -> ProviderResult<StorageMultiProof> {
269 self.state_provider.storage_multiproof(address, slots, hashed_storage)
270 }
271}
272
273impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
274 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
275 self.state_provider.block_hash(number)
276 }
277
278 fn canonical_hashes_range(
279 &self,
280 start: alloy_primitives::BlockNumber,
281 end: alloy_primitives::BlockNumber,
282 ) -> ProviderResult<Vec<B256>> {
283 self.state_provider.canonical_hashes_range(start, end)
284 }
285}
286
287impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
288 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
289 self.state_provider.hashed_post_state(bundle_state)
290 }
291}
292
293#[derive(Debug, Clone)]
299pub(crate) struct ExecutionCache {
300 code_cache: Cache<B256, Option<Bytecode>>,
302
303 storage_cache: Cache<Address, AccountStorageCache>,
306
307 account_cache: Cache<Address, Option<Account>>,
309}
310
311impl ExecutionCache {
312 pub(crate) fn get_storage(&self, address: &Address, key: &StorageKey) -> SlotStatus {
319 match self.storage_cache.get(address) {
320 None => SlotStatus::NotCached,
321 Some(account_cache) => account_cache.get_storage(key),
322 }
323 }
324
325 pub(crate) fn insert_storage(
327 &self,
328 address: Address,
329 key: StorageKey,
330 value: Option<StorageValue>,
331 ) {
332 self.insert_storage_bulk(address, [(key, value)]);
333 }
334
335 pub(crate) fn insert_storage_bulk<I>(&self, address: Address, storage_entries: I)
340 where
341 I: IntoIterator<Item = (StorageKey, Option<StorageValue>)>,
342 {
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
349 for (key, value) in storage_entries {
350 account_cache.insert_storage(key, value);
351 }
352 }
353
354 pub(crate) fn invalidate_account_storage(&self, address: &Address) {
356 self.storage_cache.invalidate(address);
357 }
358
359 pub(crate) fn total_storage_slots(&self) -> usize {
361 self.storage_cache.iter().map(|addr| addr.len()).sum()
362 }
363
364 pub(crate) fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
383 for (code_hash, bytecode) in &state_updates.contracts {
385 self.code_cache.insert(*code_hash, Some(Bytecode(bytecode.clone())));
386 }
387
388 for (addr, account) in &state_updates.state {
389 if account.status.is_not_modified() {
392 continue
393 }
394
395 if account.was_destroyed() {
397 self.account_cache.invalidate(addr);
399
400 self.invalidate_account_storage(addr);
401 continue
402 }
403
404 let Some(ref account_info) = account.info else {
408 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
409 return Err(())
410 };
411
412 let storage_entries = account.storage.iter().map(|(storage_key, slot)| {
416 ((*storage_key).into(), Some(slot.present_value))
419 });
420 self.insert_storage_bulk(*addr, storage_entries);
421
422 self.account_cache.insert(*addr, Some(Account::from(account_info)));
425 }
426
427 Ok(())
428 }
429}
430
431#[derive(Debug)]
433pub(crate) struct ExecutionCacheBuilder {
434 code_cache_entries: u64,
436
437 storage_cache_entries: u64,
439
440 account_cache_entries: u64,
442}
443
444impl ExecutionCacheBuilder {
445 pub(crate) fn build_caches(self, total_cache_size: u64) -> ExecutionCache {
447 let storage_cache_size = (total_cache_size * 8888) / 10000; let account_cache_size = (total_cache_size * 556) / 10000; let code_cache_size = (total_cache_size * 556) / 10000; const EXPIRY_TIME: Duration = Duration::from_secs(7200); const TIME_TO_IDLE: Duration = Duration::from_secs(3600); let storage_cache = CacheBuilder::new(self.storage_cache_entries)
455 .weigher(|_key: &Address, value: &AccountStorageCache| -> u32 {
456 let base_weight = 39_000;
458 let slots_weight = value.len() * 218;
459 (base_weight + slots_weight) as u32
460 })
461 .max_capacity(storage_cache_size)
462 .time_to_live(EXPIRY_TIME)
463 .time_to_idle(TIME_TO_IDLE)
464 .build_with_hasher(DefaultHashBuilder::default());
465
466 let account_cache = CacheBuilder::new(self.account_cache_entries)
467 .weigher(|_key: &Address, _value: &Option<Account>| -> u32 {
468 size_of::<Option<Account>>() as u32
470 })
471 .max_capacity(account_cache_size)
472 .time_to_live(EXPIRY_TIME)
473 .time_to_idle(TIME_TO_IDLE)
474 .build_with_hasher(DefaultHashBuilder::default());
475
476 let code_cache = CacheBuilder::new(self.code_cache_entries)
477 .weigher(|_key: &B256, value: &Option<Bytecode>| -> u32 {
478 match value {
479 Some(bytecode) => {
480 (40 + bytecode.len()) as u32
482 }
483 None => 8, }
485 })
486 .max_capacity(code_cache_size)
487 .time_to_live(EXPIRY_TIME)
488 .time_to_idle(TIME_TO_IDLE)
489 .build_with_hasher(DefaultHashBuilder::default());
490
491 ExecutionCache { code_cache, storage_cache, account_cache }
492 }
493}
494
495impl Default for ExecutionCacheBuilder {
496 fn default() -> Self {
497 Self {
505 code_cache_entries: 10_000_000,
506 storage_cache_entries: 10_000_000,
507 account_cache_entries: 10_000_000,
508 }
509 }
510}
511
512#[derive(Debug, Clone)]
515pub(crate) struct SavedCache {
516 hash: B256,
518
519 caches: ExecutionCache,
521
522 metrics: CachedStateMetrics,
524
525 usage_guard: Arc<()>,
528}
529
530impl SavedCache {
531 pub(super) fn new(hash: B256, caches: ExecutionCache, metrics: CachedStateMetrics) -> Self {
533 Self { hash, caches, metrics, usage_guard: Arc::new(()) }
534 }
535
536 pub(crate) const fn executed_block_hash(&self) -> B256 {
538 self.hash
539 }
540
541 pub(crate) fn split(self) -> (ExecutionCache, CachedStateMetrics) {
543 (self.caches, self.metrics)
544 }
545
546 pub(crate) fn is_available(&self) -> bool {
548 Arc::strong_count(&self.usage_guard) == 1
549 }
550
551 pub(crate) const fn cache(&self) -> &ExecutionCache {
553 &self.caches
554 }
555
556 pub(crate) const fn metrics(&self) -> &CachedStateMetrics {
558 &self.metrics
559 }
560
561 pub(crate) fn update_metrics(&self) {
563 self.metrics.storage_cache_size.set(self.caches.total_storage_slots() as f64);
564 self.metrics.account_cache_size.set(self.caches.account_cache.entry_count() as f64);
565 self.metrics.code_cache_size.set(self.caches.code_cache.entry_count() as f64);
566 }
567}
568
569#[cfg(test)]
570impl SavedCache {
571 fn clone_guard_for_test(&self) -> Arc<()> {
572 self.usage_guard.clone()
573 }
574}
575
576#[derive(Debug, Clone)]
581pub(crate) struct AccountStorageCache {
582 slots: Cache<StorageKey, Option<StorageValue>>,
584}
585
586impl AccountStorageCache {
587 pub(crate) fn new(max_slots: u64) -> Self {
589 Self {
590 slots: CacheBuilder::new(max_slots).build_with_hasher(DefaultHashBuilder::default()),
591 }
592 }
593
594 pub(crate) fn get_storage(&self, key: &StorageKey) -> SlotStatus {
599 match self.slots.get(key) {
600 None => SlotStatus::NotCached,
601 Some(None) => SlotStatus::Empty,
602 Some(Some(value)) => SlotStatus::Value(value),
603 }
604 }
605
606 pub(crate) fn insert_storage(&self, key: StorageKey, value: Option<StorageValue>) {
608 self.slots.insert(key, value);
609 }
610
611 pub(crate) fn len(&self) -> usize {
613 self.slots.entry_count() as usize
614 }
615}
616
617impl Default for AccountStorageCache {
618 fn default() -> Self {
619 Self::new(1_000_000)
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629 use alloy_primitives::{B256, U256};
630 use rand::Rng;
631 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
632 use std::mem::size_of;
633
634 mod tracking_allocator {
635 use std::{
636 alloc::{GlobalAlloc, Layout, System},
637 sync::atomic::{AtomicUsize, Ordering},
638 };
639
640 #[derive(Debug)]
641 pub(crate) struct TrackingAllocator {
642 allocated: AtomicUsize,
643 total_allocated: AtomicUsize,
644 inner: System,
645 }
646
647 impl TrackingAllocator {
648 pub(crate) const fn new() -> Self {
649 Self {
650 allocated: AtomicUsize::new(0),
651 total_allocated: AtomicUsize::new(0),
652 inner: System,
653 }
654 }
655
656 pub(crate) fn reset(&self) {
657 self.allocated.store(0, Ordering::SeqCst);
658 self.total_allocated.store(0, Ordering::SeqCst);
659 }
660
661 pub(crate) fn total_allocated(&self) -> usize {
662 self.total_allocated.load(Ordering::SeqCst)
663 }
664 }
665
666 unsafe impl GlobalAlloc for TrackingAllocator {
667 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
668 let ret = unsafe { self.inner.alloc(layout) };
669 if !ret.is_null() {
670 self.allocated.fetch_add(layout.size(), Ordering::SeqCst);
671 self.total_allocated.fetch_add(layout.size(), Ordering::SeqCst);
672 }
673 ret
674 }
675
676 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
677 self.allocated.fetch_sub(layout.size(), Ordering::SeqCst);
678 unsafe { self.inner.dealloc(ptr, layout) }
679 }
680 }
681 }
682
683 use tracking_allocator::TrackingAllocator;
684
685 #[global_allocator]
686 static ALLOCATOR: TrackingAllocator = TrackingAllocator::new();
687
688 fn measure_allocation<T, F>(f: F) -> (usize, T)
689 where
690 F: FnOnce() -> T,
691 {
692 ALLOCATOR.reset();
693 let result = f();
694 let total = ALLOCATOR.total_allocated();
695 (total, result)
696 }
697
698 #[test]
699 fn measure_storage_cache_overhead() {
700 let (base_overhead, cache) = measure_allocation(|| AccountStorageCache::new(1000));
701 println!("Base AccountStorageCache overhead: {base_overhead} bytes");
702 let mut rng = rand::rng();
703
704 let key = StorageKey::random();
705 let value = StorageValue::from(rng.random::<u128>());
706 let (first_slot, _) = measure_allocation(|| {
707 cache.insert_storage(key, Some(value));
708 });
709 println!("First slot insertion overhead: {first_slot} bytes");
710
711 const TOTAL_SLOTS: usize = 10_000;
712 let (test_slots, _) = measure_allocation(|| {
713 for _ in 0..TOTAL_SLOTS {
714 let key = StorageKey::random();
715 let value = StorageValue::from(rng.random::<u128>());
716 cache.insert_storage(key, Some(value));
717 }
718 });
719 println!("Average overhead over {} slots: {} bytes", TOTAL_SLOTS, test_slots / TOTAL_SLOTS);
720
721 println!("\nTheoretical sizes:");
722 println!("StorageKey size: {} bytes", size_of::<StorageKey>());
723 println!("StorageValue size: {} bytes", size_of::<StorageValue>());
724 println!("Option<StorageValue> size: {} bytes", size_of::<Option<StorageValue>>());
725 println!("Option<B256> size: {} bytes", size_of::<Option<B256>>());
726 }
727
728 #[test]
729 fn test_empty_storage_cached_state_provider() {
730 let address = Address::random();
732 let storage_key = StorageKey::random();
733 let account = ExtendedAccount::new(0, U256::ZERO);
734
735 let provider = MockEthProvider::default();
737 provider.extend_accounts(vec![(address, account)]);
738
739 let caches = ExecutionCacheBuilder::default().build_caches(1000);
740 let state_provider =
741 CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
742
743 let res = state_provider.storage(address, storage_key);
745 assert!(res.is_ok());
746 assert_eq!(res.unwrap(), None);
747 }
748
749 #[test]
750 fn test_uncached_storage_cached_state_provider() {
751 let address = Address::random();
753 let storage_key = StorageKey::random();
754 let storage_value = U256::from(1);
755 let account =
756 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
757
758 let provider = MockEthProvider::default();
760 provider.extend_accounts(vec![(address, account)]);
761
762 let caches = ExecutionCacheBuilder::default().build_caches(1000);
763 let state_provider =
764 CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
765
766 let res = state_provider.storage(address, storage_key);
768 assert!(res.is_ok());
769 assert_eq!(res.unwrap(), Some(storage_value));
770 }
771
772 #[test]
773 fn test_get_storage_populated() {
774 let address = Address::random();
776 let storage_key = StorageKey::random();
777 let storage_value = U256::from(1);
778
779 let caches = ExecutionCacheBuilder::default().build_caches(1000);
781 caches.insert_storage(address, storage_key, Some(storage_value));
782
783 let slot_status = caches.get_storage(&address, &storage_key);
785 assert_eq!(slot_status, SlotStatus::Value(storage_value));
786 }
787
788 #[test]
789 fn test_get_storage_not_cached() {
790 let storage_key = StorageKey::random();
792 let address = Address::random();
793
794 let caches = ExecutionCacheBuilder::default().build_caches(1000);
796
797 let slot_status = caches.get_storage(&address, &storage_key);
799 assert_eq!(slot_status, SlotStatus::NotCached);
800 }
801
802 #[test]
803 fn test_get_storage_empty() {
804 let address = Address::random();
807 let storage_key = StorageKey::random();
808
809 let caches = ExecutionCacheBuilder::default().build_caches(1000);
811 caches.insert_storage(address, storage_key, None);
812
813 let slot_status = caches.get_storage(&address, &storage_key);
815 assert_eq!(slot_status, SlotStatus::Empty);
816 }
817
818 #[test]
820 fn test_saved_cache_is_available() {
821 let execution_cache = ExecutionCacheBuilder::default().build_caches(1000);
822 let cache = SavedCache::new(B256::ZERO, execution_cache, CachedStateMetrics::zeroed());
823
824 assert!(cache.is_available(), "Cache should be available initially");
826
827 let _guard = cache.clone_guard_for_test();
829
830 assert!(!cache.is_available(), "Cache should not be available with active guard");
832 }
833
834 #[test]
835 fn test_saved_cache_multiple_references() {
836 let execution_cache = ExecutionCacheBuilder::default().build_caches(1000);
837 let cache =
838 SavedCache::new(B256::from([2u8; 32]), execution_cache, CachedStateMetrics::zeroed());
839
840 let guard1 = cache.clone_guard_for_test();
842 let guard2 = cache.clone_guard_for_test();
843 let guard3 = guard1.clone();
844
845 assert!(!cache.is_available());
847
848 drop(guard1);
850 assert!(!cache.is_available()); drop(guard2);
853 assert!(!cache.is_available()); drop(guard3);
856 assert!(cache.is_available()); }
858}