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