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