diff --git a/Cargo.lock b/Cargo.lock index 282945a6ea..219883f08c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -881,8 +881,13 @@ dependencies = [ "clap 4.5.21", "ethrex-core", "ethrex-rlp", + "ethrex-vm", + "futures-util", "hex", "reqwest", + "revm 14.0.3", + "revm-inspectors", + "revm-primitives 10.0.0", "serde", "serde_json", "tokio", diff --git a/crates/l2/prover/bench/Cargo.toml b/crates/l2/prover/bench/Cargo.toml index bc32ccdaf5..74307e3adc 100644 --- a/crates/l2/prover/bench/Cargo.toml +++ b/crates/l2/prover/bench/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] ethrex-core.workspace = true ethrex-rlp.workspace = true +ethrex-vm.workspace = true #rsp-host-executor = { git = "https://github.com/succinctlabs/rsp" } #rsp-client-executor = { git = "https://github.com/succinctlabs/rsp" } @@ -16,3 +17,18 @@ clap = { version = "4.5.21", features = ["derive"] } serde.workspace = true hex.workspace = true serde_json.workspace = true + +revm = { version = "14.0.3", features = [ + "serde", + "std", + "serde-json", + "optional_no_base_fee", + "optional_block_gas_limit", +], default-features = false } + +# These dependencies must be kept up to date with the corresponding revm version, otherwise errors may pop up because of trait implementation mismatches +revm-inspectors = { version = "0.8.1" } +revm-primitives = { version = "10.0.0", features = [ + "std", +], default-features = false } +futures-util = "0.3.31" diff --git a/crates/l2/prover/bench/src/lib.rs b/crates/l2/prover/bench/src/lib.rs index 06ff34e270..06a3fd023e 100644 --- a/crates/l2/prover/bench/src/lib.rs +++ b/crates/l2/prover/bench/src/lib.rs @@ -1,138 +1 @@ -use ethrex_core::{ - types::{AccountState, Block}, - Address, -}; -use ethrex_rlp::decode::RLPDecode; - -use serde::Deserialize; -use serde_json::json; - -pub async fn get_block(rpc_url: &str, block_number: usize) -> Result { - let client = reqwest::Client::new(); - - let block_number = format!("0x{block_number:x}"); - let request = &json!({ - "id": 1, - "jsonrpc": "2.0", - "method": "debug_getRawBlock", - "params": [block_number] - }); - - let response = client - .post(rpc_url) - .json(request) - .send() - .await - .map_err(|err| err.to_string())?; - - response - .json::() - .await - .map_err(|err| err.to_string()) - .and_then(|json| { - json.get("result") - .cloned() - .ok_or("failed to get result from response".to_string()) - }) - .and_then(|result| serde_json::from_value::(result).map_err(|err| err.to_string())) - .and_then(|hex_encoded_block| { - hex::decode(hex_encoded_block.trim_start_matches("0x")).map_err(|err| err.to_string()) - }) - .and_then(|encoded_block| { - Block::decode_unfinished(&encoded_block) - .map_err(|err| err.to_string()) - .map(|decoded| decoded.0) - }) -} - -pub async fn get_account( - rpc_url: &str, - block_number: usize, - address: Address, -) -> Result { - let client = reqwest::Client::new(); - - let block_number = format!("0x{block_number:x}"); - let address = format!("0x{address:x}"); - - let request = &json!( - { - "id": 1, - "jsonrpc": "2.0", - "method": "eth_getProof", - "params":[address, [], block_number] - } - ); - let response = client - .post(rpc_url) - .json(request) - .send() - .await - .map_err(|err| err.to_string())?; - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - struct AccountProof { - balance: String, - code_hash: String, - nonce: String, - storage_hash: String, - } - - let account_proof: AccountProof = response - .json::() - .await - .map_err(|err| err.to_string()) - .and_then(|json| { - json.get("result") - .cloned() - .ok_or("failed to get result from response".to_string()) - }) - .and_then(|result| serde_json::from_value(result).map_err(|err| err.to_string()))?; - - Ok(AccountState { - nonce: u64::from_str_radix(account_proof.nonce.trim_start_matches("0x"), 16) - .map_err(|_| "failed to parse nonce".to_string())?, - balance: account_proof - .balance - .parse() - .map_err(|_| "failed to parse balance".to_string())?, - storage_root: account_proof - .storage_hash - .parse() - .map_err(|_| "failed to parse storage root".to_string())?, - code_hash: account_proof - .code_hash - .parse() - .map_err(|_| "failed to parse code hash".to_string())?, - }) -} - -#[cfg(test)] -mod test { - use ethrex_core::Address; - - use crate::{get_account, get_block}; - - const BLOCK_NUMBER: usize = 21315830; - const RPC_URL: &str = ""; - const VITALIK_ADDR: &str = "d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; - - #[ignore = "Needs to manually set RPC_URL"] - #[tokio::test] - async fn get_block_works() { - get_block(RPC_URL, BLOCK_NUMBER).await.unwrap(); - } - - #[ignore = "Needs to manually set RPC_URL"] - #[tokio::test] - async fn get_account_works() { - get_account( - RPC_URL, - BLOCK_NUMBER, - Address::from_slice(&hex::decode(VITALIK_ADDR).unwrap()), - ) - .await - .unwrap(); - } -} +pub mod rpc; diff --git a/crates/l2/prover/bench/src/main.rs b/crates/l2/prover/bench/src/main.rs index 19bef0bb34..23a9640891 100644 --- a/crates/l2/prover/bench/src/main.rs +++ b/crates/l2/prover/bench/src/main.rs @@ -1,5 +1,10 @@ -use bench::get_block; +use bench::rpc::{get_account, get_block}; use clap::Parser; +use ethrex_vm::{execution_db::touched_state::get_touched_state, SpecId}; +use futures_util::future::join_all; + +const MAINNET_CHAIN_ID: u64 = 0x1; +const MAINNET_SPEC_ID: SpecId = SpecId::CANCUN; #[derive(Parser, Debug)] struct Args { @@ -11,11 +16,32 @@ struct Args { #[tokio::main] async fn main() { - let args = Args::parse(); + let Args { + rpc_url, + block_number, + } = Args::parse(); - let block = get_block(args.rpc_url, args.block_number) + // fetch block + let block = get_block(&rpc_url, &block_number) .await - .expect("failed"); + .expect("failed to fetch block"); + + // get all accounts and storage keys touched during execution of block + let touched_state = get_touched_state(&block, MAINNET_CHAIN_ID, MAINNET_SPEC_ID) + .expect("failed to get touched state"); + + // fetch all accounts and storage touched + let _accounts = join_all( + touched_state + .iter() + .map(|(address, _)| get_account(&rpc_url, &block_number, address)), + ) + .await + .into_iter() + .collect::, String>>() + .expect("failed to fetch accounts"); + // TODO: storage - println!("Succesfully fetched block {}", block.hash()); + // 4. create prover program input and execute. Measure time. + // 5. invoke rsp and execute too. Save in cache } diff --git a/crates/l2/prover/bench/src/rpc.rs b/crates/l2/prover/bench/src/rpc.rs new file mode 100644 index 0000000000..1269f86390 --- /dev/null +++ b/crates/l2/prover/bench/src/rpc.rs @@ -0,0 +1,136 @@ +use ethrex_core::{ + types::{AccountState, Block}, + Address, +}; +use ethrex_rlp::decode::RLPDecode; + +use serde::Deserialize; +use serde_json::json; + +pub async fn get_block(rpc_url: &str, block_number: &usize) -> Result { + let client = reqwest::Client::new(); + + let block_number = format!("0x{block_number:x}"); + let request = &json!({ + "id": 1, + "jsonrpc": "2.0", + "method": "debug_getRawBlock", + "params": [block_number] + }); + + let response = client + .post(rpc_url) + .json(request) + .send() + .await + .map_err(|err| err.to_string())?; + + response + .json::() + .await + .map_err(|err| err.to_string()) + .and_then(|json| { + json.get("result") + .cloned() + .ok_or("failed to get result from response".to_string()) + }) + .and_then(|result| serde_json::from_value::(result).map_err(|err| err.to_string())) + .and_then(|hex_encoded_block| { + hex::decode(hex_encoded_block.trim_start_matches("0x")).map_err(|err| err.to_string()) + }) + .and_then(|encoded_block| { + Block::decode_unfinished(&encoded_block) + .map_err(|err| err.to_string()) + .map(|decoded| decoded.0) + }) +} + +pub async fn get_account( + rpc_url: &str, + block_number: &usize, + address: &Address, +) -> Result { + let client = reqwest::Client::new(); + + let block_number = format!("0x{block_number:x}"); + let address = format!("0x{address:x}"); + + let request = &json!( + { + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getProof", + "params":[address, [], block_number] + } + ); + let response = client + .post(rpc_url) + .json(request) + .send() + .await + .map_err(|err| err.to_string())?; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct AccountProof { + balance: String, + code_hash: String, + nonce: String, + storage_hash: String, + } + + let account_proof: AccountProof = response + .json::() + .await + .map_err(|err| err.to_string()) + .and_then(|json| { + json.get("result") + .cloned() + .ok_or("failed to get result from response".to_string()) + }) + .and_then(|result| serde_json::from_value(result).map_err(|err| err.to_string()))?; + + Ok(AccountState { + nonce: u64::from_str_radix(account_proof.nonce.trim_start_matches("0x"), 16) + .map_err(|_| "failed to parse nonce".to_string())?, + balance: account_proof + .balance + .parse() + .map_err(|_| "failed to parse balance".to_string())?, + storage_root: account_proof + .storage_hash + .parse() + .map_err(|_| "failed to parse storage root".to_string())?, + code_hash: account_proof + .code_hash + .parse() + .map_err(|_| "failed to parse code hash".to_string())?, + }) +} + +#[cfg(test)] +mod test { + use ethrex_core::Address; + + use super::*; + + const BLOCK_NUMBER: usize = 21315830; + const RPC_URL: &str = "https://gateway-api.cabinet-node.com/eaebc3a9aca25671f165e4032b3453eb"; + const VITALIK_ADDR: &str = "d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + + #[tokio::test] + async fn get_block_works() { + get_block(RPC_URL, BLOCK_NUMBER).await.unwrap(); + } + + #[tokio::test] + async fn get_account_works() { + get_account( + RPC_URL, + BLOCK_NUMBER, + Address::from_slice(&hex::decode(VITALIK_ADDR).unwrap()), + ) + .await + .unwrap(); + } +} diff --git a/crates/vm/execution_db.rs b/crates/vm/execution_db.rs index a7a81040d7..32f05ff1f1 100644 --- a/crates/vm/execution_db.rs +++ b/crates/vm/execution_db.rs @@ -234,3 +234,110 @@ impl DatabaseRef for ExecutionDB { .ok_or(ExecutionDBError::BlockHashNotFound(number)) } } + +pub mod touched_state { + use ethrex_core::{types::Block, Address, U256}; + use revm::{inspectors::TracerEip3155, DatabaseCommit, DatabaseRef, Evm}; + use revm_primitives::{ + Account as RevmAccount, Address as RevmAddress, EVMError, SpecId, U256 as RevmU256, + }; + + use crate::{block_env, tx_env}; + + type TouchedStateDBError = (); + + /// Dummy DB for storing touched account addresses and storage keys while executing a block. + #[derive(Default)] + struct TouchedStateDB { + touched_state: Vec<(RevmAddress, Vec)>, + } + + #[allow(unused_variables)] + impl DatabaseRef for TouchedStateDB { + type Error = TouchedStateDBError; + + fn basic_ref( + &self, + address: RevmAddress, + ) -> Result, Self::Error> { + Ok(Some(Default::default())) + } + fn storage_ref( + &self, + address: RevmAddress, + index: RevmU256, + ) -> Result { + Ok(Default::default()) + } + fn block_hash_ref(&self, number: u64) -> Result { + Ok(Default::default()) + } + fn code_by_hash_ref( + &self, + code_hash: revm_primitives::B256, + ) -> Result { + Ok(Default::default()) + } + } + + impl DatabaseCommit for TouchedStateDB { + fn commit(&mut self, changes: revm_primitives::HashMap) { + for (address, account) in changes { + if !account.is_touched() { + continue; + } + self.touched_state + .push((address, account.storage.keys().cloned().collect())); + } + } + } + + /// Get all touched account addresses and storage keys during the execution of a block. + /// + /// Generally used for building an [super::ExecutionDB]. + pub fn get_touched_state( + block: &Block, + chain_id: u64, + spec_id: SpecId, + ) -> Result)>, EVMError> { + let block_env = block_env(&block.header); + let mut db = TouchedStateDB::default(); + + for transaction in &block.body.transactions { + let tx_env = tx_env(transaction); + + // execute tx + let evm_builder = Evm::builder() + .with_block_env(block_env.clone()) + .with_tx_env(tx_env) + .modify_cfg_env(|cfg| cfg.chain_id = chain_id) + .with_spec_id(spec_id) + .with_external_context( + TracerEip3155::new(Box::new(std::io::stderr())).without_summary(), + ); + let mut evm = evm_builder.with_ref_db(&mut db).build(); + evm.transact_commit()?; + } + + let mut touched_state: Vec<(Address, Vec)> = db + .touched_state + .into_iter() + .map(|(address, storage_keys)| { + ( + Address::from_slice(address.as_slice()), + storage_keys + .into_iter() + .map(|key| U256::from_big_endian(&key.to_be_bytes_vec())) + .collect(), + ) + }) + .collect(); + + // add withdrawal accounts + if let Some(ref withdrawals) = block.body.withdrawals { + touched_state.extend(withdrawals.iter().map(|w| (w.address, Vec::new()))) + } + + Ok(touched_state) + } +}