anvil/eth/backend/
replay.rs1use 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#[derive(Clone, Debug)]
31pub(crate) struct HistoricalReplayTransaction {
32 pub(crate) transaction: Recovered<FoundryTxEnvelope>,
33 pub(crate) source_index: usize,
34}
35
36pub(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
43pub(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
51pub(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
100pub(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}