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