anvil/eth/backend/
executor.rs

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