Skip to main content

anvil/eth/backend/
replay.rs

1//! Transaction-hash fork replay preparation and execution.
2
3use crate::{
4    config::ForkTransactionReplay,
5    eth::backend::executor::AnvilBlockExecutor,
6    mem::inspector::{AnvilInspector, InspectorTxConfig},
7};
8use alloy_consensus::{
9    BlockHeader, Transaction,
10    transaction::{Recovered, SignerRecoverable, TxHashRef},
11};
12use alloy_evm::{
13    Evm, FromRecoveredTx, FromTxWithEncoded, RecoveredTx,
14    block::{BlockExecutionResult, BlockExecutor, StateDB, TxResult},
15};
16use alloy_network::{BlockResponse, TransactionResponse};
17use alloy_primitives::B256;
18use anvil_core::eth::transaction::{MaybeImpersonatedTransaction, TransactionInfo};
19use eyre::{Context, Result};
20use foundry_evm::core::evm::IntoInstructionResult;
21use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope};
22use revm::{
23    Database,
24    context_interface::result::{ExecutionResult, Output},
25    interpreter::InstructionResult,
26    state::EvmState,
27};
28
29/// A source transaction prepared for direct historical execution.
30#[derive(Clone, Debug)]
31pub(crate) struct HistoricalReplayTransaction {
32    pub(crate) transaction: Recovered<FoundryTxEnvelope>,
33    pub(crate) source_index: usize,
34}
35
36/// A validated transaction prefix together with its source block execution inputs.
37pub(crate) struct PreparedForkTransactionReplay {
38    pub(crate) transactions: Vec<HistoricalReplayTransaction>,
39    pub(crate) timestamp: u64,
40    pub(crate) parent_beacon_block_root: Option<B256>,
41}
42
43/// The complete result of executing a historical prefix against an overlay.
44pub(crate) struct ExecutedHistoricalReplay {
45    pub(crate) block_result: BlockExecutionResult<FoundryReceiptEnvelope>,
46    pub(crate) transactions: Vec<MaybeImpersonatedTransaction<FoundryTxEnvelope>>,
47    pub(crate) transaction_infos: Vec<TransactionInfo>,
48    pub(crate) state_changes: Vec<EvmState>,
49}
50
51/// Converts and validates every source-prefix transaction before database execution.
52pub(crate) fn prepare_fork_transaction_replay(
53    replay: ForkTransactionReplay,
54) -> Result<PreparedForkTransactionReplay> {
55    let source_hash = replay.source_block.header().hash;
56    let source_number = replay.source_block.header().number;
57    let timestamp = replay.source_block.header().timestamp();
58    let parent_beacon_block_root = replay.source_block.header().parent_beacon_block_root();
59    let source_transactions = replay
60        .source_block
61        .transactions()
62        .as_transactions()
63        .expect("full source block validated during resolution");
64
65    let transactions = source_transactions
66        .iter()
67        .take(replay.target_index.saturating_add(1))
68        .enumerate()
69        .map(|(source_index, source_transaction)| {
70            let source_transaction_hash = source_transaction.tx_hash();
71            let transaction = FoundryTxEnvelope::try_from(source_transaction.clone())
72                .wrap_err_with(|| {
73                    format!(
74                        "failed to convert source transaction {source_transaction_hash} at index \
75                         {source_index} in block {source_hash} ({source_number})"
76                    )
77                })?;
78            eyre::ensure!(
79                transaction.tx_hash() == &source_transaction_hash,
80                "converted source transaction at index {source_index} in block {source_hash} \
81                 ({source_number}) changed hash from {source_transaction_hash} to {}",
82                transaction.tx_hash()
83            );
84            let sender = transaction.recover_signer().wrap_err_with(|| {
85                format!(
86                    "failed to recover sender for source transaction {source_transaction_hash} at \
87                     index {source_index} in block {source_hash} ({source_number})"
88                )
89            })?;
90            Ok(HistoricalReplayTransaction {
91                transaction: Recovered::new_unchecked(transaction, sender),
92                source_index,
93            })
94        })
95        .collect::<Result<_>>()?;
96
97    Ok(PreparedForkTransactionReplay { transactions, timestamp, parent_beacon_block_root })
98}
99
100/// Executes a prepared prefix strictly and captures changesets for deferred publication.
101pub(crate) fn execute_historical_replay<E>(
102    executor: &mut AnvilBlockExecutor<E>,
103    transactions: &[HistoricalReplayTransaction],
104    inspector_config: &InspectorTxConfig,
105) -> Result<(Vec<MaybeImpersonatedTransaction<FoundryTxEnvelope>>, Vec<TransactionInfo>)>
106where
107    E: Evm<
108            DB: StateDB,
109            Inspector = AnvilInspector,
110            Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
111        >,
112    E::HaltReason: Clone + IntoInstructionResult,
113{
114    let mut stored_transactions = Vec::with_capacity(transactions.len());
115    let mut transaction_infos = Vec::with_capacity(transactions.len());
116
117    for replay in transactions {
118        let transaction = replay.transaction.tx();
119        let transaction_hash = *transaction.tx_hash();
120        let sender = replay.transaction.signer();
121        let nonce = executor
122            .evm_mut()
123            .db_mut()
124            .basic(sender)
125            .wrap_err_with(|| {
126                format!(
127                    "database error preparing source transaction {transaction_hash} at index {}",
128                    replay.source_index
129                )
130            })?
131            .unwrap_or_default()
132            .nonce;
133
134        let result = executor
135            .execute_transaction_without_commit(replay.transaction.clone().into_encoded())
136            .map_err(|err| {
137                eyre::eyre!(
138                    "failed to execute source transaction {transaction_hash} at index {}: {err}",
139                    replay.source_index
140                )
141            })?;
142        let execution_result = result.result().result.clone();
143        let gas_used = execution_result.tx_gas_used();
144        executor.commit_transaction(result);
145
146        let traces = executor.evm_mut().inspector_mut().finish_transaction(inspector_config);
147        let (exit_reason, out) = match execution_result {
148            ExecutionResult::Success { reason, output, .. } => (reason.into(), Some(output)),
149            ExecutionResult::Revert { output, .. } => {
150                (InstructionResult::Revert, Some(Output::Call(output)))
151            }
152            ExecutionResult::Halt { reason, .. } => (reason.into_instruction_result(), None),
153        };
154        let contract_address = transaction.to().is_none().then(|| sender.create(nonce));
155        transaction_infos.push(TransactionInfo {
156            transaction_hash,
157            transaction_index: replay.source_index as u64,
158            from: sender,
159            to: transaction.to(),
160            contract_address,
161            traces,
162            exit: exit_reason,
163            out: out.map(Output::into_data),
164            nonce,
165            gas_used,
166        });
167        stored_transactions.push(MaybeImpersonatedTransaction::new(transaction.clone()));
168    }
169
170    Ok((stored_transactions, transaction_infos))
171}