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

How to simulate debug_traceCall with revm #1092

Closed
metaclips opened this issue Feb 16, 2024 · 8 comments
Closed

How to simulate debug_traceCall with revm #1092

metaclips opened this issue Feb 16, 2024 · 8 comments
Labels

Comments

@metaclips
Copy link

Hello, I have been looking into the examples of revm, most especially that of block traces, any way I can modify the example to give a simplified output shown on debug_traceCall

@Blaeaea
Copy link

Blaeaea commented Feb 16, 2024

i don't think that's possible with revm, revm is just a rust implementation of evm

@mattsse
Copy link
Collaborator

mattsse commented Feb 16, 2024

for rpc tracing, ptal https://github.com/paradigmxyz/evm-inspectors

@Blaeaea
Copy link

Blaeaea commented Feb 16, 2024

for rpc tracing, ptal https://github.com/paradigmxyz/evm-inspectors

could you add example of this?

@mattsse
Copy link
Collaborator

mattsse commented Feb 16, 2024

you can take a look at the anvil or reth codebase
but feel free to open an issue there for more examples

@metaclips
Copy link
Author

metaclips commented Feb 16, 2024

Kind of hackish, but works for me (kind of 😅)

// 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::{CustomPrintTracer, TracerEip3155};
use revm::primitives::{Address, TransactTo, U256};
use revm::{inspector_handle_register, Evm};
use revm_inspectors::tracing::TracingInspectorConfig;
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("RPC_URL")?;
    let client = Arc::new(client);

    // Params
    let chain_id: u64 = 0;
    let block_number = 0;

    // 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],
        block.transactions.first().unwrap().hash
    );
    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::builder()
        .with_db(&mut state)
        .with_external_context(revm_inspectors::tracing::TracingInspector::new(
            TracingInspectorConfig {
                exclude_precompile_calls: true,
                record_call_return_data: true,
                record_logs: false,
                record_memory_snapshots: false,
                record_state_diff: false,
                record_steps: false,
                record_stack_snapshots: revm_inspectors::tracing::StackSnapshotType::Pushes,
            },
        ))
        .modify_block_env(|b| {
            if let Some(number) = block.number {
                let nn = number.0[0];
                b.number = U256::from(nn);
            }
            local_fill!(b.coinbase, block.author);
            local_fill!(b.timestamp, Some(block.timestamp), U256::from_limbs);
            local_fill!(b.difficulty, Some(block.difficulty), U256::from_limbs);
            local_fill!(b.gas_limit, Some(block.gas_limit), U256::from_limbs);
            if let Some(base_fee) = block.base_fee_per_gas {
                local_fill!(b.basefee, Some(base_fee), U256::from_limbs);
            }
        })
        .modify_cfg_env(|c| {
            c.chain_id = chain_id;
            c.limit_contract_code_size = None;
        })
        .build();

    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");

    evm.context.external.get_traces();

    // Fill in CfgEnv
    for (i, tx) in block.transactions.iter().enumerate() {
        evm = evm
            .modify()
            .modify_tx_env(|etx| {
                etx.caller = Address::from(tx.from.as_fixed_bytes());
                etx.gas_limit = tx.gas.as_u64();
                local_fill!(etx.gas_price, tx.gas_price, U256::from_limbs);
                local_fill!(etx.value, Some(tx.value), U256::from_limbs);
                etx.data = tx.clone().input.0.into();
                let mut gas_priority_fee = U256::ZERO;
                local_fill!(
                    gas_priority_fee,
                    tx.max_priority_fee_per_gas,
                    U256::from_limbs
                );
                etx.gas_priority_fee = Some(gas_priority_fee);
                etx.chain_id = Some(chain_id);
                etx.nonce = Some(tx.nonce.as_u64());
                if let Some(access_list) = tx.clone().access_list {
                    etx.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 {
                    etx.access_list = Default::default();
                }

                etx.transact_to = match tx.to {
                    Some(to_address) => {
                        TransactTo::Call(Address::from(to_address.as_fixed_bytes()))
                    }
                    None => TransactTo::create(),
                };
            })
            .build();

        // Construct the file writer to write the trace to
        let file_name = format!("{}.log", i);
        let write = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(file_name);
        let inner = Arc::new(Mutex::new(BufWriter::new(
            write.expect("Failed to open file"),
        )));

        println!("{:?}", tx.hash);
        let mut writer = FlushWriter::new(Arc::clone(&inner));

        if let Err(error) = evm.transact_commit() {
            println!("Got error: {:?}", error);
        }

        // Inspect and commit the transaction to the EVM
        let traces = evm.context.external.get_traces(); // .set_writer(Box::new(writer));

        writer
            .write_all(format!("{:#?}", traces).as_bytes())
            .unwrap();

        // 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: {}s",
        elapsed.as_secs_f64()
    );

    Ok(())
}

I'll leave this issue open till we have a more better solution

@metaclips
Copy link
Author

metaclips commented Feb 17, 2024

@rakita the generate_block_traces.rs blocks for me, investigating, I noticed that this condition is ALWAYS met leading to the program blocking and CPU going to 99%. I wonder what I could be doing wrong. I am running from the main branch and ran the example as is. Could I be doing something wrong?

@rakita
Copy link
Member

rakita commented Feb 21, 2024

@rakita the generate_block_traces.rs blocks for me, investigating, I noticed that this condition is ALWAYS met leading to the program blocking and CPU going to 99%. I wonder what I could be doing wrong. I am running from the main branch and ran the example as is. Could I be doing something wrong?

Hey it is probably related to this: #1117

Fatal error can happen if rpc does not return. The bug was that this was not propagated and would continue running.

@rakita
Copy link
Member

rakita commented Mar 2, 2024

would close this

@rakita rakita closed this as completed Mar 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants