1use std::hash::Hash;
2
3use clap::Parser;
4use display_json::DebugAsJson;
5use indexmap::Equivalent;
6use quick_cache::UnitWeighter;
7use quick_cache::sync::Cache;
8use quick_cache::sync::DefaultLifecycle;
9use quick_cache::sync::GuardResult;
10use rustc_hash::FxBuildHasher;
11
12use crate::eth::primitives::Account;
13use crate::eth::primitives::Address;
14use crate::eth::primitives::ExecutionChanges;
15use crate::eth::primitives::Slot;
16use crate::eth::primitives::SlotIndex;
17use crate::eth::primitives::SlotValue;
18
19pub struct StorageCache {
20 slot_cache: Cache<(Address, SlotIndex), SlotValue, UnitWeighter, FxBuildHasher>,
21 account_cache: Cache<Address, Account, UnitWeighter, FxBuildHasher>,
22 account_latest_cache: Cache<Address, Account, UnitWeighter, FxBuildHasher>,
23 slot_latest_cache: Cache<(Address, SlotIndex), SlotValue, UnitWeighter, FxBuildHasher>,
24}
25
26#[derive(DebugAsJson, Clone, Parser, serde::Serialize)]
27pub struct CacheConfig {
28 #[arg(long = "slot-cache-capacity", env = "SLOT_CACHE_CAPACITY", default_value = "100000")]
30 pub slot_cache_capacity: usize,
31
32 #[arg(long = "account-cache-capacity", env = "ACCOUNT_CACHE_CAPACITY", default_value = "20000")]
34 pub account_cache_capacity: usize,
35
36 #[arg(long = "account-history-cache-capacity", env = "ACCOUNT_HISTORY_CACHE_CAPACITY", default_value = "20000")]
38 pub account_history_cache_capacity: usize,
39
40 #[arg(long = "slot-history-cache-capacity", env = "SLOT_HISTORY_CACHE_CAPACITY", default_value = "100000")]
42 pub slot_history_cache_capacity: usize,
43}
44
45impl CacheConfig {
46 pub fn init(&self) -> StorageCache {
47 StorageCache::new(self)
48 }
49}
50
51impl StorageCache {
52 pub fn new(config: &CacheConfig) -> Self {
53 Self {
54 slot_cache: Cache::with(
55 config.slot_cache_capacity,
56 config.slot_cache_capacity as u64,
57 UnitWeighter,
58 FxBuildHasher,
59 DefaultLifecycle::default(),
60 ),
61 account_cache: Cache::with(
62 config.account_cache_capacity,
63 config.account_cache_capacity as u64,
64 UnitWeighter,
65 FxBuildHasher,
66 DefaultLifecycle::default(),
67 ),
68 account_latest_cache: Cache::with(
69 config.account_history_cache_capacity,
70 config.account_history_cache_capacity as u64,
71 UnitWeighter,
72 FxBuildHasher,
73 DefaultLifecycle::default(),
74 ),
75 slot_latest_cache: Cache::with(
76 config.slot_history_cache_capacity,
77 config.slot_history_cache_capacity as u64,
78 UnitWeighter,
79 FxBuildHasher,
80 DefaultLifecycle::default(),
81 ),
82 }
83 }
84
85 pub fn clear(&self) {
86 self.slot_cache.clear();
87 self.account_cache.clear();
88 self.account_latest_cache.clear();
89 self.slot_latest_cache.clear();
90 }
91
92 pub fn cache_slot_if_missing(&self, address: Address, slot: Slot) {
93 self.slot_cache.insert_if_missing((address, slot.index), slot.value);
94 }
95
96 pub fn cache_account_if_missing(&self, account: Account) {
97 self.account_cache.insert_if_missing(account.address, account);
98 }
99
100 fn _cache_account_and_slots_from_changes_impl(
101 changes: ExecutionChanges,
102 account_cache: &Cache<Address, Account, UnitWeighter, FxBuildHasher>,
103 slot_cache: &Cache<(Address, SlotIndex), SlotValue, UnitWeighter, FxBuildHasher>,
104 ) {
105 for (address, change) in changes.accounts {
107 let account = (address, change).into();
108 account_cache.insert(address, account);
109 }
110
111 for ((address, index), value) in changes.slots {
113 slot_cache.insert((address, index), value);
114 }
115 }
116
117 pub fn cache_account_and_slots_from_changes(&self, changes: ExecutionChanges) {
118 Self::_cache_account_and_slots_from_changes_impl(changes, &self.account_cache, &self.slot_cache);
119 }
120
121 pub fn cache_account_and_slots_latest_from_changes(&self, changes: ExecutionChanges) {
122 Self::_cache_account_and_slots_from_changes_impl(changes, &self.account_latest_cache, &self.slot_latest_cache);
123 }
124
125 pub fn get_slot(&self, address: Address, index: SlotIndex) -> Option<Slot> {
126 self.slot_cache.get(&(address, index)).map(|value| Slot { value, index })
127 }
128
129 pub fn get_account(&self, address: Address) -> Option<Account> {
130 self.account_cache.get(&address)
131 }
132
133 pub fn cache_account_latest_if_missing(&self, address: Address, account: Account) {
134 self.account_latest_cache.insert_if_missing(address, account);
135 }
136
137 pub fn cache_slot_latest_if_missing(&self, address: Address, slot: Slot) {
138 self.slot_latest_cache.insert_if_missing((address, slot.index), slot.value);
139 }
140
141 pub fn get_account_latest(&self, address: Address) -> Option<Account> {
142 self.account_latest_cache.get(&address)
143 }
144
145 pub fn get_slot_latest(&self, address: Address, index: SlotIndex) -> Option<Slot> {
146 self.slot_latest_cache.get(&(address, index)).map(|value| Slot { value, index })
147 }
148}
149
150trait CacheExt<Key, Val> {
151 fn insert_if_missing(&self, key: Key, val: Val);
152}
153
154impl<Key, Val, We, B, L> CacheExt<Key, Val> for Cache<Key, Val, We, B, L>
155where
156 Key: Hash + Equivalent<Key> + ToOwned<Owned = Key> + std::cmp::Eq,
157 Val: Clone,
158 We: quick_cache::Weighter<Key, Val> + Clone,
159 B: std::hash::BuildHasher + Clone,
160 L: quick_cache::Lifecycle<Key, Val> + Clone,
161{
162 fn insert_if_missing(&self, key: Key, val: Val) {
163 match self.get_value_or_guard(&key, None) {
164 GuardResult::Value(_) => (),
165 GuardResult::Guard(g) => {
166 let _ = g.insert(val);
168 }
169 GuardResult::Timeout => unreachable!(),
170 }
171 }
172}