1use ethrex_common::{
2 Address, H256, U256,
3 constants::EMPTY_KECCAK_HASH,
4 types::{AccountState, BlockHash, BlockHeader, BlockNumber, ChainConfig, Code, CodeMetadata},
5};
6use ethrex_crypto::keccak::keccak_hash;
7use ethrex_storage::Store;
8use ethrex_vm::{EvmError, VmDatabase};
9use rustc_hash::FxHashMap;
10use std::{
11 cmp::Ordering,
12 collections::BTreeMap,
13 sync::{Arc, Mutex, RwLock},
14};
15use tracing::instrument;
16
17#[derive(Clone, Copy)]
18struct AccountStateCacheEntry {
19 state: AccountState,
20 hashed_address: H256,
21}
22
23type AccountStateCache = FxHashMap<Address, Option<AccountStateCacheEntry>>;
24
25#[derive(Clone)]
26pub struct StoreVmDatabase {
27 pub store: Store,
28 pub block_hash: BlockHash,
29 pub block_hash_cache: Arc<Mutex<BTreeMap<BlockNumber, BlockHash>>>,
33 account_state_cache: Arc<RwLock<AccountStateCache>>,
37 pub state_root: H256,
38}
39
40impl StoreVmDatabase {
41 pub fn new(store: Store, block_header: BlockHeader) -> Result<Self, EvmError> {
42 if !store
47 .has_state_root(block_header.state_root)
48 .map_err(|e| EvmError::DB(e.to_string()))?
49 {
50 return Err(EvmError::DB(format!(
51 "state root missing for block {} (state_root {:#x})",
52 block_header.number, block_header.state_root
53 )));
54 }
55 Ok(StoreVmDatabase {
56 store,
57 block_hash: block_header.hash(),
58 block_hash_cache: Arc::new(Mutex::new(BTreeMap::new())),
59 account_state_cache: Arc::new(RwLock::new(FxHashMap::default())),
60 state_root: block_header.state_root,
61 })
62 }
63
64 pub fn new_with_block_hash_cache(
65 store: Store,
66 block_header: BlockHeader,
67 block_hash_cache: BTreeMap<BlockNumber, BlockHash>,
68 ) -> Result<Self, EvmError> {
69 if !store
71 .has_state_root(block_header.state_root)
72 .map_err(|e| EvmError::DB(e.to_string()))?
73 {
74 return Err(EvmError::DB(format!(
75 "state root missing for block {} (state_root {:#x})",
76 block_header.number, block_header.state_root
77 )));
78 }
79 Ok(StoreVmDatabase {
80 store,
81 block_hash: block_header.hash(),
82 block_hash_cache: Arc::new(Mutex::new(block_hash_cache)),
83 account_state_cache: Arc::new(RwLock::new(FxHashMap::default())),
84 state_root: block_header.state_root,
85 })
86 }
87
88 #[cfg(any(test, feature = "testing"))]
92 pub fn new_for_test(store: Store) -> Self {
93 StoreVmDatabase {
94 store,
95 block_hash: H256::zero(),
96 block_hash_cache: Arc::new(Mutex::new(BTreeMap::new())),
97 account_state_cache: Arc::new(RwLock::new(FxHashMap::default())),
98 state_root: H256::zero(),
99 }
100 }
101
102 fn get_cached_account_state_entry(
103 &self,
104 address: Address,
105 ) -> Result<Option<AccountStateCacheEntry>, EvmError> {
106 if let Some(entry) = self
107 .account_state_cache
108 .read()
109 .map_err(|_| EvmError::Custom("LockError".to_string()))?
110 .get(&address)
111 .copied()
112 {
113 return Ok(entry);
114 }
115
116 let loaded = self
117 .store
118 .get_account_state_by_root(self.state_root, address)
119 .map_err(|e| EvmError::DB(e.to_string()))?;
120 let cached = loaded.map(|state| AccountStateCacheEntry {
121 state,
122 hashed_address: H256::from(keccak_hash(address.to_fixed_bytes())),
123 });
124 self.account_state_cache
125 .write()
126 .map_err(|_| EvmError::Custom("LockError".to_string()))?
127 .insert(address, cached);
128 Ok(cached)
129 }
130}
131
132impl VmDatabase for StoreVmDatabase {
133 #[instrument(
134 level = "trace",
135 name = "Account read",
136 skip_all,
137 fields(namespace = "block_execution")
138 )]
139 fn get_account_state(&self, address: Address) -> Result<Option<AccountState>, EvmError> {
140 Ok(self
141 .get_cached_account_state_entry(address)?
142 .map(|entry| entry.state))
143 }
144
145 #[instrument(
146 level = "trace",
147 name = "Storage read",
148 skip_all,
149 fields(namespace = "block_execution")
150 )]
151 fn get_storage_slot(&self, address: Address, key: H256) -> Result<Option<U256>, EvmError> {
152 let Some(entry) = self.get_cached_account_state_entry(address)? else {
153 return Ok(None);
154 };
155 self.store
156 .get_storage_at_root_with_known_storage_root(
157 self.state_root,
158 entry.hashed_address,
159 entry.state.storage_root,
160 key,
161 )
162 .map_err(|e| EvmError::DB(e.to_string()))
163 }
164
165 #[instrument(
166 level = "trace",
167 name = "Block hash read",
168 skip_all,
169 fields(namespace = "block_execution")
170 )]
171 fn get_block_hash(&self, block_number: u64) -> Result<H256, EvmError> {
172 let mut block_hash_cache = self
173 .block_hash_cache
174 .lock()
175 .map_err(|_| EvmError::Custom("LockError".to_string()))?;
176 if let Some(block_hash) = block_hash_cache.get(&block_number) {
178 return Ok(*block_hash);
179 }
180 if self
182 .store
183 .is_canonical_sync(self.block_hash)
184 .map_err(|err| EvmError::DB(err.to_string()))?
185 {
186 if let Some(hash) = self
187 .store
188 .get_canonical_block_hash_sync(block_number)
189 .map_err(|err| EvmError::DB(err.to_string()))?
190 {
191 block_hash_cache.insert(block_number, hash);
192 return Ok(hash);
193 }
194 } else {
196 let oldest_succesor = block_hash_cache
198 .iter()
199 .find_map(|(key, hash)| (*key > block_number).then_some(*hash))
200 .unwrap_or(self.block_hash);
201 for ancestor_res in self.store.ancestors(oldest_succesor) {
202 let (hash, ancestor) = ancestor_res.map_err(|e| EvmError::DB(e.to_string()))?;
203 block_hash_cache.insert(ancestor.number, hash);
204 match ancestor.number.cmp(&block_number) {
205 Ordering::Greater => continue,
206 Ordering::Equal => return Ok(hash),
207 Ordering::Less => {
208 return Err(EvmError::DB(format!(
209 "Block number requested {block_number} is higher than the current block number {}",
210 ancestor.number
211 )));
212 }
213 }
214 }
215 }
216 Err(EvmError::DB(format!(
218 "Block hash not found for block number {block_number}"
219 )))
220 }
221
222 fn get_chain_config(&self) -> Result<ChainConfig, EvmError> {
223 Ok(self.store.get_chain_config())
224 }
225
226 #[instrument(
227 level = "trace",
228 name = "Account code read",
229 skip_all,
230 fields(namespace = "block_execution")
231 )]
232 fn get_account_code(&self, code_hash: H256) -> Result<Code, EvmError> {
233 if code_hash == *EMPTY_KECCAK_HASH {
234 return Ok(Code::default());
235 }
236 match self.store.get_account_code(code_hash) {
237 Ok(Some(code)) => Ok(code),
238 Ok(None) => Err(EvmError::DB(format!(
239 "Code not found for hash: {code_hash:?}",
240 ))),
241 Err(e) => Err(EvmError::DB(e.to_string())),
242 }
243 }
244
245 #[instrument(
246 level = "trace",
247 name = "Code metadata read",
248 skip_all,
249 fields(namespace = "block_execution")
250 )]
251 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, EvmError> {
252 use ethrex_common::constants::EMPTY_KECCAK_HASH;
253
254 if code_hash == *EMPTY_KECCAK_HASH {
255 return Ok(CodeMetadata { length: 0 });
256 }
257 match self.store.get_code_metadata(code_hash) {
258 Ok(Some(metadata)) => Ok(metadata),
259 Ok(None) => Err(EvmError::DB(format!(
260 "Code metadata not found for hash: {code_hash:?}",
261 ))),
262 Err(e) => Err(EvmError::DB(e.to_string())),
263 }
264 }
265}