From 5a47ae0d2bb0909cc70d1b8ae2b6fc721ab1ca7d Mon Sep 17 00:00:00 2001 From: "refcell.eth" Date: Tue, 5 Dec 2023 08:03:42 -0800 Subject: [PATCH] feat(examples): generate block traces (#895) --- .gitignore | 4 +- Cargo.lock | 1 + README.md | 9 +- crates/revm/Cargo.toml | 6 + examples/generate_block_traces.rs | 175 ++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 examples/generate_block_traces.rs diff --git a/.gitignore b/.gitignore index e23ee9b8cd..5675048759 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,10 @@ target .idea pkg/ -tests bins/revme/temp_folder bins/revme/tests ethereumjs-util.js book + +# Generated by the block traces example +traces diff --git a/Cargo.lock b/Cargo.lock index 9049c0ebf8..2c2fb89b11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2370,6 +2370,7 @@ dependencies = [ "ethers-core", "ethers-providers", "futures", + "indicatif", "revm-interpreter", "revm-precompile", "serde", diff --git a/README.md b/README.md index 8a96a0558a..d1671d5372 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,19 @@ cargo flamegraph --root --freq 4000 --min-width 0.001 --package revm-test --bin This command will produce a flamegraph image output to `flamegraph.svg`. Flamegraph also requires sudo mode to run (hence the `--root` cli arg) and will prompt you for your password if not in sudo mode already. -## Running example +## Running examples ```shell cargo run -p revm --features ethersdb --example fork_ref_transact ``` +Generate block traces and write them to json files in a new `traces/` directory. +Each file corresponds to a transaction in the block and is named as such: `.json`. + +```shell +cargo run -p revm --features std,serde,ethersdb --example generate_block_traces +``` + # Used by: * [Foundry](https://github.com/foundry-rs/foundry) is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index a0a4f157e9..85c1d6c7b1 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -37,6 +37,7 @@ futures = { version = "0.3.29", optional = true } ethers-contract = { version = "2.0.11", default-features = false } anyhow = "1.0.75" criterion = "0.5" +indicatif = "0.17" [features] default = ["std", "c-kzg", "secp256k1"] @@ -80,6 +81,11 @@ name = "fork_ref_transact" path = "../../examples/fork_ref_transact.rs" required-features = ["ethersdb"] +[[example]] +name = "generate_block_traces" +path = "../../examples/generate_block_traces.rs" +required-features = ["std", "serde", "ethersdb"] + [[bench]] name = "bench" path = "benches/bench.rs" diff --git a/examples/generate_block_traces.rs b/examples/generate_block_traces.rs new file mode 100644 index 0000000000..37579b7f2c --- /dev/null +++ b/examples/generate_block_traces.rs @@ -0,0 +1,175 @@ +// Example Adapted From: https://github.com/bluealloy/revm/issues/672 + +use ethers_core::types::BlockId; +use ethers_providers::Middleware; +use ethers_providers::{Http, Provider}; +use indicatif::ProgressBar; +use revm::db::{CacheDB, EthersDB, StateBuilder}; +use revm::inspectors::TracerEip3155; +use revm::primitives::{Address, Env, TransactTo, U256}; +use revm::EVM; +use std::fs::OpenOptions; +use std::io::BufWriter; +use std::io::Write; +use std::sync::Arc; +use std::sync::Mutex; + +macro_rules! local_fill { + ($left:expr, $right:expr, $fun:expr) => { + if let Some(right) = $right { + $left = $fun(right.0) + } + }; + ($left:expr, $right:expr) => { + if let Some(right) = $right { + $left = Address::from(right.as_fixed_bytes()) + } + }; +} + +struct FlushWriter { + writer: Arc>>, +} + +impl FlushWriter { + fn new(writer: Arc>>) -> Self { + Self { writer } + } +} + +impl Write for FlushWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.writer.lock().unwrap().write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.writer.lock().unwrap().flush() + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Create ethers client and wrap it in Arc + let client = Provider::::try_from( + "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27", + )?; + let client = Arc::new(client); + + // Params + let chain_id: u64 = 1; + let block_number = 10889447; + + // Fetch the transaction-rich block + let block = match client.get_block_with_txs(block_number).await { + Ok(Some(block)) => block, + Ok(None) => anyhow::bail!("Block not found"), + Err(error) => anyhow::bail!("Error: {:?}", error), + }; + println!("Fetched block number: {}", block.number.unwrap().0[0]); + let previous_block_number = block_number - 1; + + // Use the previous block state as the db with caching + let prev_id: BlockId = previous_block_number.into(); + // SAFETY: This cannot fail since this is in the top-level tokio runtime + let state_db = EthersDB::new(Arc::clone(&client), Some(prev_id)).expect("panic"); + let cache_db: CacheDB>> = CacheDB::new(state_db); + let mut state = StateBuilder::new_with_database(cache_db).build(); + let mut evm = EVM::new(); + evm.database(&mut state); + + let mut env = Env::default(); + if let Some(number) = block.number { + let nn = number.0[0]; + env.block.number = U256::from(nn); + } + local_fill!(env.block.coinbase, block.author); + local_fill!(env.block.timestamp, Some(block.timestamp), U256::from_limbs); + local_fill!( + env.block.difficulty, + Some(block.difficulty), + U256::from_limbs + ); + local_fill!(env.block.gas_limit, Some(block.gas_limit), U256::from_limbs); + if let Some(base_fee) = block.base_fee_per_gas { + local_fill!(env.block.basefee, Some(base_fee), U256::from_limbs); + } + + let txs = block.transactions.len(); + println!("Found {txs} transactions."); + + let console_bar = Arc::new(ProgressBar::new(txs as u64)); + let elapsed = std::time::Duration::ZERO; + + // Create the traces directory if it doesn't exist + std::fs::create_dir_all("traces").expect("Failed to create traces directory"); + + // Fill in CfgEnv + env.cfg.chain_id = chain_id; + for tx in block.transactions { + env.tx.caller = Address::from(tx.from.as_fixed_bytes()); + env.tx.gas_limit = tx.gas.as_u64(); + local_fill!(env.tx.gas_price, tx.gas_price, U256::from_limbs); + local_fill!(env.tx.value, Some(tx.value), U256::from_limbs); + env.tx.data = tx.input.0.into(); + let mut gas_priority_fee = U256::ZERO; + local_fill!( + gas_priority_fee, + tx.max_priority_fee_per_gas, + U256::from_limbs + ); + env.tx.gas_priority_fee = Some(gas_priority_fee); + env.tx.chain_id = Some(chain_id); + env.tx.nonce = Some(tx.nonce.as_u64()); + if let Some(access_list) = tx.access_list { + env.tx.access_list = access_list + .0 + .into_iter() + .map(|item| { + let new_keys: Vec = item + .storage_keys + .into_iter() + .map(|h256| U256::from_le_bytes(h256.0)) + .collect(); + (Address::from(item.address.as_fixed_bytes()), new_keys) + }) + .collect(); + } else { + env.tx.access_list = Default::default(); + } + + env.tx.transact_to = match tx.to { + Some(to_address) => TransactTo::Call(Address::from(to_address.as_fixed_bytes())), + None => TransactTo::create(), + }; + + evm.env = env.clone(); + + // Construct the file writer to write the trace to + let tx_number = tx.transaction_index.unwrap().0[0]; + let file_name = format!("traces/{}.json", tx_number); + let write = OpenOptions::new().write(true).create(true).open(file_name); + let inner = Arc::new(Mutex::new(BufWriter::new( + write.expect("Failed to open file"), + ))); + let writer = FlushWriter::new(Arc::clone(&inner)); + + // Inspect and commit the transaction to the EVM + let inspector = TracerEip3155::new(Box::new(writer), true, true); + if let Err(error) = evm.inspect_commit(inspector) { + println!("Got error: {:?}", error); + } + + // Flush the file writer + inner.lock().unwrap().flush().expect("Failed to flush file"); + + console_bar.inc(1); + } + + console_bar.finish_with_message("Finished all transactions."); + println!( + "Finished execution. Total CPU time: {:.6}s", + elapsed.as_secs_f64() + ); + + Ok(()) +}