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 super::AccountWithSlots;
13use crate::eth::primitives::Account;
14use crate::eth::primitives::Address;
15use crate::eth::primitives::ExecutionChanges;
16use crate::eth::primitives::Slot;
17use crate::eth::primitives::SlotIndex;
18use crate::eth::primitives::SlotValue;
19
20pub struct StorageCache {
21 slot_cache: Cache<(Address, SlotIndex), SlotValue, UnitWeighter, FxBuildHasher>,
22 account_cache: Cache<Address, Account, UnitWeighter, FxBuildHasher>,
23 account_latest_cache: Cache<Address, Account, UnitWeighter, FxBuildHasher>,
24 slot_latest_cache: Cache<(Address, SlotIndex), SlotValue, UnitWeighter, FxBuildHasher>,
25}
26
27#[derive(DebugAsJson, Clone, Parser, serde::Serialize)]
28pub struct CacheConfig {
29 #[arg(long = "slot-cache-capacity", env = "SLOT_CACHE_CAPACITY", default_value = "100000")]
31 pub slot_cache_capacity: usize,
32
33 #[arg(long = "account-cache-capacity", env = "ACCOUNT_CACHE_CAPACITY", default_value = "20000")]
35 pub account_cache_capacity: usize,
36
37 #[arg(long = "account-history-cache-capacity", env = "ACCOUNT_HISTORY_CACHE_CAPACITY", default_value = "20000")]
39 pub account_history_cache_capacity: usize,
40
41 #[arg(long = "slot-history-cache-capacity", env = "SLOT_HISTORY_CACHE_CAPACITY", default_value = "100000")]
43 pub slot_history_cache_capacity: usize,
44}
45
46impl CacheConfig {
47 pub fn init(&self) -> StorageCache {
48 StorageCache::new(self)
49 }
50}
51
52impl StorageCache {
53 pub fn new(config: &CacheConfig) -> Self {
54 Self {
55 slot_cache: Cache::with(
56 config.slot_cache_capacity,
57 config.slot_cache_capacity as u64,
58 UnitWeighter,
59 FxBuildHasher,
60 DefaultLifecycle::default(),
61 ),
62 account_cache: Cache::with(
63 config.account_cache_capacity,
64 config.account_cache_capacity as u64,
65 UnitWeighter,
66 FxBuildHasher,
67 DefaultLifecycle::default(),
68 ),
69 account_latest_cache: Cache::with(
70 config.account_history_cache_capacity,
71 config.account_history_cache_capacity as u64,
72 UnitWeighter,
73 FxBuildHasher,
74 DefaultLifecycle::default(),
75 ),
76 slot_latest_cache: Cache::with(
77 config.slot_history_cache_capacity,
78 config.slot_history_cache_capacity as u64,
79 UnitWeighter,
80 FxBuildHasher,
81 DefaultLifecycle::default(),
82 ),
83 }
84 }
85
86 pub fn clear(&self) {
87 self.slot_cache.clear();
88 self.account_cache.clear();
89 self.account_latest_cache.clear();
90 self.slot_latest_cache.clear();
91 }
92
93 pub fn cache_slot_if_missing(&self, address: Address, slot: Slot) {
94 self.slot_cache.insert_if_missing((address, slot.index), slot.value);
95 }
96
97 pub fn cache_account_if_missing(&self, account: Account) {
98 self.account_cache.insert_if_missing(account.address, account);
99 }
100
101 pub fn cache_account_and_slots_from_changes(&self, changes: ExecutionChanges) {
102 for (address, change) in changes {
103 for slot in change.slots.into_values().flat_map(|slot| slot.take()) {
105 self.slot_cache.insert((address, slot.index), slot.value);
106 }
107
108 let mut account = AccountWithSlots::new(address);
110 if let Some(nonce) = change.nonce.take_ref() {
111 account.info.nonce = *nonce;
112 }
113 if let Some(balance) = change.balance.take_ref() {
114 account.info.balance = *balance;
115 }
116 if let Some(Some(bytecode)) = change.bytecode.take_ref() {
117 account.info.bytecode = Some(bytecode.clone());
118 }
119 self.account_cache.insert(address, account.info);
120 }
121 }
122
123 pub fn cache_account_and_slots_latest_from_changes(&self, changes: ExecutionChanges) {
124 for (address, change) in changes {
125 for slot in change.slots.into_values().flat_map(|slot| slot.take()) {
127 self.slot_latest_cache.insert((address, slot.index), slot.value);
128 }
129
130 let mut account = AccountWithSlots::new(address);
132 if let Some(nonce) = change.nonce.take_ref() {
133 account.info.nonce = *nonce;
134 }
135 if let Some(balance) = change.balance.take_ref() {
136 account.info.balance = *balance;
137 }
138 if let Some(Some(bytecode)) = change.bytecode.take_ref() {
139 account.info.bytecode = Some(bytecode.clone());
140 }
141 self.account_latest_cache.insert(address, account.info);
142 }
143 }
144
145 pub fn get_slot(&self, address: Address, index: SlotIndex) -> Option<Slot> {
146 self.slot_cache.get(&(address, index)).map(|value| Slot { value, index })
147 }
148
149 pub fn get_account(&self, address: Address) -> Option<Account> {
150 self.account_cache.get(&address)
151 }
152
153 pub fn cache_account_latest_if_missing(&self, address: Address, account: Account) {
154 self.account_latest_cache.insert_if_missing(address, account);
155 }
156
157 pub fn cache_slot_latest_if_missing(&self, address: Address, slot: Slot) {
158 self.slot_latest_cache.insert_if_missing((address, slot.index), slot.value);
159 }
160
161 pub fn get_account_latest(&self, address: Address) -> Option<Account> {
162 self.account_latest_cache.get(&address)
163 }
164
165 pub fn get_slot_latest(&self, address: Address, index: SlotIndex) -> Option<Slot> {
166 self.slot_latest_cache.get(&(address, index)).map(|value| Slot { value, index })
167 }
168}
169
170trait CacheExt<Key, Val> {
171 fn insert_if_missing(&self, key: Key, val: Val);
172}
173
174impl<Key, Val, We, B, L> CacheExt<Key, Val> for Cache<Key, Val, We, B, L>
175where
176 Key: Hash + Equivalent<Key> + ToOwned<Owned = Key> + std::cmp::Eq,
177 Val: Clone,
178 We: quick_cache::Weighter<Key, Val> + Clone,
179 B: std::hash::BuildHasher + Clone,
180 L: quick_cache::Lifecycle<Key, Val> + Clone,
181{
182 fn insert_if_missing(&self, key: Key, val: Val) {
183 match self.get_value_or_guard(&key, None) {
184 GuardResult::Value(_) => (),
185 GuardResult::Guard(g) => {
186 let _ = g.insert(val);
188 }
189 GuardResult::Timeout => unreachable!(),
190 }
191 }
192}