Skip to main content

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