anvil/eth/backend/
executor.rs

1use crate::{
2    PrecompileFactory,
3    eth::{
4        backend::{
5            cheats::{CheatEcrecover, CheatsManager},
6            db::Db,
7            env::Env,
8            mem::op_haltreason_to_instruction_result,
9            validate::TransactionValidator,
10        },
11        error::InvalidTransactionError,
12        pool::transactions::PoolTransaction,
13    },
14    inject_custom_precompiles,
15    mem::inspector::AnvilInspector,
16};
17use alloy_consensus::{
18    Receipt, ReceiptWithBloom, constants::EMPTY_WITHDRAWALS, proofs::calculate_receipt_root,
19};
20use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams};
21use alloy_evm::{
22    EthEvm, Evm,
23    eth::EthEvmContext,
24    precompiles::{DynPrecompile, Precompile, PrecompilesMap},
25};
26use alloy_op_evm::OpEvm;
27use alloy_primitives::{B256, Bloom, BloomInput, Log};
28use anvil_core::eth::{
29    block::{Block, BlockInfo, PartialHeader},
30    transaction::{PendingTransaction, TransactionInfo, TypedReceipt, TypedTransaction},
31};
32use foundry_evm::{
33    backend::DatabaseError,
34    core::{either_evm::EitherEvm, precompiles::EC_RECOVER},
35    traces::{CallTraceDecoder, CallTraceNode},
36};
37use foundry_evm_networks::NetworkConfigs;
38use op_revm::{L1BlockInfo, OpContext, precompiles::OpPrecompiles};
39use revm::{
40    Database, DatabaseRef, Inspector, Journal,
41    context::{Block as RevmBlock, BlockEnv, Cfg, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext},
42    context_interface::result::{EVMError, ExecutionResult, Output},
43    database::WrapDatabaseRef,
44    handler::{EthPrecompiles, instructions::EthInstructions},
45    interpreter::InstructionResult,
46    precompile::{PrecompileSpecId, Precompiles},
47    primitives::hardfork::SpecId,
48};
49use std::{fmt::Debug, sync::Arc};
50
51/// Represents an executed transaction (transacted on the DB)
52#[derive(Debug)]
53pub struct ExecutedTransaction {
54    transaction: Arc<PoolTransaction>,
55    exit_reason: InstructionResult,
56    out: Option<Output>,
57    gas_used: u64,
58    logs: Vec<Log>,
59    traces: Vec<CallTraceNode>,
60    nonce: u64,
61}
62
63// == impl ExecutedTransaction ==
64
65impl ExecutedTransaction {
66    /// Creates the receipt for the transaction
67    fn create_receipt(&self, cumulative_gas_used: &mut u64) -> TypedReceipt {
68        let logs = self.logs.clone();
69        *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used);
70
71        // successful return see [Return]
72        let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8);
73        let receipt_with_bloom: ReceiptWithBloom = Receipt {
74            status: (status_code == 1).into(),
75            cumulative_gas_used: *cumulative_gas_used,
76            logs,
77        }
78        .into();
79
80        match &self.transaction.pending_transaction.transaction.transaction {
81            TypedTransaction::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom),
82            TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom),
83            TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom),
84            TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom),
85            TypedTransaction::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom),
86            TypedTransaction::Deposit(_tx) => {
87                TypedReceipt::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom {
88                    receipt: op_alloy_consensus::OpDepositReceipt {
89                        inner: receipt_with_bloom.receipt,
90                        deposit_nonce: Some(0),
91                        deposit_receipt_version: Some(1),
92                    },
93                    logs_bloom: receipt_with_bloom.logs_bloom,
94                })
95            }
96        }
97    }
98}
99
100/// Represents the outcome of mining a new block
101#[derive(Clone, Debug)]
102pub struct ExecutedTransactions {
103    /// The block created after executing the `included` transactions
104    pub block: BlockInfo,
105    /// All transactions included in the block
106    pub included: Vec<Arc<PoolTransaction>>,
107    /// All transactions that were invalid at the point of their execution and were not included in
108    /// the block
109    pub invalid: Vec<Arc<PoolTransaction>>,
110}
111
112/// An executor for a series of transactions
113pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> {
114    /// where to insert the transactions
115    pub db: &'a mut Db,
116    /// type used to validate before inclusion
117    pub validator: &'a V,
118    /// all pending transactions
119    pub pending: std::vec::IntoIter<Arc<PoolTransaction>>,
120    pub block_env: BlockEnv,
121    /// The configuration environment and spec id
122    pub cfg_env: CfgEnv,
123    pub parent_hash: B256,
124    /// Cumulative gas used by all executed transactions
125    pub gas_used: u64,
126    /// Cumulative blob gas used by all executed transactions
127    pub blob_gas_used: u64,
128    pub enable_steps_tracing: bool,
129    pub networks: NetworkConfigs,
130    pub print_logs: bool,
131    pub print_traces: bool,
132    /// Recorder used for decoding traces, used together with print_traces
133    pub call_trace_decoder: Arc<CallTraceDecoder>,
134    /// Precompiles to inject to the EVM.
135    pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
136    pub blob_params: BlobParams,
137    pub cheats: CheatsManager,
138}
139
140impl<DB: Db + ?Sized, V: TransactionValidator> TransactionExecutor<'_, DB, V> {
141    /// Executes all transactions and puts them in a new block with the provided `timestamp`
142    pub fn execute(mut self) -> ExecutedTransactions {
143        let mut transactions = Vec::new();
144        let mut transaction_infos = Vec::new();
145        let mut receipts = Vec::new();
146        let mut bloom = Bloom::default();
147        let mut cumulative_gas_used = 0u64;
148        let mut invalid = Vec::new();
149        let mut included = Vec::new();
150        let gas_limit = self.block_env.gas_limit;
151        let parent_hash = self.parent_hash;
152        let block_number = self.block_env.number;
153        let difficulty = self.block_env.difficulty;
154        let mix_hash = self.block_env.prevrandao;
155        let beneficiary = self.block_env.beneficiary;
156        let timestamp = self.block_env.timestamp;
157        let base_fee = if self.cfg_env.spec.is_enabled_in(SpecId::LONDON) {
158            Some(self.block_env.basefee)
159        } else {
160            None
161        };
162
163        let is_shanghai = self.cfg_env.spec >= SpecId::SHANGHAI;
164        let is_cancun = self.cfg_env.spec >= SpecId::CANCUN;
165        let is_prague = self.cfg_env.spec >= SpecId::PRAGUE;
166        let excess_blob_gas = if is_cancun { self.block_env.blob_excess_gas() } else { None };
167        let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None };
168
169        for tx in self.into_iter() {
170            let tx = match tx {
171                TransactionExecutionOutcome::Executed(tx) => {
172                    included.push(tx.transaction.clone());
173                    tx
174                }
175                TransactionExecutionOutcome::BlockGasExhausted(tx) => {
176                    trace!(target: "backend",  tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx,  "block gas limit exhausting, skipping transaction");
177                    continue;
178                }
179                TransactionExecutionOutcome::BlobGasExhausted(tx) => {
180                    trace!(target: "backend",  blob_gas = %tx.pending_transaction.transaction.blob_gas().unwrap_or_default(), ?tx,  "block blob gas limit exhausting, skipping transaction");
181                    continue;
182                }
183                TransactionExecutionOutcome::TransactionGasExhausted(tx) => {
184                    trace!(target: "backend",  tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx,  "transaction gas limit exhausting, skipping transaction");
185                    continue;
186                }
187                TransactionExecutionOutcome::Invalid(tx, _) => {
188                    trace!(target: "backend", ?tx,  "skipping invalid transaction");
189                    invalid.push(tx);
190                    continue;
191                }
192                TransactionExecutionOutcome::DatabaseError(_, err) => {
193                    // Note: this is only possible in forking mode, if for example a rpc request
194                    // failed
195                    trace!(target: "backend", ?err,  "Failed to execute transaction due to database error");
196                    continue;
197                }
198            };
199            if is_cancun {
200                let tx_blob_gas = tx
201                    .transaction
202                    .pending_transaction
203                    .transaction
204                    .transaction
205                    .blob_gas()
206                    .unwrap_or(0);
207                cumulative_blob_gas_used =
208                    Some(cumulative_blob_gas_used.unwrap_or(0u64).saturating_add(tx_blob_gas));
209            }
210            let receipt = tx.create_receipt(&mut cumulative_gas_used);
211
212            let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx;
213            build_logs_bloom(logs.clone(), &mut bloom);
214
215            let contract_address = out.as_ref().and_then(|out| {
216                if let Output::Create(_, contract_address) = out {
217                    trace!(target: "backend", "New contract deployed: at {:?}", contract_address);
218                    *contract_address
219                } else {
220                    None
221                }
222            });
223
224            let transaction_index = transaction_infos.len() as u64;
225            let info = TransactionInfo {
226                transaction_hash: transaction.hash(),
227                transaction_index,
228                from: *transaction.pending_transaction.sender(),
229                to: transaction.pending_transaction.transaction.to(),
230                contract_address,
231                traces,
232                exit,
233                out: out.map(Output::into_data),
234                nonce: tx.nonce,
235                gas_used: tx.gas_used,
236            };
237
238            transaction_infos.push(info);
239            receipts.push(receipt);
240            transactions.push(transaction.pending_transaction.transaction.clone());
241        }
242
243        let receipts_root = calculate_receipt_root(&receipts);
244
245        let partial_header = PartialHeader {
246            parent_hash,
247            beneficiary,
248            state_root: self.db.maybe_state_root().unwrap_or_default(),
249            receipts_root,
250            logs_bloom: bloom,
251            difficulty,
252            number: block_number.saturating_to(),
253            gas_limit,
254            gas_used: cumulative_gas_used,
255            timestamp: timestamp.saturating_to(),
256            extra_data: Default::default(),
257            mix_hash: mix_hash.unwrap_or_default(),
258            nonce: Default::default(),
259            base_fee,
260            parent_beacon_block_root: is_cancun.then_some(Default::default()),
261            blob_gas_used: cumulative_blob_gas_used,
262            excess_blob_gas,
263            withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
264            requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
265        };
266
267        let block = Block::new(partial_header, transactions.clone());
268        let block = BlockInfo { block, transactions: transaction_infos, receipts };
269        ExecutedTransactions { block, included, invalid }
270    }
271
272    fn env_for(&self, tx: &PendingTransaction) -> Env {
273        let mut tx_env = tx.to_revm_tx_env();
274
275        if self.networks.is_optimism() {
276            tx_env.enveloped_tx = Some(alloy_rlp::encode(&tx.transaction.transaction).into());
277        }
278
279        Env::new(self.cfg_env.clone(), self.block_env.clone(), tx_env, self.networks)
280    }
281}
282
283/// Represents the result of a single transaction execution attempt
284#[derive(Debug)]
285pub enum TransactionExecutionOutcome {
286    /// Transaction successfully executed
287    Executed(ExecutedTransaction),
288    /// Invalid transaction not executed
289    Invalid(Arc<PoolTransaction>, InvalidTransactionError),
290    /// Execution skipped because could exceed block gas limit
291    BlockGasExhausted(Arc<PoolTransaction>),
292    /// Execution skipped because it exceeded the blob gas limit
293    BlobGasExhausted(Arc<PoolTransaction>),
294    /// Execution skipped because it exceeded the transaction gas limit
295    TransactionGasExhausted(Arc<PoolTransaction>),
296    /// When an error occurred during execution
297    DatabaseError(Arc<PoolTransaction>, DatabaseError),
298}
299
300impl<DB: Db + ?Sized, V: TransactionValidator> Iterator for &mut TransactionExecutor<'_, DB, V> {
301    type Item = TransactionExecutionOutcome;
302
303    fn next(&mut self) -> Option<Self::Item> {
304        let transaction = self.pending.next()?;
305        let sender = *transaction.pending_transaction.sender();
306        let account = match self.db.basic(sender).map(|acc| acc.unwrap_or_default()) {
307            Ok(account) => account,
308            Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)),
309        };
310        let env = self.env_for(&transaction.pending_transaction);
311
312        // check that we comply with the block's gas limit, if not disabled
313        let max_block_gas = self.gas_used.saturating_add(env.tx.base.gas_limit);
314        if !env.evm_env.cfg_env.disable_block_gas_limit
315            && max_block_gas > env.evm_env.block_env.gas_limit
316        {
317            return Some(TransactionExecutionOutcome::BlockGasExhausted(transaction));
318        }
319
320        // check that we comply with the transaction's gas limit as imposed by Osaka (EIP-7825)
321        if env.evm_env.cfg_env.tx_gas_limit_cap.is_none()
322            && transaction.pending_transaction.transaction.gas_limit()
323                > env.evm_env.cfg_env().tx_gas_limit_cap()
324        {
325            return Some(TransactionExecutionOutcome::TransactionGasExhausted(transaction));
326        }
327
328        // check that we comply with the block's blob gas limit
329        let max_blob_gas = self.blob_gas_used.saturating_add(
330            transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0),
331        );
332        if max_blob_gas > self.blob_params.max_blob_gas_per_block() {
333            return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction));
334        }
335
336        // validate before executing
337        if let Err(err) = self.validator.validate_pool_transaction_for(
338            &transaction.pending_transaction,
339            &account,
340            &env,
341        ) {
342            warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", transaction.hash(), err);
343            return Some(TransactionExecutionOutcome::Invalid(transaction, err));
344        }
345
346        let nonce = account.nonce;
347
348        let mut inspector = AnvilInspector::default().with_tracing();
349        if self.enable_steps_tracing {
350            inspector = inspector.with_steps_tracing();
351        }
352        if self.print_logs {
353            inspector = inspector.with_log_collector();
354        }
355        if self.print_traces {
356            inspector = inspector.with_trace_printer();
357        }
358
359        let exec_result = {
360            let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector);
361            self.networks.inject_precompiles(evm.precompiles_mut());
362
363            if let Some(factory) = &self.precompile_factory {
364                inject_custom_precompiles(&mut evm, factory.precompiles());
365            }
366
367            let cheats = Arc::new(self.cheats.clone());
368            if cheats.has_recover_overrides() {
369                let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
370                evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| {
371                    Some(DynPrecompile::new_stateful(
372                        cheat_ecrecover.precompile_id().clone(),
373                        move |input| cheat_ecrecover.call(input),
374                    ))
375                });
376            }
377
378            trace!(target: "backend", "[{:?}] executing", transaction.hash());
379            // transact and commit the transaction
380            match evm.transact_commit(env.tx) {
381                Ok(exec_result) => exec_result,
382                Err(err) => {
383                    warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err);
384                    match err {
385                        EVMError::Database(err) => {
386                            return Some(TransactionExecutionOutcome::DatabaseError(
387                                transaction,
388                                err,
389                            ));
390                        }
391                        EVMError::Transaction(err) => {
392                            return Some(TransactionExecutionOutcome::Invalid(
393                                transaction,
394                                err.into(),
395                            ));
396                        }
397                        // This will correspond to prevrandao not set, and it should never happen.
398                        // If it does, it's a bug.
399                        e => panic!("failed to execute transaction: {e}"),
400                    }
401                }
402            }
403        };
404
405        if self.print_traces {
406            inspector.print_traces(self.call_trace_decoder.clone());
407        }
408        inspector.print_logs();
409
410        let (exit_reason, gas_used, out, logs) = match exec_result {
411            ExecutionResult::Success { reason, gas_used, logs, output, .. } => {
412                (reason.into(), gas_used, Some(output), Some(logs))
413            }
414            ExecutionResult::Revert { gas_used, output } => {
415                (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None)
416            }
417            ExecutionResult::Halt { reason, gas_used } => {
418                (op_haltreason_to_instruction_result(reason), gas_used, None, None)
419            }
420        };
421
422        if exit_reason == InstructionResult::OutOfGas {
423            // this currently useful for debugging estimations
424            warn!(target: "backend", "[{:?}] executed with out of gas", transaction.hash())
425        }
426
427        trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out);
428
429        // Track the total gas used for total gas per block checks
430        self.gas_used = self.gas_used.saturating_add(gas_used);
431
432        // Track the total blob gas used for total blob gas per blob checks
433        if let Some(blob_gas) = transaction.pending_transaction.transaction.transaction.blob_gas() {
434            self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas);
435        }
436
437        trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used);
438
439        let tx = ExecutedTransaction {
440            transaction,
441            exit_reason,
442            out,
443            gas_used,
444            logs: logs.unwrap_or_default(),
445            traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(),
446            nonce,
447        };
448
449        Some(TransactionExecutionOutcome::Executed(tx))
450    }
451}
452
453/// Inserts all logs into the bloom
454fn build_logs_bloom(logs: Vec<Log>, bloom: &mut Bloom) {
455    for log in logs {
456        bloom.accrue(BloomInput::Raw(&log.address[..]));
457        for topic in log.topics() {
458            bloom.accrue(BloomInput::Raw(&topic[..]));
459        }
460    }
461}
462
463/// Creates a database with given database and inspector.
464pub fn new_evm_with_inspector<DB, I>(
465    db: DB,
466    env: &Env,
467    inspector: I,
468) -> EitherEvm<DB, I, PrecompilesMap>
469where
470    DB: Database<Error = DatabaseError> + Debug,
471    I: Inspector<EthEvmContext<DB>> + Inspector<OpContext<DB>>,
472{
473    if env.networks.is_optimism() {
474        let op_cfg = env.evm_env.cfg_env.clone().with_spec(op_revm::OpSpecId::ISTHMUS);
475        let op_context = OpContext {
476            journaled_state: {
477                let mut journal = Journal::new(db);
478                // Converting SpecId into OpSpecId
479                journal.set_spec_id(env.evm_env.cfg_env.spec);
480                journal
481            },
482            block: env.evm_env.block_env.clone(),
483            cfg: op_cfg.clone(),
484            tx: env.tx.clone(),
485            chain: L1BlockInfo::default(),
486            local: LocalContext::default(),
487            error: Ok(()),
488        };
489
490        let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles();
491        let op_evm = op_revm::OpEvm(RevmEvm::new_with_inspector(
492            op_context,
493            inspector,
494            EthInstructions::default(),
495            PrecompilesMap::from_static(op_precompiles),
496        ));
497
498        let op = OpEvm::new(op_evm, true);
499
500        EitherEvm::Op(op)
501    } else {
502        let spec = env.evm_env.cfg_env.spec;
503        let eth_context = EthEvmContext {
504            journaled_state: {
505                let mut journal = Journal::new(db);
506                journal.set_spec_id(spec);
507                journal
508            },
509            block: env.evm_env.block_env.clone(),
510            cfg: env.evm_env.cfg_env.clone(),
511            tx: env.tx.base.clone(),
512            chain: (),
513            local: LocalContext::default(),
514            error: Ok(()),
515        };
516
517        let eth_precompiles = EthPrecompiles {
518            precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
519            spec,
520        }
521        .precompiles;
522        let eth_evm = RevmEvm::new_with_inspector(
523            eth_context,
524            inspector,
525            EthInstructions::default(),
526            PrecompilesMap::from_static(eth_precompiles),
527        );
528
529        let eth = EthEvm::new(eth_evm, true);
530
531        EitherEvm::Eth(eth)
532    }
533}
534
535/// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`.
536pub fn new_evm_with_inspector_ref<'db, DB, I>(
537    db: &'db DB,
538    env: &Env,
539    inspector: &'db mut I,
540) -> EitherEvm<WrapDatabaseRef<&'db DB>, &'db mut I, PrecompilesMap>
541where
542    DB: DatabaseRef<Error = DatabaseError> + Debug + 'db + ?Sized,
543    I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>
544        + Inspector<OpContext<WrapDatabaseRef<&'db DB>>>,
545    WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
546{
547    new_evm_with_inspector(WrapDatabaseRef(db), env, inspector)
548}