anvil/eth/backend/
executor.rs

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