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