example_block_traces/
main.rs

1//! Example that show how to replay a block and trace the execution of each transaction.
2//!
3//! The EIP3155 trace of each transaction is saved into file `traces/{tx_number}.json`.
4#![cfg_attr(not(test), warn(unused_crate_dependencies))]
5
6use alloy_consensus::Transaction;
7use alloy_eips::{BlockId, BlockNumberOrTag};
8use alloy_provider::{network::primitives::BlockTransactions, Provider, ProviderBuilder};
9use indicatif::ProgressBar;
10use revm::{
11    context::TxEnv,
12    database::{AlloyDB, CacheDB, StateBuilder},
13    database_interface::WrapDatabaseAsync,
14    inspector::{inspectors::TracerEip3155, InspectEvm},
15    primitives::{TxKind, U256},
16    Context, MainBuilder, MainContext,
17};
18use std::{
19    fs::{create_dir_all, OpenOptions},
20    io::{BufWriter, Write},
21    sync::{Arc, Mutex},
22    time::Instant,
23};
24
25struct FlushWriter {
26    writer: Arc<Mutex<BufWriter<std::fs::File>>>,
27}
28
29impl FlushWriter {
30    fn new(writer: Arc<Mutex<BufWriter<std::fs::File>>>) -> Self {
31        Self { writer }
32    }
33}
34
35impl Write for FlushWriter {
36    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
37        self.writer.lock().unwrap().write(buf)
38    }
39
40    fn flush(&mut self) -> std::io::Result<()> {
41        self.writer.lock().unwrap().flush()
42    }
43}
44
45#[tokio::main]
46async fn main() -> anyhow::Result<()> {
47    create_dir_all("traces")?;
48
49    // Set up the HTTP transport which is consumed by the RPC client.
50    let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27".parse()?;
51
52    // Create a provider
53    let client = ProviderBuilder::new().connect_http(rpc_url);
54
55    // Params
56    let chain_id: u64 = 1;
57    let block_number = 10889447;
58
59    // Fetch the transaction-rich block
60    let block = match client
61        .get_block_by_number(BlockNumberOrTag::Number(block_number))
62        .full()
63        .await
64    {
65        Ok(Some(block)) => block,
66        Ok(None) => anyhow::bail!("Block not found"),
67        Err(error) => anyhow::bail!("Error: {:?}", error),
68    };
69    println!("Fetched block number: {}", block.header.number);
70    let previous_block_number = block_number - 1;
71
72    // Use the previous block state as the db with caching
73    let prev_id: BlockId = previous_block_number.into();
74    // SAFETY: This cannot fail since this is in the top-level tokio runtime
75
76    let state_db = WrapDatabaseAsync::new(AlloyDB::new(client, prev_id)).unwrap();
77    let cache_db: CacheDB<_> = CacheDB::new(state_db);
78    let mut state = StateBuilder::new_with_database(cache_db).build();
79    let ctx = Context::mainnet()
80        .with_db(&mut state)
81        .modify_block_chained(|b| {
82            b.number = U256::from(block.header.number);
83            b.beneficiary = block.header.beneficiary;
84            b.timestamp = U256::from(block.header.timestamp);
85
86            b.difficulty = block.header.difficulty;
87            b.gas_limit = block.header.gas_limit;
88            b.basefee = block.header.base_fee_per_gas.unwrap_or_default();
89        })
90        .modify_cfg_chained(|c| {
91            c.chain_id = chain_id;
92        });
93
94    let write = OpenOptions::new()
95        .write(true)
96        .create(true)
97        .truncate(true)
98        .open("traces/0.json");
99    let inner = Arc::new(Mutex::new(BufWriter::new(
100        write.expect("Failed to open file"),
101    )));
102    let writer = FlushWriter::new(Arc::clone(&inner));
103    let mut evm = ctx.build_mainnet_with_inspector(TracerEip3155::new(Box::new(writer)));
104
105    let txs = block.transactions.len();
106    println!("Found {txs} transactions.");
107
108    let console_bar = Arc::new(ProgressBar::new(txs as u64));
109    let start = Instant::now();
110
111    // Fill in CfgEnv
112    let BlockTransactions::Full(transactions) = block.transactions else {
113        panic!("Wrong transaction type")
114    };
115
116    for tx in transactions {
117        // Construct the file writer to write the trace to
118        let tx_number = tx.transaction_index.unwrap_or_default();
119
120        let tx = TxEnv::builder()
121            .caller(tx.inner.signer())
122            .gas_limit(tx.gas_limit())
123            .gas_price(tx.gas_price().unwrap_or(tx.inner.max_fee_per_gas()))
124            .value(tx.value())
125            .data(tx.input().to_owned())
126            .gas_priority_fee(tx.max_priority_fee_per_gas())
127            .chain_id(Some(chain_id))
128            .nonce(tx.nonce())
129            .access_list(tx.access_list().cloned().unwrap_or_default())
130            .kind(match tx.to() {
131                Some(to_address) => TxKind::Call(to_address),
132                None => TxKind::Create,
133            })
134            .build()
135            .unwrap();
136
137        let file_name = format!("traces/{tx_number}.json");
138        let write = OpenOptions::new()
139            .write(true)
140            .create(true)
141            .truncate(true)
142            .open(file_name);
143        let inner = Arc::new(Mutex::new(BufWriter::new(
144            write.expect("Failed to open file"),
145        )));
146        let writer = FlushWriter::new(Arc::clone(&inner));
147
148        // Inspect and commit the transaction to the EVM
149        let res: Result<_, _> = evm.inspect_one(tx, TracerEip3155::new(Box::new(writer)));
150
151        if let Err(error) = res {
152            println!("Got error: {error:?}");
153        }
154
155        // Flush the file writer
156        inner.lock().unwrap().flush().expect("Failed to flush file");
157
158        console_bar.inc(1);
159    }
160
161    console_bar.finish_with_message("Finished all transactions.");
162
163    let elapsed = start.elapsed();
164    println!(
165        "Finished execution. Total CPU time: {:.6}s",
166        elapsed.as_secs_f64()
167    );
168
169    Ok(())
170}