1pub mod levm;
2use levm::LEVM;
3
4use crate::db::{DynVmDatabase, VmDatabase};
5use crate::errors::EvmError;
6use crate::execution_result::ExecutionResult;
7use ethrex_common::types::block_access_list::BlockAccessList;
8use ethrex_common::types::requests::Requests;
9use ethrex_common::types::{
10 AccessList, AccountUpdate, Block, BlockHeader, Fork, GenericTransaction, Receipt, Transaction,
11 Withdrawal,
12};
13use ethrex_common::{Address, types::fee_config::FeeConfig};
14use ethrex_crypto::Crypto;
15pub use ethrex_levm::call_frame::CallFrameBackup;
16use ethrex_levm::db::gen_db::GeneralizedDatabase;
17pub use ethrex_levm::db::{CachingDatabase, Database as LevmDatabase};
18use ethrex_levm::errors::{ExecutionReport, TxResult};
19use ethrex_levm::vm::VMType;
20use std::sync::Arc;
21use std::sync::atomic::AtomicUsize;
22use std::sync::mpsc::Sender;
23use tracing::instrument;
24
25#[derive(Clone)]
26pub struct Evm {
27 pub db: GeneralizedDatabase,
28 pub vm_type: VMType,
29 pub crypto: Arc<dyn Crypto>,
30}
31
32impl core::fmt::Debug for Evm {
33 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34 write!(f, "LEVM",)
35 }
36}
37
38impl Evm {
39 pub fn new_for_l1(db: impl VmDatabase + 'static, crypto: Arc<dyn Crypto>) -> Self {
41 let wrapped_db: DynVmDatabase = Box::new(db);
42 Evm {
43 db: GeneralizedDatabase::new(Arc::new(wrapped_db)),
44 vm_type: VMType::L1,
45 crypto,
46 }
47 }
48
49 pub fn new_for_l2(
50 db: impl VmDatabase + 'static,
51 fee_config: FeeConfig,
52 crypto: Arc<dyn Crypto>,
53 ) -> Result<Self, EvmError> {
54 let wrapped_db: DynVmDatabase = Box::new(db);
55
56 let evm = Evm {
57 db: GeneralizedDatabase::new(Arc::new(wrapped_db)),
58 vm_type: VMType::L2(fee_config),
59 crypto,
60 };
61
62 Ok(evm)
63 }
64
65 pub fn new_from_db_for_l1(
66 store: Arc<impl LevmDatabase + 'static>,
67 crypto: Arc<dyn Crypto>,
68 ) -> Self {
69 Self::_new_from_db(store, VMType::L1, crypto)
70 }
71
72 pub fn new_from_db_for_l2(
73 store: Arc<impl LevmDatabase + 'static>,
74 fee_config: FeeConfig,
75 crypto: Arc<dyn Crypto>,
76 ) -> Self {
77 Self::_new_from_db(store, VMType::L2(fee_config), crypto)
78 }
79
80 fn _new_from_db(
81 store: Arc<impl LevmDatabase + 'static>,
82 vm_type: VMType,
83 crypto: Arc<dyn Crypto>,
84 ) -> Self {
85 Evm {
86 db: GeneralizedDatabase::new(store),
87 vm_type,
88 crypto,
89 }
90 }
91
92 pub fn execute_block(
97 &mut self,
98 block: &Block,
99 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
100 LEVM::execute_block(block, &mut self.db, self.vm_type, self.crypto.as_ref())
101 }
102
103 #[instrument(
104 level = "trace",
105 name = "Block execution",
106 skip_all,
107 fields(namespace = "block_execution")
108 )]
109 pub fn execute_block_pipeline(
110 &mut self,
111 block: &Block,
112 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
113 queue_length: &AtomicUsize,
114 bal: Option<&BlockAccessList>,
115 bal_parallel_exec_enabled: bool,
116 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
117 LEVM::execute_block_pipeline(
118 block,
119 &mut self.db,
120 self.vm_type,
121 merkleizer,
122 queue_length,
123 self.crypto.as_ref(),
124 bal,
125 bal_parallel_exec_enabled,
126 )
127 }
128
129 #[allow(clippy::too_many_arguments)]
134 pub fn execute_tx(
135 &mut self,
136 tx: &Transaction,
137 block_header: &BlockHeader,
138 cumulative_gas_spent: &mut u64,
139 sender: Address,
140 ) -> Result<(Receipt, ExecutionReport), EvmError> {
141 let execution_report = LEVM::execute_tx(
142 tx,
143 sender,
144 block_header,
145 &mut self.db,
146 self.vm_type,
147 self.crypto.as_ref(),
148 )?;
149
150 *cumulative_gas_spent += execution_report.gas_spent;
152
153 let receipt = Receipt::new(
154 tx.tx_type(),
155 execution_report.is_success(),
156 *cumulative_gas_spent,
157 execution_report.logs.clone(),
158 );
159
160 Ok((receipt, execution_report))
161 }
162
163 pub fn undo_last_tx(&mut self) -> Result<(), EvmError> {
164 LEVM::undo_last_tx(&mut self.db)
165 }
166
167 pub fn apply_system_calls(&mut self, block_header: &BlockHeader) -> Result<(), EvmError> {
170 let chain_config = self.db.store.get_chain_config()?;
171 let fork = chain_config.fork(block_header.timestamp);
172
173 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
174 LEVM::beacon_root_contract_call(
175 block_header,
176 &mut self.db,
177 self.vm_type,
178 self.crypto.as_ref(),
179 )?;
180 }
181
182 if fork >= Fork::Prague {
183 LEVM::process_block_hash_history(
184 block_header,
185 &mut self.db,
186 self.vm_type,
187 self.crypto.as_ref(),
188 )?;
189 }
190
191 Ok(())
192 }
193
194 pub fn get_state_transitions(&mut self) -> Result<Vec<AccountUpdate>, EvmError> {
197 LEVM::get_state_transitions(&mut self.db)
198 }
199
200 pub fn process_withdrawals(&mut self, withdrawals: &[Withdrawal]) -> Result<(), EvmError> {
203 LEVM::process_withdrawals(&mut self.db, withdrawals)
204 }
205
206 pub fn extract_requests(
207 &mut self,
208 receipts: &[Receipt],
209 header: &BlockHeader,
210 ) -> Result<Vec<Requests>, EvmError> {
211 levm::extract_all_requests_levm(
212 receipts,
213 &mut self.db,
214 header,
215 self.vm_type,
216 self.crypto.as_ref(),
217 )
218 }
219
220 pub fn take_bal(&mut self) -> Option<BlockAccessList> {
223 self.db.take_bal()
224 }
225
226 pub fn enable_bal_recording(&mut self) {
228 self.db.enable_bal_recording();
229 }
230
231 pub fn set_bal_index(&mut self, index: u32) {
233 self.db.set_bal_index(index);
234 }
235
236 pub fn simulate_tx_from_generic(
237 &mut self,
238 tx: &GenericTransaction,
239 header: &BlockHeader,
240 ) -> Result<ExecutionResult, EvmError> {
241 LEVM::simulate_tx_from_generic(tx, header, &mut self.db, self.vm_type, self.crypto.as_ref())
242 }
243
244 pub fn create_access_list(
245 &mut self,
246 tx: &GenericTransaction,
247 header: &BlockHeader,
248 ) -> Result<(u64, AccessList, Option<String>), EvmError> {
249 let result = {
250 LEVM::create_access_list(
251 tx.clone(),
252 header,
253 &mut self.db,
254 self.vm_type,
255 self.crypto.as_ref(),
256 )?
257 };
258
259 match result {
260 (
261 ExecutionResult::Success {
262 gas_used,
263 gas_refunded: _,
264 logs: _,
265 output: _,
266 },
267 access_list,
268 ) => Ok((gas_used, access_list, None)),
269 (
270 ExecutionResult::Revert {
271 gas_used,
272 output: _,
273 },
274 access_list,
275 ) => Ok((
276 gas_used,
277 access_list,
278 Some("Transaction Reverted".to_string()),
279 )),
280 (ExecutionResult::Halt { reason, gas_used }, access_list) => {
281 Ok((gas_used, access_list, Some(reason)))
282 }
283 }
284 }
285}
286
287#[derive(Clone, Debug)]
288pub struct BlockExecutionResult {
289 pub receipts: Vec<Receipt>,
290 pub requests: Vec<Requests>,
291 pub block_gas_used: u64,
294 pub tx_gas_breakdowns: Vec<TxGasBreakdown>,
299}
300
301#[derive(Clone, Debug)]
305pub struct TxGasBreakdown {
306 pub tx_index: usize,
307 pub tx_hash: ethrex_common::H256,
308 pub status: TxStatus,
309 pub gas_used: u64,
311 pub gas_spent: u64,
313 pub gas_refunded: u64,
314 pub state_gas_used: u64,
316 pub regular_gas_used: u64,
318}
319
320#[derive(Clone, Copy, Debug, PartialEq, Eq)]
321pub enum TxStatus {
322 Success,
323 Revert,
324 Halt,
325}
326
327impl core::fmt::Display for TxStatus {
328 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
329 match self {
330 TxStatus::Success => f.write_str("success"),
331 TxStatus::Revert => f.write_str("revert"),
332 TxStatus::Halt => f.write_str("halt"),
333 }
334 }
335}
336
337impl TxGasBreakdown {
338 pub fn from_report(
339 tx_index: usize,
340 tx_hash: ethrex_common::H256,
341 report: &ExecutionReport,
342 ) -> Self {
343 let status = match &report.result {
344 TxResult::Success => TxStatus::Success,
345 TxResult::Revert(err) if err.is_revert_opcode() => TxStatus::Revert,
346 TxResult::Revert(_) => TxStatus::Halt,
347 };
348 Self {
349 tx_index,
350 tx_hash,
351 status,
352 gas_used: report.gas_used,
353 gas_spent: report.gas_spent,
354 gas_refunded: report.gas_refunded,
355 state_gas_used: report.state_gas_used,
356 regular_gas_used: report.gas_used.saturating_sub(report.state_gas_used),
357 }
358 }
359}
360
361pub fn log_gas_used_mismatch(
365 breakdowns: &[TxGasBreakdown],
366 block_number: u64,
367 actual: u64,
368 expected: u64,
369) {
370 let delta = actual as i128 - expected as i128;
371 if breakdowns.is_empty() {
372 ::tracing::error!(
373 block = block_number,
374 actual,
375 expected,
376 delta,
377 "block gas_used mismatch (no per-tx breakdown available on this path)",
378 );
379 return;
380 }
381 let sum_regular: u64 = breakdowns.iter().map(|b| b.regular_gas_used).sum();
382 let sum_state: u64 = breakdowns.iter().map(|b| b.state_gas_used).sum();
383 let sum_refunded: u64 = breakdowns.iter().map(|b| b.gas_refunded).sum();
384 ::tracing::error!(
385 block = block_number,
386 actual,
387 expected,
388 delta,
389 n_txs = breakdowns.len(),
390 sum_regular,
391 sum_state,
392 max_dim = sum_regular.max(sum_state),
393 sum_refunded,
394 "block gas_used mismatch",
395 );
396 for b in breakdowns {
397 ::tracing::error!(
398 tx_idx = b.tx_index,
399 tx_hash = %b.tx_hash,
400 status = %b.status,
401 gas_used = b.gas_used,
402 regular = b.regular_gas_used,
403 state = b.state_gas_used,
404 gas_spent = b.gas_spent,
405 gas_refunded = b.gas_refunded,
406 " tx breakdown",
407 );
408 }
409}