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 fn get_cached_account_state_entry(
89 &self,
90 address: Address,
91 ) -> Result<Option<AccountStateCacheEntry>, EvmError> {
92 if let Some(entry) = self
93 .account_state_cache
94 .read()
95 .map_err(|_| EvmError::Custom("LockError".to_string()))?
96 .get(&address)
97 .copied()
98 {
99 return Ok(entry);
100 }
101
102 let loaded = self
103 .store
104 .get_account_state_by_root(self.state_root, address)
105 .map_err(|e| EvmError::DB(e.to_string()))?;
106 let cached = loaded.map(|state| AccountStateCacheEntry {
107 state,
108 hashed_address: H256::from(keccak_hash(address.to_fixed_bytes())),
109 });
110 self.account_state_cache
111 .write()
112 .map_err(|_| EvmError::Custom("LockError".to_string()))?
113 .insert(address, cached);
114 Ok(cached)
115 }
116}
117
118impl VmDatabase for StoreVmDatabase {
119 #[instrument(
120 level = "trace",
121 name = "Account read",
122 skip_all,
123 fields(namespace = "block_execution")
124 )]
125 fn get_account_state(&self, address: Address) -> Result<Option<AccountState>, EvmError> {
126 Ok(self
127 .get_cached_account_state_entry(address)?
128 .map(|entry| entry.state))
129 }
130
131 #[instrument(
132 level = "trace",
133 name = "Storage read",
134 skip_all,
135 fields(namespace = "block_execution")
136 )]
137 fn get_storage_slot(&self, address: Address, key: H256) -> Result<Option<U256>, EvmError> {
138 let Some(entry) = self.get_cached_account_state_entry(address)? else {
139 return Ok(None);
140 };
141 self.store
142 .get_storage_at_root_with_known_storage_root(
143 self.state_root,
144 entry.hashed_address,
145 entry.state.storage_root,
146 key,
147 )
148 .map_err(|e| EvmError::DB(e.to_string()))
149 }
150
151 #[instrument(
152 level = "trace",
153 name = "Block hash read",
154 skip_all,
155 fields(namespace = "block_execution")
156 )]
157 fn get_block_hash(&self, block_number: u64) -> Result<H256, EvmError> {
158 let mut block_hash_cache = self
159 .block_hash_cache
160 .lock()
161 .map_err(|_| EvmError::Custom("LockError".to_string()))?;
162 if let Some(block_hash) = block_hash_cache.get(&block_number) {
164 return Ok(*block_hash);
165 }
166 if self
168 .store
169 .is_canonical_sync(self.block_hash)
170 .map_err(|err| EvmError::DB(err.to_string()))?
171 {
172 if let Some(hash) = self
173 .store
174 .get_canonical_block_hash_sync(block_number)
175 .map_err(|err| EvmError::DB(err.to_string()))?
176 {
177 block_hash_cache.insert(block_number, hash);
178 return Ok(hash);
179 }
180 } else {
182 let oldest_succesor = block_hash_cache
184 .iter()
185 .find_map(|(key, hash)| (*key > block_number).then_some(*hash))
186 .unwrap_or(self.block_hash);
187 for ancestor_res in self.store.ancestors(oldest_succesor) {
188 let (hash, ancestor) = ancestor_res.map_err(|e| EvmError::DB(e.to_string()))?;
189 block_hash_cache.insert(ancestor.number, hash);
190 match ancestor.number.cmp(&block_number) {
191 Ordering::Greater => continue,
192 Ordering::Equal => return Ok(hash),
193 Ordering::Less => {
194 return Err(EvmError::DB(format!(
195 "Block number requested {block_number} is higher than the current block number {}",
196 ancestor.number
197 )));
198 }
199 }
200 }
201 }
202 Err(EvmError::DB(format!(
204 "Block hash not found for block number {block_number}"
205 )))
206 }
207
208 fn get_chain_config(&self) -> Result<ChainConfig, EvmError> {
209 Ok(self.store.get_chain_config())
210 }
211
212 #[instrument(
213 level = "trace",
214 name = "Account code read",
215 skip_all,
216 fields(namespace = "block_execution")
217 )]
218 fn get_account_code(&self, code_hash: H256) -> Result<Code, EvmError> {
219 if code_hash == *EMPTY_KECCAK_HASH {
220 return Ok(Code::default());
221 }
222 match self.store.get_account_code(code_hash) {
223 Ok(Some(code)) => Ok(code),
224 Ok(None) => Err(EvmError::DB(format!(
225 "Code not found for hash: {code_hash:?}",
226 ))),
227 Err(e) => Err(EvmError::DB(e.to_string())),
228 }
229 }
230
231 #[instrument(
232 level = "trace",
233 name = "Code metadata read",
234 skip_all,
235 fields(namespace = "block_execution")
236 )]
237 fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, EvmError> {
238 use ethrex_common::constants::EMPTY_KECCAK_HASH;
239
240 if code_hash == *EMPTY_KECCAK_HASH {
241 return Ok(CodeMetadata { length: 0 });
242 }
243 match self.store.get_code_metadata(code_hash) {
244 Ok(Some(metadata)) => Ok(metadata),
245 Ok(None) => Err(EvmError::DB(format!(
246 "Code metadata not found for hash: {code_hash:?}",
247 ))),
248 Err(e) => Err(EvmError::DB(e.to_string())),
249 }
250 }
251}