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 inject_custom_precompiles,
15 mem::inspector::AnvilInspector,
16};
17use alloy_consensus::{
18 Receipt, ReceiptWithBloom, constants::EMPTY_WITHDRAWALS, proofs::calculate_receipt_root,
19};
20use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams};
21use alloy_evm::{
22 EthEvm, Evm,
23 eth::EthEvmContext,
24 precompiles::{DynPrecompile, Precompile, PrecompilesMap},
25};
26use alloy_op_evm::OpEvm;
27use alloy_primitives::{B256, Bloom, BloomInput, Log};
28use anvil_core::eth::{
29 block::{Block, BlockInfo, PartialHeader},
30 transaction::{PendingTransaction, TransactionInfo, TypedReceipt, TypedTransaction},
31};
32use foundry_evm::{
33 backend::DatabaseError,
34 core::{either_evm::EitherEvm, precompiles::EC_RECOVER},
35 traces::{CallTraceDecoder, CallTraceNode},
36};
37use foundry_evm_networks::NetworkConfigs;
38use op_revm::{L1BlockInfo, OpContext, precompiles::OpPrecompiles};
39use revm::{
40 Database, DatabaseRef, Inspector, Journal,
41 context::{Block as RevmBlock, BlockEnv, Cfg, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext},
42 context_interface::result::{EVMError, ExecutionResult, Output},
43 database::WrapDatabaseRef,
44 handler::{EthPrecompiles, instructions::EthInstructions},
45 interpreter::InstructionResult,
46 precompile::{PrecompileSpecId, Precompiles},
47 primitives::hardfork::SpecId,
48};
49use std::{fmt::Debug, sync::Arc};
50
51#[derive(Debug)]
53pub struct ExecutedTransaction {
54 transaction: Arc<PoolTransaction>,
55 exit_reason: InstructionResult,
56 out: Option<Output>,
57 gas_used: u64,
58 logs: Vec<Log>,
59 traces: Vec<CallTraceNode>,
60 nonce: u64,
61}
62
63impl ExecutedTransaction {
66 fn create_receipt(&self, cumulative_gas_used: &mut u64) -> TypedReceipt {
68 let logs = self.logs.clone();
69 *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used);
70
71 let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8);
73 let receipt_with_bloom: ReceiptWithBloom = Receipt {
74 status: (status_code == 1).into(),
75 cumulative_gas_used: *cumulative_gas_used,
76 logs,
77 }
78 .into();
79
80 match &self.transaction.pending_transaction.transaction.transaction {
81 TypedTransaction::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom),
82 TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom),
83 TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom),
84 TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom),
85 TypedTransaction::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom),
86 TypedTransaction::Deposit(_tx) => {
87 TypedReceipt::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom {
88 receipt: op_alloy_consensus::OpDepositReceipt {
89 inner: receipt_with_bloom.receipt,
90 deposit_nonce: Some(0),
91 deposit_receipt_version: Some(1),
92 },
93 logs_bloom: receipt_with_bloom.logs_bloom,
94 })
95 }
96 }
97 }
98}
99
100#[derive(Clone, Debug)]
102pub struct ExecutedTransactions {
103 pub block: BlockInfo,
105 pub included: Vec<Arc<PoolTransaction>>,
107 pub invalid: Vec<Arc<PoolTransaction>>,
110}
111
112pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> {
114 pub db: &'a mut Db,
116 pub validator: &'a V,
118 pub pending: std::vec::IntoIter<Arc<PoolTransaction>>,
120 pub block_env: BlockEnv,
121 pub cfg_env: CfgEnv,
123 pub parent_hash: B256,
124 pub gas_used: u64,
126 pub blob_gas_used: u64,
128 pub enable_steps_tracing: bool,
129 pub networks: NetworkConfigs,
130 pub print_logs: bool,
131 pub print_traces: bool,
132 pub call_trace_decoder: Arc<CallTraceDecoder>,
134 pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
136 pub blob_params: BlobParams,
137 pub cheats: CheatsManager,
138}
139
140impl<DB: Db + ?Sized, V: TransactionValidator> TransactionExecutor<'_, DB, V> {
141 pub fn execute(mut self) -> ExecutedTransactions {
143 let mut transactions = Vec::new();
144 let mut transaction_infos = Vec::new();
145 let mut receipts = Vec::new();
146 let mut bloom = Bloom::default();
147 let mut cumulative_gas_used = 0u64;
148 let mut invalid = Vec::new();
149 let mut included = Vec::new();
150 let gas_limit = self.block_env.gas_limit;
151 let parent_hash = self.parent_hash;
152 let block_number = self.block_env.number;
153 let difficulty = self.block_env.difficulty;
154 let mix_hash = self.block_env.prevrandao;
155 let beneficiary = self.block_env.beneficiary;
156 let timestamp = self.block_env.timestamp;
157 let base_fee = if self.cfg_env.spec.is_enabled_in(SpecId::LONDON) {
158 Some(self.block_env.basefee)
159 } else {
160 None
161 };
162
163 let is_shanghai = self.cfg_env.spec >= SpecId::SHANGHAI;
164 let is_cancun = self.cfg_env.spec >= SpecId::CANCUN;
165 let is_prague = self.cfg_env.spec >= SpecId::PRAGUE;
166 let excess_blob_gas = if is_cancun { self.block_env.blob_excess_gas() } else { None };
167 let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None };
168
169 for tx in self.into_iter() {
170 let tx = match tx {
171 TransactionExecutionOutcome::Executed(tx) => {
172 included.push(tx.transaction.clone());
173 tx
174 }
175 TransactionExecutionOutcome::BlockGasExhausted(tx) => {
176 trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction");
177 continue;
178 }
179 TransactionExecutionOutcome::BlobGasExhausted(tx) => {
180 trace!(target: "backend", blob_gas = %tx.pending_transaction.transaction.blob_gas().unwrap_or_default(), ?tx, "block blob gas limit exhausting, skipping transaction");
181 continue;
182 }
183 TransactionExecutionOutcome::TransactionGasExhausted(tx) => {
184 trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "transaction gas limit exhausting, skipping transaction");
185 continue;
186 }
187 TransactionExecutionOutcome::Invalid(tx, _) => {
188 trace!(target: "backend", ?tx, "skipping invalid transaction");
189 invalid.push(tx);
190 continue;
191 }
192 TransactionExecutionOutcome::DatabaseError(_, err) => {
193 trace!(target: "backend", ?err, "Failed to execute transaction due to database error");
196 continue;
197 }
198 };
199 if is_cancun {
200 let tx_blob_gas = tx
201 .transaction
202 .pending_transaction
203 .transaction
204 .transaction
205 .blob_gas()
206 .unwrap_or(0);
207 cumulative_blob_gas_used =
208 Some(cumulative_blob_gas_used.unwrap_or(0u64).saturating_add(tx_blob_gas));
209 }
210 let receipt = tx.create_receipt(&mut cumulative_gas_used);
211
212 let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx;
213 build_logs_bloom(logs.clone(), &mut bloom);
214
215 let contract_address = out.as_ref().and_then(|out| {
216 if let Output::Create(_, contract_address) = out {
217 trace!(target: "backend", "New contract deployed: at {:?}", contract_address);
218 *contract_address
219 } else {
220 None
221 }
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 partial_header = PartialHeader {
246 parent_hash,
247 beneficiary,
248 state_root: self.db.maybe_state_root().unwrap_or_default(),
249 receipts_root,
250 logs_bloom: bloom,
251 difficulty,
252 number: block_number.saturating_to(),
253 gas_limit,
254 gas_used: cumulative_gas_used,
255 timestamp: timestamp.saturating_to(),
256 extra_data: Default::default(),
257 mix_hash: mix_hash.unwrap_or_default(),
258 nonce: Default::default(),
259 base_fee,
260 parent_beacon_block_root: is_cancun.then_some(Default::default()),
261 blob_gas_used: cumulative_blob_gas_used,
262 excess_blob_gas,
263 withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
264 requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
265 };
266
267 let block = Block::new(partial_header, transactions.clone());
268 let block = BlockInfo { block, transactions: transaction_infos, receipts };
269 ExecutedTransactions { block, included, invalid }
270 }
271
272 fn env_for(&self, tx: &PendingTransaction) -> Env {
273 let mut tx_env = tx.to_revm_tx_env();
274
275 if self.networks.is_optimism() {
276 tx_env.enveloped_tx = Some(alloy_rlp::encode(&tx.transaction.transaction).into());
277 }
278
279 Env::new(self.cfg_env.clone(), self.block_env.clone(), tx_env, self.networks)
280 }
281}
282
283#[derive(Debug)]
285pub enum TransactionExecutionOutcome {
286 Executed(ExecutedTransaction),
288 Invalid(Arc<PoolTransaction>, InvalidTransactionError),
290 BlockGasExhausted(Arc<PoolTransaction>),
292 BlobGasExhausted(Arc<PoolTransaction>),
294 TransactionGasExhausted(Arc<PoolTransaction>),
296 DatabaseError(Arc<PoolTransaction>, DatabaseError),
298}
299
300impl<DB: Db + ?Sized, V: TransactionValidator> Iterator for &mut TransactionExecutor<'_, DB, V> {
301 type Item = TransactionExecutionOutcome;
302
303 fn next(&mut self) -> Option<Self::Item> {
304 let transaction = self.pending.next()?;
305 let sender = *transaction.pending_transaction.sender();
306 let account = match self.db.basic(sender).map(|acc| acc.unwrap_or_default()) {
307 Ok(account) => account,
308 Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)),
309 };
310 let env = self.env_for(&transaction.pending_transaction);
311
312 let max_block_gas = self.gas_used.saturating_add(env.tx.base.gas_limit);
314 if !env.evm_env.cfg_env.disable_block_gas_limit
315 && max_block_gas > env.evm_env.block_env.gas_limit
316 {
317 return Some(TransactionExecutionOutcome::BlockGasExhausted(transaction));
318 }
319
320 if env.evm_env.cfg_env.tx_gas_limit_cap.is_none()
322 && transaction.pending_transaction.transaction.gas_limit()
323 > env.evm_env.cfg_env().tx_gas_limit_cap()
324 {
325 return Some(TransactionExecutionOutcome::TransactionGasExhausted(transaction));
326 }
327
328 let max_blob_gas = self.blob_gas_used.saturating_add(
330 transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0),
331 );
332 if max_blob_gas > self.blob_params.max_blob_gas_per_block() {
333 return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction));
334 }
335
336 if let Err(err) = self.validator.validate_pool_transaction_for(
338 &transaction.pending_transaction,
339 &account,
340 &env,
341 ) {
342 warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", transaction.hash(), err);
343 return Some(TransactionExecutionOutcome::Invalid(transaction, err));
344 }
345
346 let nonce = account.nonce;
347
348 let mut inspector = AnvilInspector::default().with_tracing();
349 if self.enable_steps_tracing {
350 inspector = inspector.with_steps_tracing();
351 }
352 if self.print_logs {
353 inspector = inspector.with_log_collector();
354 }
355 if self.print_traces {
356 inspector = inspector.with_trace_printer();
357 }
358
359 let exec_result = {
360 let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector);
361 self.networks.inject_precompiles(evm.precompiles_mut());
362
363 if let Some(factory) = &self.precompile_factory {
364 inject_custom_precompiles(&mut evm, factory.precompiles());
365 }
366
367 let cheats = Arc::new(self.cheats.clone());
368 if cheats.has_recover_overrides() {
369 let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
370 evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| {
371 Some(DynPrecompile::new_stateful(
372 cheat_ecrecover.precompile_id().clone(),
373 move |input| cheat_ecrecover.call(input),
374 ))
375 });
376 }
377
378 trace!(target: "backend", "[{:?}] executing", transaction.hash());
379 match evm.transact_commit(env.tx) {
381 Ok(exec_result) => exec_result,
382 Err(err) => {
383 warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err);
384 match err {
385 EVMError::Database(err) => {
386 return Some(TransactionExecutionOutcome::DatabaseError(
387 transaction,
388 err,
389 ));
390 }
391 EVMError::Transaction(err) => {
392 return Some(TransactionExecutionOutcome::Invalid(
393 transaction,
394 err.into(),
395 ));
396 }
397 e => panic!("failed to execute transaction: {e}"),
400 }
401 }
402 }
403 };
404
405 if self.print_traces {
406 inspector.print_traces(self.call_trace_decoder.clone());
407 }
408 inspector.print_logs();
409
410 let (exit_reason, gas_used, out, logs) = match exec_result {
411 ExecutionResult::Success { reason, gas_used, logs, output, .. } => {
412 (reason.into(), gas_used, Some(output), Some(logs))
413 }
414 ExecutionResult::Revert { gas_used, output } => {
415 (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None)
416 }
417 ExecutionResult::Halt { reason, gas_used } => {
418 (op_haltreason_to_instruction_result(reason), gas_used, None, None)
419 }
420 };
421
422 if exit_reason == InstructionResult::OutOfGas {
423 warn!(target: "backend", "[{:?}] executed with out of gas", transaction.hash())
425 }
426
427 trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out);
428
429 self.gas_used = self.gas_used.saturating_add(gas_used);
431
432 if let Some(blob_gas) = transaction.pending_transaction.transaction.transaction.blob_gas() {
434 self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas);
435 }
436
437 trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used);
438
439 let tx = ExecutedTransaction {
440 transaction,
441 exit_reason,
442 out,
443 gas_used,
444 logs: logs.unwrap_or_default(),
445 traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(),
446 nonce,
447 };
448
449 Some(TransactionExecutionOutcome::Executed(tx))
450 }
451}
452
453fn build_logs_bloom(logs: Vec<Log>, bloom: &mut Bloom) {
455 for log in logs {
456 bloom.accrue(BloomInput::Raw(&log.address[..]));
457 for topic in log.topics() {
458 bloom.accrue(BloomInput::Raw(&topic[..]));
459 }
460 }
461}
462
463pub fn new_evm_with_inspector<DB, I>(
465 db: DB,
466 env: &Env,
467 inspector: I,
468) -> EitherEvm<DB, I, PrecompilesMap>
469where
470 DB: Database<Error = DatabaseError> + Debug,
471 I: Inspector<EthEvmContext<DB>> + Inspector<OpContext<DB>>,
472{
473 if env.networks.is_optimism() {
474 let op_cfg = env.evm_env.cfg_env.clone().with_spec(op_revm::OpSpecId::ISTHMUS);
475 let op_context = OpContext {
476 journaled_state: {
477 let mut journal = Journal::new(db);
478 journal.set_spec_id(env.evm_env.cfg_env.spec);
480 journal
481 },
482 block: env.evm_env.block_env.clone(),
483 cfg: op_cfg.clone(),
484 tx: env.tx.clone(),
485 chain: L1BlockInfo::default(),
486 local: LocalContext::default(),
487 error: Ok(()),
488 };
489
490 let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles();
491 let op_evm = op_revm::OpEvm(RevmEvm::new_with_inspector(
492 op_context,
493 inspector,
494 EthInstructions::default(),
495 PrecompilesMap::from_static(op_precompiles),
496 ));
497
498 let op = OpEvm::new(op_evm, true);
499
500 EitherEvm::Op(op)
501 } else {
502 let spec = env.evm_env.cfg_env.spec;
503 let eth_context = EthEvmContext {
504 journaled_state: {
505 let mut journal = Journal::new(db);
506 journal.set_spec_id(spec);
507 journal
508 },
509 block: env.evm_env.block_env.clone(),
510 cfg: env.evm_env.cfg_env.clone(),
511 tx: env.tx.base.clone(),
512 chain: (),
513 local: LocalContext::default(),
514 error: Ok(()),
515 };
516
517 let eth_precompiles = EthPrecompiles {
518 precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
519 spec,
520 }
521 .precompiles;
522 let eth_evm = RevmEvm::new_with_inspector(
523 eth_context,
524 inspector,
525 EthInstructions::default(),
526 PrecompilesMap::from_static(eth_precompiles),
527 );
528
529 let eth = EthEvm::new(eth_evm, true);
530
531 EitherEvm::Eth(eth)
532 }
533}
534
535pub fn new_evm_with_inspector_ref<'db, DB, I>(
537 db: &'db DB,
538 env: &Env,
539 inspector: &'db mut I,
540) -> EitherEvm<WrapDatabaseRef<&'db DB>, &'db mut I, PrecompilesMap>
541where
542 DB: DatabaseRef<Error = DatabaseError> + Debug + 'db + ?Sized,
543 I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>
544 + Inspector<OpContext<WrapDatabaseRef<&'db DB>>>,
545 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
546{
547 new_evm_with_inspector(WrapDatabaseRef(db), env, inspector)
548}