1use crate::{
2 eth::{
3 backend::cheats::CheatsManager,
4 error::InvalidTransactionError,
5 pool::transactions::{PoolTransaction, TxMarker},
6 },
7 mem::inspector::{AnvilInspector, InspectorTxConfig},
8};
9use alloy_consensus::{
10 Eip658Value, Receipt, ReceiptWithBloom, Transaction, TransactionEnvelope, TxReceipt,
11 transaction::{Either, Recovered},
12};
13use alloy_eips::{
14 Encodable2718, eip2935, eip4788,
15 eip6110::DEPOSIT_REQUEST_TYPE,
16 eip7685::Requests,
17 eip7702::{RecoveredAuthority, RecoveredAuthorization},
18};
19use alloy_evm::{
20 Evm, FromRecoveredTx, FromTxWithEncoded, RecoveredTx,
21 block::{
22 BalIndexedDatabase, BlockExecutionError, BlockExecutionResult, BlockExecutor,
23 BlockValidationError, ExecutableTx, GasOutput, StateDB, SystemCaller, TxResult,
24 },
25 eth::{
26 EthTxResult,
27 eip6110::parse_deposits_from_receipts,
28 receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx},
29 spec::EthExecutorSpec,
30 },
31 precompiles::PrecompilesMap,
32};
33use alloy_hardforks::{EthereumHardfork, EthereumHardforks, ForkCondition};
34use alloy_primitives::{Address, B256, Bytes, Log, U256, map::HashSet};
35use anvil_core::eth::transaction::{
36 MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo,
37};
38use foundry_evm::core::{env::FoundryTransaction, evm::IntoInstructionResult};
39use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope, FoundryTxType};
40use revm::{
41 Database, DatabaseCommit,
42 context::Block as RevmBlock,
43 context_interface::result::{ExecutionResult, Output, ResultAndState},
44 interpreter::InstructionResult,
45 primitives::hardfork::SpecId,
46 state::{AccountInfo, EvmState},
47};
48use std::{fmt, fmt::Debug, mem::take, sync::Arc};
49use tempo_precompiles::{NONCE_PRECOMPILE_ADDRESS, nonce::NonceManager};
50
51#[cfg(feature = "base")]
52use base_common_consensus::Eip8130Receipt;
53#[cfg(feature = "base")]
54use base_common_evm::Eip8130PhaseStatuses;
55
56#[cfg(any(feature = "base", feature = "optimism"))]
57use foundry_evm::hardfork::FoundryHardfork;
58
59#[cfg(any(feature = "base", feature = "optimism"))]
60pub(crate) mod optimism;
61
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
64pub(crate) enum BlockExecutionKind {
65 #[default]
67 Complete,
68 TransactionPrefix,
70}
71
72#[derive(Clone, Copy, Debug)]
74pub(crate) struct EthereumBlockTransitions {
75 pub(crate) hardfork: EthereumHardfork,
76 pub(crate) deposit_contract_address: Address,
77 pub(crate) parent_beacon_block_root: Option<B256>,
78 pub(crate) execution_kind: BlockExecutionKind,
79}
80
81#[derive(Clone, Copy, Debug)]
83struct ActiveEthereumSpec {
84 hardfork: EthereumHardfork,
85 deposit_contract_address: Address,
86}
87
88impl EthereumHardforks for ActiveEthereumSpec {
89 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
90 if fork <= self.hardfork { ForkCondition::ZERO_TIMESTAMP } else { ForkCondition::Never }
91 }
92}
93
94impl EthExecutorSpec for ActiveEthereumSpec {
95 fn deposit_contract_address(&self) -> Option<Address> {
96 Some(self.deposit_contract_address)
97 }
98}
99
100pub(crate) fn apply_ethereum_pre_execution_changes<E>(
102 evm: &mut E,
103 parent_hash: B256,
104 transitions: EthereumBlockTransitions,
105) -> Result<(), BlockExecutionError>
106where
107 E: Evm<DB: DatabaseCommit>,
108{
109 let mut caller = SystemCaller::new(ActiveEthereumSpec {
110 hardfork: transitions.hardfork,
111 deposit_contract_address: transitions.deposit_contract_address,
112 });
113 caller.apply_blockhashes_contract_call(parent_hash, evm)?;
114 caller.apply_beacon_root_contract_call(transitions.parent_beacon_block_root, evm)
115}
116
117pub(crate) fn apply_ethereum_post_execution_changes<E>(
119 evm: &mut E,
120 transitions: EthereumBlockTransitions,
121 receipts: &[FoundryReceiptEnvelope],
122) -> Result<Requests, BlockExecutionError>
123where
124 E: Evm<DB: DatabaseCommit>,
125{
126 if transitions.hardfork < EthereumHardfork::Prague {
127 return Ok(Requests::default());
128 }
129
130 let spec = ActiveEthereumSpec {
131 hardfork: transitions.hardfork,
132 deposit_contract_address: transitions.deposit_contract_address,
133 };
134 let mut requests = Requests::default();
135 append_deposit_requests(spec, receipts, &mut requests)?;
136 SystemCaller::new(spec).append_post_execution_changes(evm, &mut requests)?;
137 Ok(requests)
138}
139
140fn append_deposit_requests(
141 spec: ActiveEthereumSpec,
142 receipts: &[FoundryReceiptEnvelope],
143 requests: &mut Requests,
144) -> Result<(), BlockExecutionError> {
145 let deposits = parse_deposits_from_receipts(spec, receipts)?;
146 if !deposits.is_empty() {
147 requests.push_request_with_type(DEPOSIT_REQUEST_TYPE, deposits);
148 }
149 Ok(())
150}
151
152#[derive(Debug, Default, Clone, Copy)]
154#[non_exhaustive]
155pub struct FoundryReceiptBuilder;
156
157impl FoundryReceiptBuilder {
158 #[cfg_attr(not(feature = "base"), allow(clippy::missing_const_for_fn))]
159 fn wrap_receipt(
160 tx_type: FoundryTxType,
161 receipt: ReceiptWithBloom<Receipt>,
162 ) -> FoundryReceiptEnvelope {
163 match tx_type {
164 FoundryTxType::Legacy => FoundryReceiptEnvelope::Legacy(receipt),
165 FoundryTxType::Eip2930 => FoundryReceiptEnvelope::Eip2930(receipt),
166 FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt),
167 FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt),
168 FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt),
169 #[cfg(any(feature = "base", feature = "optimism"))]
170 FoundryTxType::Deposit => {
171 panic!("deposit receipts require fork-specific metadata")
172 }
173 #[cfg(feature = "optimism")]
174 FoundryTxType::PostExec => FoundryReceiptEnvelope::PostExec(receipt),
175 #[cfg(feature = "base")]
176 FoundryTxType::Eip8130 => FoundryReceiptEnvelope::Eip8130(ReceiptWithBloom {
177 receipt: Eip8130Receipt::new(receipt.receipt, Eip8130PhaseStatuses::take()),
178 logs_bloom: receipt.logs_bloom,
179 }),
180 FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt),
181 FoundryTxType::Celo => {
182 FoundryReceiptEnvelope::Unknown(alloy_network::AnyReceiptEnvelope {
183 inner: receipt,
184 r#type: foundry_primitives::CIP64_TX_TYPE,
185 })
186 }
187 }
188 }
189
190 pub(crate) fn build_simulated_receipt(
192 tx_type: FoundryTxType,
193 result: &ExecutionResult,
194 logs: Vec<Log>,
195 cumulative_gas_used: u64,
196 ) -> FoundryReceiptEnvelope {
197 let receipt =
198 Receipt { status: Eip658Value::Eip658(result.is_success()), cumulative_gas_used, logs }
199 .with_bloom();
200 Self::wrap_receipt(tx_type, receipt)
201 }
202}
203
204impl ReceiptBuilder for FoundryReceiptBuilder {
205 type Transaction = FoundryTxEnvelope;
206 type Receipt = FoundryReceiptEnvelope;
207
208 fn build_receipt<E: Evm>(
209 &self,
210 ctx: ReceiptBuilderCtx<'_, FoundryTxType, E>,
211 ) -> FoundryReceiptEnvelope {
212 let receipt = Receipt {
213 status: Eip658Value::Eip658(ctx.result.is_success()),
214 cumulative_gas_used: ctx.cumulative_gas_used,
215 logs: ctx.result.into_logs(),
216 }
217 .with_bloom();
218 Self::wrap_receipt(ctx.tx_type, receipt)
219 }
220}
221
222#[derive(Debug)]
226pub struct AnvilTxResult<H> {
227 pub inner: EthTxResult<H, FoundryTxType>,
228 #[cfg(any(feature = "base", feature = "optimism"))]
230 pub depositor_nonce: Option<u64>,
231}
232
233impl<H: Send + 'static> TxResult for AnvilTxResult<H> {
234 type HaltReason = H;
235
236 fn result(&self) -> &ResultAndState<Self::HaltReason> {
237 self.inner.result()
238 }
239
240 fn into_result(self) -> ResultAndState<Self::HaltReason> {
241 self.inner.into_result()
242 }
243}
244
245pub struct AnvilBlockExecutor<E> {
251 evm: E,
253 parent_hash: B256,
255 spec_id: SpecId,
257 ethereum_transitions: Option<EthereumBlockTransitions>,
259 receipt_builder: FoundryReceiptBuilder,
261 receipts: Vec<FoundryReceiptEnvelope>,
263 gas_used: u64,
265 blob_gas_used: u64,
267 max_blob_gas_per_block: u64,
269 #[cfg(feature = "optimism")]
271 optimism_jovian: bool,
272 #[cfg(any(feature = "base", feature = "optimism"))]
274 deposit_hardfork: Option<FoundryHardfork>,
275 state_changes: Option<Vec<EvmState>>,
277}
278
279impl<E: fmt::Debug> fmt::Debug for AnvilBlockExecutor<E> {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 let mut debug = f.debug_struct("AnvilBlockExecutor");
282 debug
283 .field("evm", &self.evm)
284 .field("parent_hash", &self.parent_hash)
285 .field("spec_id", &self.spec_id)
286 .field("ethereum_transitions", &self.ethereum_transitions)
287 .field("gas_used", &self.gas_used)
288 .field("blob_gas_used", &self.blob_gas_used)
289 .field("max_blob_gas_per_block", &self.max_blob_gas_per_block);
290 #[cfg(feature = "optimism")]
291 debug.field("optimism_jovian", &self.optimism_jovian);
292 debug.field("receipts", &self.receipts.len()).finish_non_exhaustive()
293 }
294}
295
296impl<E> AnvilBlockExecutor<E> {
297 pub(crate) const fn new(
299 evm: E,
300 parent_hash: B256,
301 spec_id: SpecId,
302 ethereum_transitions: Option<EthereumBlockTransitions>,
303 ) -> Self {
304 Self {
305 evm,
306 parent_hash,
307 spec_id,
308 ethereum_transitions,
309 receipt_builder: FoundryReceiptBuilder,
310 receipts: Vec::new(),
311 gas_used: 0,
312 blob_gas_used: 0,
313 max_blob_gas_per_block: u64::MAX,
314 #[cfg(feature = "optimism")]
315 optimism_jovian: false,
316 #[cfg(any(feature = "base", feature = "optimism"))]
317 deposit_hardfork: None,
318 state_changes: None,
319 }
320 }
321
322 pub(crate) fn with_state_changes(mut self) -> Self {
324 self.state_changes = Some(Vec::new());
325 self
326 }
327
328 pub(crate) const fn with_max_blob_gas_per_block(mut self, limit: u64) -> Self {
330 self.max_blob_gas_per_block = limit;
331 self
332 }
333
334 pub(crate) fn take_state_changes(&mut self) -> Vec<EvmState> {
336 self.state_changes.take().unwrap_or_default()
337 }
338}
339
340impl<E> AnvilBlockExecutor<E>
341where
342 E: Evm<
343 DB: StateDB + BalIndexedDatabase,
344 Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
345 >,
346{
347 pub(crate) fn execute_transaction_without_commit_with<T, F>(
350 &mut self,
351 tx: T,
352 transact: F,
353 ) -> Result<AnvilTxResult<E::HaltReason>, BlockExecutionError>
354 where
355 T: ExecutableTx<Self>,
356 F: FnOnce(
357 &mut E,
358 E::Tx,
359 B256,
360 ) -> Result<ResultAndState<E::HaltReason>, BlockExecutionError>,
361 {
362 let (tx_env, tx) = tx.into_parts();
363
364 let block_available_gas = self.evm.block().gas_limit() - self.gas_used;
365 if tx.tx().gas_limit() > block_available_gas {
366 return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
367 transaction_gas_limit: tx.tx().gas_limit(),
368 block_available_gas,
369 }
370 .into());
371 }
372
373 #[cfg(any(feature = "base", feature = "optimism"))]
376 let depositor_nonce = if tx.tx().tx_type().is_deposit() {
377 let account = self
378 .evm
379 .db_mut()
380 .basic(*tx.signer())
381 .map_err(BlockExecutionError::other)?
382 .unwrap_or_default();
383 Some(account.nonce)
384 } else {
385 None
386 };
387 let transaction_hash = tx.tx().trie_hash();
388 #[cfg(feature = "optimism")]
389 let blob_gas_used =
390 optimism::blob_gas_used(self.evm.db_mut(), tx.tx(), self.optimism_jovian)?;
391 #[cfg(not(feature = "optimism"))]
392 let blob_gas_used = tx.tx().blob_gas_used().unwrap_or_default();
393 #[cfg(feature = "optimism")]
394 let blob_gas_limit = block_blob_gas_limit(
395 self.optimism_jovian,
396 self.evm.block().gas_limit(),
397 self.max_blob_gas_per_block,
398 );
399 #[cfg(not(feature = "optimism"))]
400 let blob_gas_limit = self.max_blob_gas_per_block;
401 if self.blob_gas_used.saturating_add(blob_gas_used) > blob_gas_limit {
402 return Err(BlockExecutionError::msg("block blob gas limit exceeded"));
403 }
404 let result = transact(&mut self.evm, tx_env, transaction_hash)?;
405
406 Ok(AnvilTxResult {
407 inner: EthTxResult { result, blob_gas_used, tx_type: tx.tx().tx_type() },
408 #[cfg(any(feature = "base", feature = "optimism"))]
409 depositor_nonce,
410 })
411 }
412}
413
414impl<E> BlockExecutor for AnvilBlockExecutor<E>
415where
416 E: Evm<
417 DB: StateDB + BalIndexedDatabase,
418 Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
419 >,
420{
421 type Transaction = FoundryTxEnvelope;
422 type Receipt = FoundryReceiptEnvelope;
423 type Evm = E;
424 type Result = AnvilTxResult<E::HaltReason>;
425
426 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
427 if let Some(transitions) = self.ethereum_transitions {
428 if let Some(state_changes) = &mut self.state_changes {
431 if transitions.hardfork >= EthereumHardfork::Prague {
432 let result = self
433 .evm
434 .transact_system_call(
435 eip4788::SYSTEM_ADDRESS,
436 eip2935::HISTORY_STORAGE_ADDRESS,
437 Bytes::from(self.parent_hash),
438 )
439 .map_err(BlockExecutionError::other)?;
440 state_changes.push(result.state.clone());
441 self.evm.db_mut().commit(result.state);
442 }
443 if transitions.hardfork >= EthereumHardfork::Cancun {
444 let parent_beacon_block_root = transitions
445 .parent_beacon_block_root
446 .ok_or(BlockValidationError::MissingParentBeaconBlockRoot)?;
447 let result = self
448 .evm
449 .transact_system_call(
450 eip4788::SYSTEM_ADDRESS,
451 eip4788::BEACON_ROOTS_ADDRESS,
452 Bytes::from(parent_beacon_block_root),
453 )
454 .map_err(BlockExecutionError::other)?;
455 state_changes.push(result.state.clone());
456 self.evm.db_mut().commit(result.state);
457 }
458 return Ok(());
459 }
460 apply_ethereum_pre_execution_changes(&mut self.evm, self.parent_hash, transitions)?;
461 }
462 Ok(())
463 }
464
465 fn execute_transaction_without_commit(
466 &mut self,
467 tx: impl ExecutableTx<Self>,
468 ) -> Result<Self::Result, BlockExecutionError> {
469 self.execute_transaction_without_commit_with(tx, |evm, tx_env, transaction_hash| {
470 evm.transact(tx_env).map_err(|err| BlockExecutionError::evm(err, transaction_hash))
471 })
472 }
473
474 fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
475 let AnvilTxResult {
476 inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type },
477 #[cfg(any(feature = "base", feature = "optimism"))]
478 depositor_nonce,
479 } = output;
480
481 let gas_used = result.tx_gas_used();
482 self.gas_used += gas_used;
483
484 if self.spec_id >= SpecId::CANCUN {
485 self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used);
486 }
487
488 #[cfg(any(feature = "base", feature = "optimism"))]
489 let receipt = if let Some(depositor_nonce) = depositor_nonce {
490 optimism::build_mined_deposit_receipt(
491 result,
492 self.deposit_hardfork,
493 depositor_nonce,
494 self.gas_used,
495 )
496 } else {
497 self.receipt_builder.build_receipt(ReceiptBuilderCtx {
498 tx_type,
499 evm: &self.evm,
500 result,
501 state: &state,
502 cumulative_gas_used: self.gas_used,
503 })
504 };
505 #[cfg(not(any(feature = "base", feature = "optimism")))]
506 let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx {
507 tx_type,
508 evm: &self.evm,
509 result,
510 state: &state,
511 cumulative_gas_used: self.gas_used,
512 });
513
514 if let Some(state_changes) = &mut self.state_changes {
515 state_changes.push(state.clone());
516 }
517 self.receipts.push(receipt);
518 self.evm.db_mut().set_bal_index(self.receipts.len() as u64);
521 self.evm.db_mut().commit(state);
522
523 GasOutput::new(gas_used)
524 }
525
526 fn finish(
527 mut self,
528 ) -> Result<(Self::Evm, BlockExecutionResult<FoundryReceiptEnvelope>), BlockExecutionError>
529 {
530 self.evm.db_mut().set_bal_index(self.receipts.len() as u64 + 1);
531 let requests = match self.ethereum_transitions {
532 Some(transitions) if transitions.execution_kind == BlockExecutionKind::Complete => {
533 apply_ethereum_post_execution_changes(&mut self.evm, transitions, &self.receipts)?
534 }
535 _ => Requests::default(),
536 };
537 Ok((
538 self.evm,
539 BlockExecutionResult {
540 receipts: self.receipts,
541 requests,
542 gas_used: self.gas_used,
543 blob_gas_used: self.blob_gas_used,
544 },
545 ))
546 }
547
548 fn evm_mut(&mut self) -> &mut Self::Evm {
549 &mut self.evm
550 }
551
552 fn evm(&self) -> &Self::Evm {
553 &self.evm
554 }
555
556 fn receipts(&self) -> &[FoundryReceiptEnvelope] {
557 &self.receipts
558 }
559}
560
561pub struct ExecutedPoolTransactions<T> {
563 pub included: Vec<Arc<PoolTransaction<T>>>,
565 pub stale: Vec<Arc<PoolTransaction<T>>>,
567 pub invalid: Vec<Arc<PoolTransaction<T>>>,
569 pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
572 pub tx_info: Vec<TransactionInfo>,
574 pub txs: Vec<MaybeImpersonatedTransaction<T>>,
576}
577
578pub struct PoolTxGasConfig {
584 pub disable_block_gas_limit: bool,
585 pub enforced_tx_gas_limit_cap: Option<u64>,
587 pub max_blob_gas_per_block: u64,
588 pub is_cancun: bool,
589}
590
591pub(crate) fn execute_pool_transaction<B>(
593 executor: &mut B,
594 tx_env: <B::Evm as Evm>::Tx,
595 recovered: Recovered<B::Transaction>,
596 _is_replay: bool,
597) -> Result<B::Result, BlockExecutionError>
598where
599 B: BlockExecutor,
600{
601 executor.execute_transaction_without_commit((tx_env, recovered))
602}
603
604#[allow(clippy::type_complexity)]
609pub fn execute_pool_transactions<B, ExecuteTransaction>(
610 executor: &mut B,
611 pool_transactions: &[Arc<PoolTransaction<B::Transaction>>],
612 gas_config: &PoolTxGasConfig,
613 inspector_config: &InspectorTxConfig,
614 cheats: &CheatsManager,
615 validator: &dyn Fn(
616 &PoolTransaction<B::Transaction>,
617 &AccountInfo,
618 ) -> Result<(), InvalidTransactionError>,
619 execute_transaction: &mut ExecuteTransaction,
620) -> ExecutedPoolTransactions<B::Transaction>
621where
622 B: BlockExecutor<
623 Transaction = FoundryTxEnvelope,
624 Evm: Evm<
625 DB: Database + Debug,
626 Inspector = AnvilInspector,
627 Precompiles = PrecompilesMap,
628 >,
629 >,
630 B::Receipt: TxReceipt,
631 <B::Result as TxResult>::HaltReason: Clone + IntoInstructionResult,
632 <B::Evm as Evm>::Tx: FromTxWithEncoded<B::Transaction> + FoundryTransaction,
633 ExecuteTransaction: FnMut(
634 &mut B,
635 <B::Evm as Evm>::Tx,
636 Recovered<B::Transaction>,
637 bool,
638 ) -> Result<B::Result, BlockExecutionError>,
639{
640 let gas_limit = executor.evm().block().gas_limit();
641
642 let mut included = Vec::new();
643 let mut stale = Vec::new();
644 let mut invalid = Vec::new();
645 let mut not_yet_valid = Vec::new();
646 let mut tx_info: Vec<TransactionInfo> = Vec::new();
647 let mut transactions = Vec::new();
648 let mut blob_gas_used = 0u64;
649 let mut unavailable_markers = HashSet::<TxMarker>::default();
650
651 for pool_tx in pool_transactions {
652 let pending = &pool_tx.pending_transaction;
653 let sender = *pending.sender();
654 if pool_tx.requires.iter().any(|marker| unavailable_markers.contains(marker)) {
655 trace!(target: "backend", "[{:?}] dependency was not included, skipping transaction", pool_tx.hash());
656 unavailable_markers.extend(pool_tx.provides.iter().cloned());
657 continue;
658 }
659
660 let account = match executor.evm_mut().db_mut().basic(sender).map(|a| a.unwrap_or_default())
661 {
662 Ok(acc) => acc,
663 Err(err) => {
664 trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash());
665 unavailable_markers.extend(pool_tx.provides.iter().cloned());
666 continue;
667 }
668 };
669
670 let transaction = pending.transaction.as_ref();
671 let state_nonce = match transaction {
672 FoundryTxEnvelope::Tempo(tx) if tx.tx().nonce_key == U256::MAX => None,
673 FoundryTxEnvelope::Tempo(tx) if !tx.tx().nonce_key.is_zero() => {
674 let slot = NonceManager::new().nonces[sender][tx.tx().nonce_key].slot();
675 match executor.evm_mut().db_mut().storage(NONCE_PRECOMPILE_ADDRESS, slot) {
676 Ok(nonce) => Some(nonce.saturating_to::<u64>()),
677 Err(err) => {
678 trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash());
679 unavailable_markers.extend(pool_tx.provides.iter().cloned());
680 continue;
681 }
682 }
683 }
684 #[cfg(feature = "base")]
685 FoundryTxEnvelope::Eip8130(_) => None,
686 #[cfg(any(feature = "base", feature = "optimism"))]
687 FoundryTxEnvelope::Deposit(_) => None,
688 #[cfg(feature = "optimism")]
689 FoundryTxEnvelope::PostExec(_) => None,
690 _ => Some(account.nonce),
691 };
692 if state_nonce.is_some_and(|nonce| transaction.nonce() < nonce) {
693 warn!(target: "backend", "Skipping stale tx [{:?}]", pool_tx.hash());
694 stale.push(pool_tx.clone());
695 continue;
696 }
697
698 if let FoundryTxEnvelope::Tempo(aa_tx) = transaction
699 && let Some(valid_after) = aa_tx.tx().valid_after
700 && U256::from(valid_after.get()) > executor.evm().block().timestamp()
701 {
702 trace!(target: "backend", "[{:?}] transaction not valid yet, will retry later", pool_tx.hash());
703 not_yet_valid.push(pool_tx.clone());
704 unavailable_markers.extend(pool_tx.provides.iter().cloned());
705 continue;
706 }
707
708 let tx_env =
709 build_tx_env_for_pending::<B::Transaction, <B::Evm as Evm>::Tx>(pending, cheats);
710
711 let cumulative_gas =
713 executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0);
714 let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit());
715 if !gas_config.disable_block_gas_limit && max_block_gas > gas_limit {
716 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction");
717 unavailable_markers.extend(pool_tx.provides.iter().cloned());
718 continue;
719 }
720
721 if let Some(tx_gas_limit_cap) = gas_config.enforced_tx_gas_limit_cap
723 && pending.transaction.gas_limit() > tx_gas_limit_cap
724 {
725 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction");
726 unavailable_markers.extend(pool_tx.provides.iter().cloned());
727 continue;
728 }
729
730 let declared_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0);
733 if blob_gas_used.saturating_add(declared_blob_gas) > gas_config.max_blob_gas_per_block {
734 trace!(target: "backend", blob_gas = %declared_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction");
735 unavailable_markers.extend(pool_tx.provides.iter().cloned());
736 continue;
737 }
738
739 if let Err(err) = validator(pool_tx, &account) {
741 warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err);
742 invalid.push(pool_tx.clone());
743 continue;
744 }
745
746 let nonce = account.nonce;
747
748 let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender);
749 trace!(target: "backend", "[{:?}] executing", pool_tx.hash());
750 match execute_transaction(executor, tx_env, recovered, pool_tx.is_replay) {
751 Ok(result) => {
752 let exec_result = result.result().result.clone();
753 let gas_used = result.result().result.tx_gas_used();
754
755 executor.commit_transaction(result);
756
757 let (_, inspector, precompiles) = executor.evm_mut().components_mut();
758 let traces = inspector.finish_transaction(inspector_config, precompiles);
759
760 if gas_config.is_cancun {
761 blob_gas_used = blob_gas_used.saturating_add(declared_blob_gas);
762 }
763
764 let (exit_reason, out, _logs) = match exec_result {
765 ExecutionResult::Success { reason, logs, output, .. } => {
766 (reason.into(), Some(output), logs)
767 }
768 ExecutionResult::Revert { output, .. } => {
769 (InstructionResult::Revert, Some(Output::Call(output)), Vec::new())
770 }
771 ExecutionResult::Halt { reason, .. } => {
772 (reason.into_instruction_result(), None, Vec::new())
773 }
774 };
775
776 if exit_reason == InstructionResult::OutOfGas {
777 warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash());
778 }
779
780 trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out);
781 trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used);
782
783 let contract_address = pending.transaction.to().is_none().then(|| {
784 let addr = sender.create(nonce);
785 trace!(target: "backend", "Contract creation tx: computed address {:?}", addr);
786 addr
787 });
788
789 let transaction_index = tx_info.len() as u64;
791 let info = TransactionInfo {
792 transaction_hash: pool_tx.hash(),
793 transaction_index,
794 from: sender,
795 to: pending.transaction.to(),
796 contract_address,
797 traces,
798 exit: exit_reason,
799 out: out.map(Output::into_data),
800 nonce,
801 gas_used,
802 };
803
804 included.push(pool_tx.clone());
805 tx_info.push(info);
806 transactions.push(pending.transaction.clone());
807 }
808 Err(err) => {
809 executor.evm_mut().inspector_mut().discard_transaction(inspector_config);
810 if err.as_validation().is_some() {
811 warn!(target: "backend", "Skipping invalid tx [{:?}]: {}", pool_tx.hash(), err);
812 invalid.push(pool_tx.clone());
813 } else {
814 unavailable_markers.extend(pool_tx.provides.iter().cloned());
815 trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash());
816 }
817 }
818 }
819 }
820
821 ExecutedPoolTransactions { included, stale, invalid, not_yet_valid, tx_info, txs: transactions }
822}
823
824pub fn build_tx_env_for_pending<Tx, T>(tx: &PendingTransaction<Tx>, cheats: &CheatsManager) -> T
826where
827 Tx: Transaction + Encodable2718,
828 T: FromTxWithEncoded<Tx> + FoundryTransaction,
829{
830 let encoded = tx.transaction.encoded_2718().into();
831 let mut tx_env: T =
832 FromTxWithEncoded::from_encoded_tx(tx.transaction.as_ref(), *tx.sender(), encoded);
833
834 if let Some(signed_auths) = tx.transaction.authorization_list()
835 && cheats.has_recover_overrides()
836 {
837 let auth_list = tx_env.authorization_list_mut();
838 let cheated_auths = signed_auths
839 .iter()
840 .zip(take(auth_list))
841 .map(|(signed_auth, either_auth)| {
842 either_auth.right_and_then(|recovered_auth| {
843 if recovered_auth.authority().is_none()
844 && let Ok(signature) = signed_auth.signature()
845 && let Some(override_addr) =
846 cheats.get_recover_override(&signature.as_bytes().into())
847 {
848 Either::Right(RecoveredAuthorization::new_unchecked(
849 recovered_auth.into_parts().0,
850 RecoveredAuthority::Valid(override_addr),
851 ))
852 } else {
853 Either::Right(recovered_auth)
854 }
855 })
856 })
857 .collect();
858 *tx_env.authorization_list_mut() = cheated_auths;
859 }
860
861 tx_env
862}
863
864pub(crate) const fn block_blob_gas_limit(
869 optimism_jovian: bool,
870 block_gas_limit: u64,
871 max_blob_gas_per_block: u64,
872) -> u64 {
873 if optimism_jovian { block_gas_limit } else { max_blob_gas_per_block }
874}
875
876#[cfg(test)]
877mod tests {
878 use super::*;
879 use alloy_eips::{
880 eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS, eip7002::WITHDRAWAL_REQUEST_TYPE,
881 eip7251::CONSOLIDATION_REQUEST_TYPE,
882 };
883 use alloy_sol_types::{SolEvent, sol};
884
885 sol! {
886 event DepositEvent(
887 bytes pubkey,
888 bytes withdrawal_credentials,
889 bytes amount,
890 bytes signature,
891 bytes index
892 );
893 }
894
895 #[test]
896 fn prague_requests_use_consensus_order() {
897 let event = DepositEvent {
898 pubkey: Bytes::from(vec![0x11; 48]),
899 withdrawal_credentials: Bytes::from(vec![0x22; 32]),
900 amount: Bytes::from(vec![0x33; 8]),
901 signature: Bytes::from(vec![0x44; 96]),
902 index: Bytes::from(vec![0x55; 8]),
903 };
904 let log = DepositEvent::encode_log(&Log {
905 address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
906 data: event,
907 });
908 let receipt =
909 Receipt { status: Eip658Value::Eip658(true), cumulative_gas_used: 0, logs: vec![log] }
910 .with_bloom();
911 let receipts = [FoundryReceiptEnvelope::Legacy(receipt)];
912 let mut requests = Requests::default();
913
914 append_deposit_requests(
915 ActiveEthereumSpec {
916 hardfork: EthereumHardfork::Prague,
917 deposit_contract_address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
918 },
919 &receipts,
920 &mut requests,
921 )
922 .unwrap();
923 requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, [0xaa]);
924 requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, [0xbb]);
925
926 assert_eq!(
927 requests.iter().map(|request| request[0]).collect::<Vec<_>>(),
928 [DEPOSIT_REQUEST_TYPE, WITHDRAWAL_REQUEST_TYPE, CONSOLIDATION_REQUEST_TYPE]
929 );
930 }
931
932 #[test]
933 fn deposit_requests_use_configured_contract_address() {
934 let configured_address = Address::repeat_byte(0x42);
935 let event = DepositEvent {
936 pubkey: Bytes::from(vec![0x11; 48]),
937 withdrawal_credentials: Bytes::from(vec![0x22; 32]),
938 amount: Bytes::from(vec![0x33; 8]),
939 signature: Bytes::from(vec![0x44; 96]),
940 index: Bytes::from(vec![0x55; 8]),
941 };
942 let configured_log =
943 DepositEvent::encode_log(&Log { address: configured_address, data: event.clone() });
944 let mainnet_log = DepositEvent::encode_log(&Log {
945 address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
946 data: event,
947 });
948 let receipt = Receipt {
949 status: Eip658Value::Eip658(true),
950 cumulative_gas_used: 0,
951 logs: vec![mainnet_log, configured_log],
952 }
953 .with_bloom();
954 let receipts = [FoundryReceiptEnvelope::Legacy(receipt)];
955 let mut requests = Requests::default();
956
957 append_deposit_requests(
958 ActiveEthereumSpec {
959 hardfork: EthereumHardfork::Prague,
960 deposit_contract_address: configured_address,
961 },
962 &receipts,
963 &mut requests,
964 )
965 .unwrap();
966
967 let request = requests.first().expect("configured deposit should be collected");
968 assert_eq!(request[0], DEPOSIT_REQUEST_TYPE);
969 assert_eq!(request.len(), 1 + 48 + 32 + 8 + 96 + 8);
970 }
971}