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