anvil/eth/backend/
executor.rs

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