Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(examples): Generate Block Traces #895

Merged
merged 1 commit into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<tx index>.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.
Expand Down
6 changes: 6 additions & 0 deletions crates/revm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -78,6 +79,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"
Expand Down
175 changes: 175 additions & 0 deletions examples/generate_block_traces.rs
Original file line number Diff line number Diff line change
@@ -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<Mutex<BufWriter<std::fs::File>>>,
}

impl FlushWriter {
fn new(writer: Arc<Mutex<BufWriter<std::fs::File>>>) -> Self {
Self { writer }
}
}

impl Write for FlushWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
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<M>
let client = Provider::<Http>::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<EthersDB<Provider<Http>>> = 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<U256> = 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(())
}