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};
19pub use 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<Arc<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 mut 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 mut receipt = Receipt::new(
154 tx.tx_type(),
155 execution_report.is_success(),
156 *cumulative_gas_spent,
157 execution_report.logs.clone(),
158 );
159
160 if matches!(tx, Transaction::FrameTransaction(_)) {
162 receipt.payer = execution_report.payer_address;
163 receipt.frame_receipts = execution_report.frame_results.take().map(|results| {
164 results
165 .into_iter()
166 .map(
167 |(status, gas_used, logs)| ethrex_common::types::FrameReceipt {
168 status,
169 gas_used,
170 logs,
171 },
172 )
173 .collect()
174 });
175 }
176
177 Ok((receipt, execution_report))
178 }
179
180 pub fn undo_last_tx(&mut self) -> Result<(), EvmError> {
181 LEVM::undo_last_tx(&mut self.db)
182 }
183
184 pub fn apply_system_calls(&mut self, block_header: &BlockHeader) -> Result<(), EvmError> {
187 let chain_config = self.db.store.get_chain_config()?;
188 let fork = chain_config.fork(block_header.timestamp);
189
190 if fork >= Fork::Hegota && matches!(self.vm_type, VMType::L1) {
194 LEVM::install_expiry_verifier_code(&mut self.db, self.crypto.as_ref())?;
195 }
196
197 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
198 LEVM::beacon_root_contract_call(
199 block_header,
200 &mut self.db,
201 self.vm_type,
202 self.crypto.as_ref(),
203 )?;
204 }
205
206 if fork >= Fork::Prague {
207 LEVM::process_block_hash_history(
208 block_header,
209 &mut self.db,
210 self.vm_type,
211 self.crypto.as_ref(),
212 )?;
213 }
214
215 Ok(())
216 }
217
218 pub fn get_state_transitions(&mut self) -> Result<Vec<AccountUpdate>, EvmError> {
221 LEVM::get_state_transitions(&mut self.db)
222 }
223
224 pub fn process_withdrawals(&mut self, withdrawals: &[Withdrawal]) -> Result<(), EvmError> {
227 LEVM::process_withdrawals(&mut self.db, withdrawals)
228 }
229
230 pub fn extract_requests(
231 &mut self,
232 receipts: &[Receipt],
233 header: &BlockHeader,
234 ) -> Result<Vec<Requests>, EvmError> {
235 levm::extract_all_requests_levm(
236 receipts,
237 &mut self.db,
238 header,
239 self.vm_type,
240 self.crypto.as_ref(),
241 )
242 }
243
244 pub fn take_bal(&mut self) -> Option<BlockAccessList> {
247 self.db.take_bal()
248 }
249
250 pub fn enable_bal_recording(&mut self) {
252 self.db.enable_bal_recording();
253 }
254
255 pub fn set_bal_index(&mut self, index: u32) {
257 self.db.set_bal_index(index);
258 }
259
260 pub fn simulate_tx_from_generic(
261 &mut self,
262 tx: &GenericTransaction,
263 header: &BlockHeader,
264 ) -> Result<ExecutionResult, EvmError> {
265 LEVM::simulate_tx_from_generic(tx, header, &mut self.db, self.vm_type, self.crypto.as_ref())
266 }
267
268 pub fn simulate_frame_validation_prefix(
271 &mut self,
272 tx: &Transaction,
273 header: &BlockHeader,
274 prefix: ðrex_common::types::ValidationPrefix,
275 canonical_paymaster_code_hash: Option<ethrex_common::H256>,
276 ) -> Result<FrameValidationOutcome, EvmError> {
277 LEVM::simulate_frame_validation_prefix(
278 tx,
279 header,
280 &mut self.db,
281 self.vm_type,
282 self.crypto.as_ref(),
283 prefix,
284 canonical_paymaster_code_hash,
285 )
286 }
287
288 pub fn create_access_list(
289 &mut self,
290 tx: &GenericTransaction,
291 header: &BlockHeader,
292 ) -> Result<(u64, AccessList, Option<String>), EvmError> {
293 let result = {
294 LEVM::create_access_list(
295 tx.clone(),
296 header,
297 &mut self.db,
298 self.vm_type,
299 self.crypto.as_ref(),
300 )?
301 };
302
303 match result {
304 (
305 ExecutionResult::Success {
306 gas_used,
307 gas_refunded: _,
308 logs: _,
309 output: _,
310 },
311 access_list,
312 ) => Ok((gas_used, access_list, None)),
313 (
314 ExecutionResult::Revert {
315 gas_used,
316 output: _,
317 },
318 access_list,
319 ) => Ok((
320 gas_used,
321 access_list,
322 Some("Transaction Reverted".to_string()),
323 )),
324 (ExecutionResult::Halt { reason, gas_used }, access_list) => {
325 Ok((gas_used, access_list, Some(reason)))
326 }
327 }
328 }
329}
330
331#[derive(Clone, Debug)]
335pub struct FrameValidationOutcome {
336 pub passed: bool,
339 pub violation: Option<String>,
342 pub max_cost: ethrex_common::U256,
345 pub accessed_paymaster: Option<(Address, bool)>,
349 pub touched_sender_slots: Vec<ethrex_common::H256>,
352}
353
354#[derive(Clone, Debug)]
355pub struct BlockExecutionResult {
356 pub receipts: Vec<Receipt>,
357 pub requests: Vec<Requests>,
358 pub block_gas_used: u64,
361 pub tx_gas_breakdowns: Vec<TxGasBreakdown>,
366}
367
368#[derive(Clone, Debug)]
372pub struct TxGasBreakdown {
373 pub tx_index: usize,
374 pub tx_hash: ethrex_common::H256,
375 pub status: TxStatus,
376 pub gas_used: u64,
378 pub gas_spent: u64,
380 pub gas_refunded: u64,
381 pub state_gas_used: u64,
383 pub regular_gas_used: u64,
385}
386
387#[derive(Clone, Copy, Debug, PartialEq, Eq)]
388pub enum TxStatus {
389 Success,
390 Revert,
391 Halt,
392}
393
394impl core::fmt::Display for TxStatus {
395 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
396 match self {
397 TxStatus::Success => f.write_str("success"),
398 TxStatus::Revert => f.write_str("revert"),
399 TxStatus::Halt => f.write_str("halt"),
400 }
401 }
402}
403
404impl TxGasBreakdown {
405 pub fn from_report(
406 tx_index: usize,
407 tx_hash: ethrex_common::H256,
408 report: &ExecutionReport,
409 ) -> Self {
410 let status = match &report.result {
411 TxResult::Success => TxStatus::Success,
412 TxResult::Revert(err) if err.is_revert_opcode() => TxStatus::Revert,
413 TxResult::Revert(_) => TxStatus::Halt,
414 };
415 Self {
416 tx_index,
417 tx_hash,
418 status,
419 gas_used: report.gas_used,
420 gas_spent: report.gas_spent,
421 gas_refunded: report.gas_refunded,
422 state_gas_used: report.state_gas_used,
423 regular_gas_used: report.gas_used.saturating_sub(report.state_gas_used),
424 }
425 }
426}
427
428pub fn log_gas_used_mismatch(
432 breakdowns: &[TxGasBreakdown],
433 block_number: u64,
434 actual: u64,
435 expected: u64,
436) {
437 let delta = actual as i128 - expected as i128;
438 if breakdowns.is_empty() {
439 ::tracing::error!(
440 block = block_number,
441 actual,
442 expected,
443 delta,
444 "block gas_used mismatch (no per-tx breakdown available on this path)",
445 );
446 return;
447 }
448 let sum_regular: u64 = breakdowns.iter().map(|b| b.regular_gas_used).sum();
449 let sum_state: u64 = breakdowns.iter().map(|b| b.state_gas_used).sum();
450 let sum_refunded: u64 = breakdowns.iter().map(|b| b.gas_refunded).sum();
451 ::tracing::error!(
452 block = block_number,
453 actual,
454 expected,
455 delta,
456 n_txs = breakdowns.len(),
457 sum_regular,
458 sum_state,
459 max_dim = sum_regular.max(sum_state),
460 sum_refunded,
461 "block gas_used mismatch",
462 );
463 for b in breakdowns {
464 ::tracing::error!(
465 tx_idx = b.tx_index,
466 tx_hash = %b.tx_hash,
467 status = %b.status,
468 gas_used = b.gas_used,
469 regular = b.regular_gas_used,
470 state = b.state_gas_used,
471 gas_spent = b.gas_spent,
472 gas_refunded = b.gas_refunded,
473 " tx breakdown",
474 );
475 }
476}