1pub mod levm;
2use ethrex_levm::precompiles::PrecompileMoves;
3use levm::LEVM;
4
5use crate::db::{DynVmDatabase, VmDatabase};
6use crate::errors::EvmError;
7use crate::execution_result::ExecutionResult;
8use ethrex_common::types::block_access_list::BlockAccessList;
9use ethrex_common::types::requests::Requests;
10use ethrex_common::types::{
11 AccessList, AccountUpdate, Block, BlockHeader, Fork, GenericTransaction, Receipt, Transaction,
12 Withdrawal,
13};
14use ethrex_common::{Address, types::fee_config::FeeConfig};
15use ethrex_crypto::Crypto;
16pub use ethrex_levm::StatelessValidator;
17pub use ethrex_levm::call_frame::CallFrameBackup;
18use ethrex_levm::db::gen_db::GeneralizedDatabase;
19pub use ethrex_levm::db::{BLOATED_BATCH_THRESHOLD, CachingDatabase, Database as LevmDatabase};
20pub use ethrex_levm::errors::{self, InternalError, VMError};
21use ethrex_levm::errors::{ExecutionReport, TxResult};
22pub use ethrex_levm::vm::VMType;
23use std::sync::Arc;
24use std::sync::atomic::AtomicUsize;
25use std::sync::mpsc::Sender;
26use tracing::instrument;
27
28#[derive(Clone)]
29pub struct Evm {
30 pub db: GeneralizedDatabase,
31 pub vm_type: VMType,
32 pub crypto: Arc<dyn Crypto>,
33 pub stateless_validator: Option<Arc<dyn StatelessValidator>>,
34}
35
36impl core::fmt::Debug for Evm {
37 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38 write!(f, "LEVM",)
39 }
40}
41
42impl Evm {
43 pub fn set_precompile_moves(&mut self, moves: PrecompileMoves) {
48 self.db.precompile_moves = (!moves.is_empty()).then(|| Arc::new(moves));
49 }
50
51 pub fn new_for_l1(db: impl VmDatabase + 'static, crypto: Arc<dyn Crypto>) -> Self {
53 let wrapped_db: DynVmDatabase = Box::new(db);
54 Evm {
55 db: GeneralizedDatabase::new(Arc::new(wrapped_db)),
56 vm_type: VMType::L1,
57 crypto,
58 stateless_validator: None,
59 }
60 }
61
62 pub fn new_for_l2(
63 db: impl VmDatabase + 'static,
64 fee_config: FeeConfig,
65 crypto: Arc<dyn Crypto>,
66 ) -> Result<Self, EvmError> {
67 let wrapped_db: DynVmDatabase = Box::new(db);
68
69 let evm = Evm {
70 db: GeneralizedDatabase::new(Arc::new(wrapped_db)),
71 vm_type: VMType::L2(fee_config),
72 crypto,
73 stateless_validator: None,
74 };
75
76 Ok(evm)
77 }
78
79 pub fn new_from_db_for_l1(
80 store: Arc<impl LevmDatabase + 'static>,
81 crypto: Arc<dyn Crypto>,
82 ) -> Self {
83 Self::_new_from_db(store, VMType::L1, crypto)
84 }
85
86 pub fn new_from_db_for_l2(
87 store: Arc<impl LevmDatabase + 'static>,
88 fee_config: FeeConfig,
89 crypto: Arc<dyn Crypto>,
90 ) -> Self {
91 Self::_new_from_db(store, VMType::L2(fee_config), crypto)
92 }
93
94 fn _new_from_db(
95 store: Arc<impl LevmDatabase + 'static>,
96 vm_type: VMType,
97 crypto: Arc<dyn Crypto>,
98 ) -> Self {
99 Evm {
100 db: GeneralizedDatabase::new(store),
101 vm_type,
102 crypto,
103 stateless_validator: None,
104 }
105 }
106
107 pub fn execute_block(
112 &mut self,
113 block: &Block,
114 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
115 LEVM::execute_block(
116 block,
117 &mut self.db,
118 self.vm_type,
119 self.crypto.as_ref(),
120 self.stateless_validator.as_deref(),
121 )
122 }
123
124 #[instrument(
125 level = "trace",
126 name = "Block execution",
127 skip_all,
128 fields(namespace = "block_execution")
129 )]
130 pub fn execute_block_pipeline(
131 &mut self,
132 block: &Block,
133 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
134 queue_length: &AtomicUsize,
135 bal: Option<Arc<BlockAccessList>>,
136 bal_parallel_exec_enabled: bool,
137 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
138 LEVM::execute_block_pipeline(
139 block,
140 &mut self.db,
141 self.vm_type,
142 merkleizer,
143 queue_length,
144 self.crypto.as_ref(),
145 bal,
146 bal_parallel_exec_enabled,
147 self.stateless_validator.as_deref(),
148 )
149 }
150
151 #[allow(clippy::too_many_arguments)]
156 pub fn execute_tx(
157 &mut self,
158 tx: &Transaction,
159 block_header: &BlockHeader,
160 cumulative_gas_spent: &mut u64,
161 sender: Address,
162 ) -> Result<(Receipt, ExecutionReport), EvmError> {
163 let mut execution_report = LEVM::execute_tx(
164 tx,
165 sender,
166 block_header,
167 &mut self.db,
168 self.vm_type,
169 self.crypto.as_ref(),
170 self.stateless_validator.as_deref(),
171 )?;
172
173 *cumulative_gas_spent += execution_report.gas_spent;
175
176 let mut receipt = Receipt::new(
177 tx.tx_type(),
178 execution_report.is_success(),
179 *cumulative_gas_spent,
180 execution_report.logs.clone(),
181 );
182
183 if matches!(tx, Transaction::FrameTransaction(_)) {
185 receipt.payer = execution_report.payer_address;
186 receipt.frame_receipts = execution_report.frame_results.take().map(|results| {
187 results
188 .into_iter()
189 .map(
190 |(status, gas_used, logs)| ethrex_common::types::FrameReceipt {
191 status,
192 gas_used,
193 logs,
194 },
195 )
196 .collect()
197 });
198 }
199
200 Ok((receipt, execution_report))
201 }
202
203 pub fn undo_last_tx(&mut self) -> Result<(), EvmError> {
204 LEVM::undo_last_tx(&mut self.db)
205 }
206
207 pub fn apply_system_calls(&mut self, block_header: &BlockHeader) -> Result<(), EvmError> {
210 let chain_config = self.db.store.get_chain_config()?;
211 let fork = chain_config.fork(block_header.timestamp);
212
213 if fork >= Fork::Hegota && matches!(self.vm_type, VMType::L1) {
217 LEVM::install_expiry_verifier_code(&mut self.db, self.crypto.as_ref())?;
218 }
219
220 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
221 LEVM::beacon_root_contract_call(
222 block_header,
223 &mut self.db,
224 self.vm_type,
225 self.crypto.as_ref(),
226 )?;
227 }
228
229 if fork >= Fork::Prague {
230 LEVM::process_block_hash_history(
231 block_header,
232 &mut self.db,
233 self.vm_type,
234 self.crypto.as_ref(),
235 )?;
236 }
237
238 Ok(())
239 }
240
241 pub fn get_state_transitions(&mut self) -> Result<Vec<AccountUpdate>, EvmError> {
244 LEVM::get_state_transitions(&mut self.db)
245 }
246
247 pub fn process_withdrawals(&mut self, withdrawals: &[Withdrawal]) -> Result<(), EvmError> {
250 LEVM::process_withdrawals(&mut self.db, withdrawals)
251 }
252
253 pub fn extract_requests(
254 &mut self,
255 receipts: &[Receipt],
256 header: &BlockHeader,
257 ) -> Result<Vec<Requests>, EvmError> {
258 levm::extract_all_requests_levm(
259 receipts,
260 &mut self.db,
261 header,
262 self.vm_type,
263 self.crypto.as_ref(),
264 )
265 }
266
267 pub fn take_bal(&mut self) -> Option<BlockAccessList> {
270 self.db.take_bal()
271 }
272
273 pub fn enable_bal_recording(&mut self) {
275 self.db.enable_bal_recording();
276 }
277
278 pub fn set_bal_index(&mut self, index: u32) {
280 self.db.set_bal_index(index);
281 }
282
283 pub fn simulate_tx_from_generic(
284 &mut self,
285 tx: &GenericTransaction,
286 header: &BlockHeader,
287 ) -> Result<ExecutionResult, EvmError> {
288 LEVM::simulate_tx_from_generic(
289 tx,
290 header,
291 &mut self.db,
292 self.vm_type,
293 self.crypto.as_ref(),
294 self.stateless_validator.as_deref(),
295 )
296 }
297
298 pub fn simulate_frame_validation_prefix(
301 &mut self,
302 tx: &Transaction,
303 header: &BlockHeader,
304 prefix: ðrex_common::types::ValidationPrefix,
305 canonical_paymaster_code_hash: Option<ethrex_common::H256>,
306 ) -> Result<FrameValidationOutcome, EvmError> {
307 LEVM::simulate_frame_validation_prefix(
308 tx,
309 header,
310 &mut self.db,
311 self.vm_type,
312 self.crypto.as_ref(),
313 prefix,
314 canonical_paymaster_code_hash,
315 )
316 }
317
318 pub fn create_access_list(
319 &mut self,
320 tx: &GenericTransaction,
321 header: &BlockHeader,
322 ) -> Result<(u64, AccessList, Option<String>), EvmError> {
323 let result = {
324 LEVM::create_access_list(
325 tx.clone(),
326 header,
327 &mut self.db,
328 self.vm_type,
329 self.crypto.as_ref(),
330 )?
331 };
332
333 match result {
334 (
335 ExecutionResult::Success {
336 gas_used,
337 gas_refunded: _,
338 logs: _,
339 output: _,
340 },
341 access_list,
342 ) => Ok((gas_used, access_list, None)),
343 (
344 ExecutionResult::Revert {
345 gas_used,
346 output: _,
347 },
348 access_list,
349 ) => Ok((
350 gas_used,
351 access_list,
352 Some("Transaction Reverted".to_string()),
353 )),
354 (ExecutionResult::Halt { reason, gas_used }, access_list) => {
355 Ok((gas_used, access_list, Some(reason)))
356 }
357 }
358 }
359}
360
361pub fn compute_burned_fees(
373 base_fee_per_gas: u64,
374 gas_spent: u64,
375 blob_base_fee: u64,
376 blob_gas_used: u64,
377) -> u64 {
378 base_fee_per_gas
379 .saturating_mul(gas_spent)
380 .saturating_add(blob_base_fee.saturating_mul(blob_gas_used))
381}
382
383#[derive(Clone, Debug)]
387pub struct FrameValidationOutcome {
388 pub passed: bool,
391 pub violation: Option<String>,
394 pub reservation_ceiling: ethrex_common::U256,
400 pub accessed_paymaster: Option<(Address, bool)>,
404 pub touched_sender_slots: Vec<ethrex_common::H256>,
407}
408
409#[derive(Clone, Debug)]
410pub struct BlockExecutionResult {
411 pub receipts: Vec<Receipt>,
412 pub requests: Vec<Requests>,
413 pub block_gas_used: u64,
416 pub burned_fees: Option<u64>,
422 pub tx_gas_breakdowns: Vec<TxGasBreakdown>,
427}
428
429#[derive(Clone, Debug)]
433pub struct TxGasBreakdown {
434 pub tx_index: usize,
435 pub tx_hash: ethrex_common::H256,
436 pub status: TxStatus,
437 pub gas_used: u64,
439 pub gas_spent: u64,
441 pub gas_refunded: u64,
442 pub state_gas_used: u64,
444 pub regular_gas_used: u64,
446}
447
448#[derive(Clone, Copy, Debug, PartialEq, Eq)]
449pub enum TxStatus {
450 Success,
451 Revert,
452 Halt,
453}
454
455impl core::fmt::Display for TxStatus {
456 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
457 match self {
458 TxStatus::Success => f.write_str("success"),
459 TxStatus::Revert => f.write_str("revert"),
460 TxStatus::Halt => f.write_str("halt"),
461 }
462 }
463}
464
465impl TxGasBreakdown {
466 pub fn from_report(
467 tx_index: usize,
468 tx_hash: ethrex_common::H256,
469 report: &ExecutionReport,
470 ) -> Self {
471 let status = match &report.result {
472 TxResult::Success => TxStatus::Success,
473 TxResult::Revert(err) if err.is_revert_opcode() => TxStatus::Revert,
474 TxResult::Revert(_) => TxStatus::Halt,
475 };
476 Self {
477 tx_index,
478 tx_hash,
479 status,
480 gas_used: report.gas_used,
481 gas_spent: report.gas_spent,
482 gas_refunded: report.gas_refunded,
483 state_gas_used: report.state_gas_used,
484 regular_gas_used: report.gas_used.saturating_sub(report.state_gas_used),
485 }
486 }
487}
488
489pub fn log_gas_used_mismatch(
493 breakdowns: &[TxGasBreakdown],
494 block_number: u64,
495 actual: u64,
496 expected: u64,
497) {
498 let delta = actual as i128 - expected as i128;
499 if breakdowns.is_empty() {
500 ::tracing::error!(
501 block = block_number,
502 actual,
503 expected,
504 delta,
505 "block gas_used mismatch (no per-tx breakdown available on this path)",
506 );
507 return;
508 }
509 let sum_regular: u64 = breakdowns.iter().map(|b| b.regular_gas_used).sum();
510 let sum_state: u64 = breakdowns.iter().map(|b| b.state_gas_used).sum();
511 let sum_refunded: u64 = breakdowns.iter().map(|b| b.gas_refunded).sum();
512 ::tracing::error!(
513 block = block_number,
514 actual,
515 expected,
516 delta,
517 n_txs = breakdowns.len(),
518 sum_regular,
519 sum_state,
520 max_dim = sum_regular.max(sum_state),
521 sum_refunded,
522 "block gas_used mismatch",
523 );
524 for b in breakdowns {
525 ::tracing::error!(
526 tx_idx = b.tx_index,
527 tx_hash = %b.tx_hash,
528 status = %b.status,
529 gas_used = b.gas_used,
530 regular = b.regular_gas_used,
531 state = b.state_gas_used,
532 gas_spent = b.gas_spent,
533 gas_refunded = b.gas_refunded,
534 " tx breakdown",
535 );
536 }
537}