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
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum ExecutionGasCap {
45 Enforced,
47 Lifted,
49}
50
51impl core::fmt::Debug for Evm {
52 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53 write!(f, "LEVM",)
54 }
55}
56
57impl Evm {
58 pub fn set_precompile_moves(&mut self, moves: PrecompileMoves) {
63 self.db.precompile_moves = (!moves.is_empty()).then(|| Arc::new(moves));
64 }
65
66 pub fn new_for_l1(db: impl VmDatabase + 'static, crypto: Arc<dyn Crypto>) -> Self {
68 let wrapped_db: DynVmDatabase = Box::new(db);
69 Evm {
70 db: GeneralizedDatabase::new(Arc::new(wrapped_db)),
71 vm_type: VMType::L1,
72 crypto,
73 stateless_validator: None,
74 }
75 }
76
77 pub fn new_for_l2(
78 db: impl VmDatabase + 'static,
79 fee_config: FeeConfig,
80 crypto: Arc<dyn Crypto>,
81 ) -> Result<Self, EvmError> {
82 let wrapped_db: DynVmDatabase = Box::new(db);
83
84 let evm = Evm {
85 db: GeneralizedDatabase::new(Arc::new(wrapped_db)),
86 vm_type: VMType::L2(fee_config),
87 crypto,
88 stateless_validator: None,
89 };
90
91 Ok(evm)
92 }
93
94 pub fn new_from_db_for_l1(
95 store: Arc<impl LevmDatabase + 'static>,
96 crypto: Arc<dyn Crypto>,
97 ) -> Self {
98 Self::_new_from_db(store, VMType::L1, crypto)
99 }
100
101 pub fn new_from_db_for_l2(
102 store: Arc<impl LevmDatabase + 'static>,
103 fee_config: FeeConfig,
104 crypto: Arc<dyn Crypto>,
105 ) -> Self {
106 Self::_new_from_db(store, VMType::L2(fee_config), crypto)
107 }
108
109 fn _new_from_db(
110 store: Arc<impl LevmDatabase + 'static>,
111 vm_type: VMType,
112 crypto: Arc<dyn Crypto>,
113 ) -> Self {
114 Evm {
115 db: GeneralizedDatabase::new(store),
116 vm_type,
117 crypto,
118 stateless_validator: None,
119 }
120 }
121
122 pub fn execute_block(
127 &mut self,
128 block: &Block,
129 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
130 LEVM::execute_block(
131 block,
132 &mut self.db,
133 self.vm_type,
134 self.crypto.as_ref(),
135 self.stateless_validator.as_deref(),
136 )
137 }
138
139 #[instrument(
140 level = "trace",
141 name = "Block execution",
142 skip_all,
143 fields(namespace = "block_execution")
144 )]
145 pub fn execute_block_pipeline(
146 &mut self,
147 block: &Block,
148 merkleizer: Option<Sender<Vec<AccountUpdate>>>,
149 queue_length: &AtomicUsize,
150 bal: Option<Arc<BlockAccessList>>,
151 bal_parallel_exec_enabled: bool,
152 ) -> Result<(BlockExecutionResult, Option<BlockAccessList>), EvmError> {
153 LEVM::execute_block_pipeline(
154 block,
155 &mut self.db,
156 self.vm_type,
157 merkleizer,
158 queue_length,
159 self.crypto.as_ref(),
160 bal,
161 bal_parallel_exec_enabled,
162 self.stateless_validator.as_deref(),
163 )
164 }
165
166 #[allow(clippy::too_many_arguments)]
171 pub fn execute_tx(
172 &mut self,
173 tx: &Transaction,
174 block_header: &BlockHeader,
175 cumulative_gas_spent: &mut u64,
176 sender: Address,
177 ) -> Result<(Receipt, ExecutionReport), EvmError> {
178 let mut execution_report = LEVM::execute_tx(
179 tx,
180 sender,
181 block_header,
182 &mut self.db,
183 self.vm_type,
184 self.crypto.as_ref(),
185 self.stateless_validator.as_deref(),
186 )?;
187
188 *cumulative_gas_spent += execution_report.gas_spent;
190
191 let mut receipt = Receipt::new(
192 tx.tx_type(),
193 execution_report.is_success(),
194 *cumulative_gas_spent,
195 execution_report.logs.clone(),
196 );
197
198 if matches!(tx, Transaction::FrameTransaction(_)) {
200 receipt.payer = execution_report.payer_address;
201 receipt.frame_receipts = execution_report.frame_results.take().map(|results| {
202 results
203 .into_iter()
204 .map(
205 |(status, gas_used, logs)| ethrex_common::types::FrameReceipt {
206 status,
207 gas_used,
208 logs,
209 },
210 )
211 .collect()
212 });
213 }
214
215 Ok((receipt, execution_report))
216 }
217
218 pub fn undo_last_tx(&mut self) -> Result<(), EvmError> {
219 LEVM::undo_last_tx(&mut self.db)
220 }
221
222 pub fn apply_system_calls(&mut self, block_header: &BlockHeader) -> Result<(), EvmError> {
225 let chain_config = self.db.store.get_chain_config()?;
226 let fork = chain_config.fork(block_header.timestamp);
227
228 if fork >= Fork::Hegota && matches!(self.vm_type, VMType::L1) {
232 LEVM::install_expiry_verifier_code(&mut self.db, self.crypto.as_ref())?;
233 }
234
235 if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun {
236 LEVM::beacon_root_contract_call(
237 block_header,
238 &mut self.db,
239 self.vm_type,
240 self.crypto.as_ref(),
241 )?;
242 }
243
244 if fork >= Fork::Prague {
245 LEVM::process_block_hash_history(
246 block_header,
247 &mut self.db,
248 self.vm_type,
249 self.crypto.as_ref(),
250 )?;
251 }
252
253 Ok(())
254 }
255
256 pub fn get_state_transitions(&mut self) -> Result<Vec<AccountUpdate>, EvmError> {
259 LEVM::get_state_transitions(&mut self.db)
260 }
261
262 pub fn process_withdrawals(&mut self, withdrawals: &[Withdrawal]) -> Result<(), EvmError> {
265 LEVM::process_withdrawals(&mut self.db, withdrawals)
266 }
267
268 pub fn extract_requests(
269 &mut self,
270 receipts: &[Receipt],
271 header: &BlockHeader,
272 ) -> Result<Vec<Requests>, EvmError> {
273 levm::extract_all_requests_levm(
274 receipts,
275 &mut self.db,
276 header,
277 self.vm_type,
278 self.crypto.as_ref(),
279 )
280 }
281
282 pub fn take_bal(&mut self) -> Option<BlockAccessList> {
285 self.db.take_bal()
286 }
287
288 pub fn enable_bal_recording(&mut self) {
290 self.db.enable_bal_recording();
291 }
292
293 pub fn set_bal_index(&mut self, index: u32) {
295 self.db.set_bal_index(index);
296 }
297
298 pub fn simulate_tx_from_generic(
299 &mut self,
300 tx: &GenericTransaction,
301 header: &BlockHeader,
302 execution_gas_cap: ExecutionGasCap,
303 ) -> Result<ExecutionResult, EvmError> {
304 LEVM::simulate_tx_from_generic(
305 tx,
306 header,
307 &mut self.db,
308 self.vm_type,
309 self.crypto.as_ref(),
310 self.stateless_validator.as_deref(),
311 execution_gas_cap,
312 )
313 }
314
315 pub fn simulate_frame_validation_prefix(
318 &mut self,
319 tx: &Transaction,
320 header: &BlockHeader,
321 prefix: ðrex_common::types::ValidationPrefix,
322 canonical_paymaster_code_hash: Option<ethrex_common::H256>,
323 ) -> Result<FrameValidationOutcome, EvmError> {
324 LEVM::simulate_frame_validation_prefix(
325 tx,
326 header,
327 &mut self.db,
328 self.vm_type,
329 self.crypto.as_ref(),
330 prefix,
331 canonical_paymaster_code_hash,
332 )
333 }
334
335 pub fn create_access_list(
336 &mut self,
337 tx: &GenericTransaction,
338 header: &BlockHeader,
339 ) -> Result<(u64, AccessList, Option<String>), EvmError> {
340 let result = {
341 LEVM::create_access_list(
342 tx.clone(),
343 header,
344 &mut self.db,
345 self.vm_type,
346 self.crypto.as_ref(),
347 )?
348 };
349
350 match result {
351 (
352 ExecutionResult::Success {
353 gas_used,
354 gas_refunded: _,
355 logs: _,
356 output: _,
357 },
358 access_list,
359 ) => Ok((gas_used, access_list, None)),
360 (
361 ExecutionResult::Revert {
362 gas_used,
363 output: _,
364 },
365 access_list,
366 ) => Ok((
367 gas_used,
368 access_list,
369 Some("Transaction Reverted".to_string()),
370 )),
371 (ExecutionResult::Halt { reason, gas_used }, access_list) => {
372 Ok((gas_used, access_list, Some(reason)))
373 }
374 }
375 }
376}
377
378pub fn compute_burned_fees(
390 base_fee_per_gas: u64,
391 gas_spent: u64,
392 blob_base_fee: u64,
393 blob_gas_used: u64,
394) -> u64 {
395 base_fee_per_gas
396 .saturating_mul(gas_spent)
397 .saturating_add(blob_base_fee.saturating_mul(blob_gas_used))
398}
399
400#[derive(Clone, Debug)]
404pub struct FrameValidationOutcome {
405 pub passed: bool,
408 pub violation: Option<String>,
411 pub reservation_ceiling: ethrex_common::U256,
417 pub accessed_paymaster: Option<(Address, bool)>,
421 pub touched_sender_slots: Vec<ethrex_common::H256>,
424}
425
426#[derive(Clone, Debug)]
427pub struct BlockExecutionResult {
428 pub receipts: Vec<Receipt>,
429 pub requests: Vec<Requests>,
430 pub block_gas_used: u64,
433 pub burned_fees: Option<u64>,
439 pub tx_gas_breakdowns: Vec<TxGasBreakdown>,
444}
445
446#[derive(Clone, Debug)]
450pub struct TxGasBreakdown {
451 pub tx_index: usize,
452 pub tx_hash: ethrex_common::H256,
453 pub status: TxStatus,
454 pub gas_used: u64,
456 pub gas_spent: u64,
458 pub gas_refunded: u64,
459 pub state_gas_used: u64,
461 pub regular_gas_used: u64,
463}
464
465#[derive(Clone, Copy, Debug, PartialEq, Eq)]
466pub enum TxStatus {
467 Success,
468 Revert,
469 Halt,
470}
471
472impl core::fmt::Display for TxStatus {
473 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
474 match self {
475 TxStatus::Success => f.write_str("success"),
476 TxStatus::Revert => f.write_str("revert"),
477 TxStatus::Halt => f.write_str("halt"),
478 }
479 }
480}
481
482impl TxGasBreakdown {
483 pub fn from_report(
484 tx_index: usize,
485 tx_hash: ethrex_common::H256,
486 report: &ExecutionReport,
487 ) -> Self {
488 let status = match &report.result {
489 TxResult::Success => TxStatus::Success,
490 TxResult::Revert(err) if err.is_revert_opcode() => TxStatus::Revert,
491 TxResult::Revert(_) => TxStatus::Halt,
492 };
493 Self {
494 tx_index,
495 tx_hash,
496 status,
497 gas_used: report.gas_used,
498 gas_spent: report.gas_spent,
499 gas_refunded: report.gas_refunded,
500 state_gas_used: report.state_gas_used,
501 regular_gas_used: report.gas_used.saturating_sub(report.state_gas_used),
502 }
503 }
504}
505
506pub fn log_gas_used_mismatch(
510 breakdowns: &[TxGasBreakdown],
511 block_number: u64,
512 actual: u64,
513 expected: u64,
514) {
515 let delta = actual as i128 - expected as i128;
516 if breakdowns.is_empty() {
517 ::tracing::error!(
518 block = block_number,
519 actual,
520 expected,
521 delta,
522 "block gas_used mismatch (no per-tx breakdown available on this path)",
523 );
524 return;
525 }
526 let sum_regular: u64 = breakdowns.iter().map(|b| b.regular_gas_used).sum();
527 let sum_state: u64 = breakdowns.iter().map(|b| b.state_gas_used).sum();
528 let sum_refunded: u64 = breakdowns.iter().map(|b| b.gas_refunded).sum();
529 ::tracing::error!(
530 block = block_number,
531 actual,
532 expected,
533 delta,
534 n_txs = breakdowns.len(),
535 sum_regular,
536 sum_state,
537 max_dim = sum_regular.max(sum_state),
538 sum_refunded,
539 "block gas_used mismatch",
540 );
541 for b in breakdowns {
542 ::tracing::error!(
543 tx_idx = b.tx_index,
544 tx_hash = %b.tx_hash,
545 status = %b.status,
546 gas_used = b.gas_used,
547 regular = b.regular_gas_used,
548 state = b.state_gas_used,
549 gas_spent = b.gas_spent,
550 gas_refunded = b.gas_refunded,
551 " tx breakdown",
552 );
553 }
554}