example_block_traces/
main.rs

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