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