diff --git a/Cargo.lock b/Cargo.lock index 490bceb99c..ae479accd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,17 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "again" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05802a5ad4d172eaf796f7047b42d0af9db513585d16d4169660a21613d34b93" +dependencies = [ + "log", + "rand 0.7.3", + "wasm-timer", +] + [[package]] name = "ahash" version = "0.8.11" @@ -770,6 +781,26 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-stdin" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ff8b5d9b5ec29e0f49583ba71847b8c8888b67a8510133048a380903aa6822" +dependencies = [ + "tokio", +] + +[[package]] +name = "async-throttle" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c99532de164435a0b91279e715bff4fa0d164643b409a67761907ffc210ee8f" +dependencies = [ + "backoff", + "dashmap 5.5.3", + "tokio", +] + [[package]] name = "async-trait" version = "0.1.83" @@ -973,6 +1004,20 @@ dependencies = [ "tower-service", ] +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.15", + "instant", + "pin-project-lite", + "rand 0.8.5", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -1025,6 +1070,35 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bench" +version = "0.1.0" +dependencies = [ + "again", + "bincode", + "bytes", + "clap 4.5.21", + "ethrex-core", + "ethrex-l2", + "ethrex-prover", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "futures-util", + "hex", + "lazy_static", + "reqwest 0.12.9", + "revm 14.0.3", + "revm-inspectors", + "revm-primitives 10.0.0", + "serde", + "serde_json", + "tokio", + "tokio-utils", + "zkvm_interface", +] + [[package]] name = "bincode" version = "1.3.3" @@ -1906,6 +1980,19 @@ dependencies = [ "serde", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.10", +] + [[package]] name = "dashu" version = "0.4.2" @@ -3503,6 +3590,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -3512,7 +3610,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -4957,7 +5055,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -6155,7 +6253,7 @@ dependencies = [ "lazy_static", "num-traits", "rand 0.8.5", - "rand_chacha", + "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax 0.8.5", "rusty-fork", @@ -6237,7 +6335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", - "getrandom", + "getrandom 0.2.15", "rand 0.8.5", "ring 0.17.8", "rustc-hash 2.0.0", @@ -6292,6 +6390,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -6299,11 +6410,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", "serde", ] +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -6329,13 +6450,31 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -6424,7 +6563,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror 1.0.69", ] @@ -6778,7 +6917,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -7024,7 +7163,7 @@ dependencies = [ "bytemuck", "bytes", "elf", - "getrandom", + "getrandom 0.2.15", "hex", "lazy-regex", "num-bigint 0.4.6", @@ -7059,7 +7198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26bbcc486222a0086d36244ae1f388cac1318270bca2dd23d77af2005d679edf" dependencies = [ "bytemuck", - "getrandom", + "getrandom 0.2.15", "libm", "stability", ] @@ -7675,6 +7814,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shutdown-async" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2799e69bde7e68bedd86c6d94bffa783219114f1f31435ddda61f4aeba348ff" +dependencies = [ + "tokio", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -8169,7 +8317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a597ed68cd03f80d9cdb9f4b50924e3c890c35c39956f7e87dd2262b72b2d12b" dependencies = [ "arrayref", - "getrandom", + "getrandom 0.2.15", "hashbrown 0.14.5", "itertools 0.13.0", "num-traits", @@ -8694,7 +8842,7 @@ dependencies = [ "aho-corasick 0.7.20", "clap 2.34.0", "crossbeam-channel", - "dashmap", + "dashmap 4.0.2", "dirs 3.0.2", "encoding_rs_io", "env_logger", @@ -8789,6 +8937,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-utils" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de75f75f464153a50fe48b9675360e3cf2ae1d7d81f9751363bd2ee4888f5ce8" +dependencies = [ + "async-stdin", + "async-throttle", + "shutdown-async", + "tub", +] + [[package]] name = "toml" version = "0.5.11" @@ -8971,6 +9131,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tub" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bca43faba247bc76eb1d6c1b8b561e4a1c5bdd427cc3d7a007faabea75c683a" +dependencies = [ + "crossbeam-queue", + "tokio", +] + [[package]] name = "twirp-rs" version = "0.13.0-succinct" @@ -9196,7 +9366,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom", + "getrandom 0.2.15", "serde", ] @@ -9280,6 +9450,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -9366,6 +9542,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.72" diff --git a/Cargo.toml b/Cargo.toml index f1f07d6bc2..effb88580e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "crates/vm/levm/bench/revm_comparison", "crates/l2/", "crates/l2/prover", + "crates/l2/prover/bench", "crates/l2/contracts", "crates/l2/sdk", "cmd/loc", diff --git a/crates/l2/proposer/prover_server.rs b/crates/l2/proposer/prover_server.rs index 235ec5e382..93ff9fb766 100644 --- a/crates/l2/proposer/prover_server.rs +++ b/crates/l2/proposer/prover_server.rs @@ -19,7 +19,11 @@ use ethrex_l2_sdk::{ eth_client::{eth_sender::Overrides, EthClient, WrappedTransaction}, }; use ethrex_storage::Store; -use ethrex_vm::{execution_db::ExecutionDB, EvmError}; +use ethrex_vm::{ + db::StoreWrapper, + execution_db::{ExecutionDB, ToExecDB}, + EvmError, +}; use secp256k1::SecretKey; use serde::{Deserialize, Serialize}; use std::{ @@ -405,12 +409,16 @@ impl ProverServer { let block = Block::new(header, body); - let db = - ExecutionDB::from_store(&block, self.store.clone()).map_err(EvmError::ExecutionDB)?; + let parent_hash = block.header.parent_hash; + let store = StoreWrapper { + store: self.store.clone(), + block_hash: parent_hash, + }; + let db = store.to_exec_db(&block).map_err(EvmError::ExecutionDB)?; let parent_block_header = self .store - .get_block_header_by_hash(block.header.parent_hash)? + .get_block_header_by_hash(parent_hash)? .ok_or(ProverServerError::StorageDataIsNone)?; debug!("Created prover input for block {block_number}"); diff --git a/crates/l2/prover/bench/Cargo.toml b/crates/l2/prover/bench/Cargo.toml new file mode 100644 index 0000000000..22a0767405 --- /dev/null +++ b/crates/l2/prover/bench/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "bench" +version = "0.1.0" +edition = "2021" + +[dependencies] +ethrex-core.workspace = true +ethrex-rlp.workspace = true +ethrex-vm.workspace = true +ethrex-prover.workspace = true +ethrex-l2.workspace = true +ethrex-trie.workspace = true +ethrex-storage.workspace = true + +zkvm_interface = { path = "../zkvm/interface", default-features = false } + +serde.workspace = true +hex.workspace = true +serde_json.workspace = true +bytes.workspace = true + +tokio = { version = "1.21", default-features = false, features = ["full"] } +reqwest = { version = "0.12.9", features = [ + "json", + "rustls-tls", +], default-features = false } +clap = { version = "4.5.21", features = ["derive"] } + +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" +tokio-utils = "0.1.2" +again = "0.1.2" +lazy_static = "1.5.0" +bincode = "1.3.3" + +[features] +## risc0 +build_risc0 = ["zkvm_interface/build_risc0", "ethrex-prover/build_risc0"] +gpu = ["ethrex-prover/gpu"] +## sp1 +build_sp1 = ["zkvm_interface/build_sp1", "zkvm_interface/build_risc0"] diff --git a/crates/l2/prover/bench/src/constants.rs b/crates/l2/prover/bench/src/constants.rs new file mode 100644 index 0000000000..084e1f92d3 --- /dev/null +++ b/crates/l2/prover/bench/src/constants.rs @@ -0,0 +1,39 @@ +// Blockchain related constants + +use std::time::Duration; + +use ethrex_core::types::ChainConfig; +use revm_primitives::SpecId; + +// Chain config for different forks as defined on https://ethereum.github.io/execution-spec-tests/v3.0.0/consuming_tests/common_types/#fork +pub static CANCUN_CONFIG: ChainConfig = ChainConfig { + chain_id: 1_u64, + homestead_block: Some(0), + dao_fork_block: Some(0), + dao_fork_support: true, + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + muir_glacier_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + arrow_glacier_block: Some(0), + gray_glacier_block: Some(0), + merge_netsplit_block: Some(0), + terminal_total_difficulty: Some(0), + shanghai_time: Some(0), + cancun_time: Some(0), + prague_time: None, + terminal_total_difficulty_passed: false, + verkle_time: None, +}; +pub const MAINNET_CHAIN_ID: u64 = 0x1; +pub const MAINNET_SPEC_ID: SpecId = SpecId::CANCUN; + +// RPC related constants + +pub const RPC_RATE_LIMIT: usize = 100; // requests per second diff --git a/crates/l2/prover/bench/src/lib.rs b/crates/l2/prover/bench/src/lib.rs new file mode 100644 index 0000000000..bc1bcdc5c0 --- /dev/null +++ b/crates/l2/prover/bench/src/lib.rs @@ -0,0 +1,2 @@ +pub mod constants; +pub mod rpc; diff --git a/crates/l2/prover/bench/src/main.rs b/crates/l2/prover/bench/src/main.rs new file mode 100644 index 0000000000..00b7d6c6b0 --- /dev/null +++ b/crates/l2/prover/bench/src/main.rs @@ -0,0 +1,85 @@ +use std::{fs::File, io::Write}; + +use bench::{ + constants::{CANCUN_CONFIG, MAINNET_CHAIN_ID}, + rpc::{db::RpcDB, get_block, get_latest_block_number}, +}; +use clap::Parser; +use ethrex_l2::utils::prover::proving_systems::ProverType; +use ethrex_prover_lib::prover::create_prover; +use ethrex_vm::execution_db::ToExecDB; +use zkvm_interface::io::ProgramInput; + +#[derive(Parser, Debug)] +struct Args { + #[arg(short, long)] + rpc_url: String, + #[arg(short, long)] + block_number: Option, +} + +#[tokio::main] +async fn main() { + let Args { + rpc_url, + block_number, + } = Args::parse(); + + let block_number = match block_number { + Some(n) => n, + None => { + println!("fetching latest block number"); + get_latest_block_number(&rpc_url) + .await + .expect("failed to fetch latest block number") + } + }; + + println!("fetching block {block_number} and its parent header"); + let block = get_block(&rpc_url, block_number) + .await + .expect("failed to fetch block"); + let parent_block_header = get_block(&rpc_url, block_number - 1) + .await + .expect("failed to fetch block") + .header; + + let db = if let Ok(file) = File::open("db.bin") { + println!("db file found"); + bincode::deserialize_from(file).expect("failed to deserialize db from file") + } else { + println!("db file not found"); + + println!("populating rpc db cache"); + let rpc_db = RpcDB::with_cache(&rpc_url, block_number - 1, &block) + .await + .expect("failed to create rpc db"); + + println!("pre-executing to build execution db"); + let db = rpc_db + .to_exec_db(&block) + .expect("failed to build execution db"); + + println!("writing db to file db.bin"); + let mut file = File::create("db.bin").expect("failed to create db file"); + file.write_all( + bincode::serialize(&db) + .expect("failed to serialize db") + .as_slice(), + ) + .expect("failed to write db to file"); + + db + }; + + println!("proving"); + let mut prover = create_prover(ProverType::SP1); + let receipt = prover + .prove(ProgramInput { + block, + parent_block_header, + db, + }) + .expect("proving failed"); + let execution_gas = prover.get_gas().expect("failed to get execution gas"); +} diff --git a/crates/l2/prover/bench/src/rpc/db.rs b/crates/l2/prover/bench/src/rpc/db.rs new file mode 100644 index 0000000000..a9d8cd8f41 --- /dev/null +++ b/crates/l2/prover/bench/src/rpc/db.rs @@ -0,0 +1,323 @@ +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::collections::HashMap; + +use crate::constants::{CANCUN_CONFIG, RPC_RATE_LIMIT}; +use crate::rpc::{get_account, get_block, get_storage, retry}; + +use ethrex_core::types::AccountInfo; +use ethrex_core::{ + types::{Account as CoreAccount, Block, TxKind}, + Address, H256, +}; +use ethrex_vm::execution_db::{ExecutionDB, ToExecDB}; +use ethrex_vm::spec_id; +use futures_util::future::join_all; +use revm::db::CacheDB; +use revm::DatabaseRef; +use revm_primitives::{ + AccountInfo as RevmAccountInfo, Address as RevmAddress, Bytecode as RevmBytecode, + Bytes as RevmBytes, B256 as RevmB256, U256 as RevmU256, +}; +use tokio_utils::RateLimiter; + +use super::{Account, NodeRLP}; + +pub struct RpcDB { + pub rpc_url: String, + pub block_number: usize, + // we concurrently download tx callers before pre-execution to minimize sequential RPC calls + pub cache: RefCell>>, + pub block_hashes: RefCell>, +} + +impl RpcDB { + pub async fn with_cache( + rpc_url: &str, + block_number: usize, + block: &Block, + ) -> Result { + let mut db = RpcDB { + rpc_url: rpc_url.to_string(), + block_number, + cache: RefCell::new(HashMap::new()), + block_hashes: RefCell::new(HashMap::new()), + }; + + db.cache_accounts(block).await?; + + Ok(db) + } + + async fn cache_accounts(&mut self, block: &Block) -> Result<(), String> { + let txs = &block.body.transactions; + + let callers = txs.iter().map(|tx| tx.sender()); + let to = txs.iter().filter_map(|tx| match tx.to() { + TxKind::Call(to) => Some(to), + TxKind::Create => None, + }); + let accessed_storage: Vec<_> = txs.iter().flat_map(|tx| tx.access_list()).collect(); + + // dedup accounts and concatenate accessed storage keys + let mut accounts = HashMap::new(); + for (address, keys) in callers + .chain(to) + .map(|address| (address, Vec::new())) + .chain(accessed_storage) + { + accounts + .entry(address) + .or_insert_with(Vec::new) + .extend(keys); + } + let accounts: Vec<_> = accounts.into_iter().collect(); + *self.cache.borrow_mut() = self.fetch_accounts(&accounts).await?; + + Ok(()) + } + + async fn fetch_accounts( + &self, + accounts: &[(Address, Vec)], + ) -> Result>, String> { + let rate_limiter = RateLimiter::new(std::time::Duration::from_secs(1)); + let mut fetched = HashMap::new(); + + let mut counter = 0; + for chunk in accounts.chunks(RPC_RATE_LIMIT) { + let futures = chunk.iter().map(|(address, storage_keys)| async move { + Ok(( + *address, + retry(|| get_account(&self.rpc_url, self.block_number, address, storage_keys)) + .await?, + )) + }); + + let fetched_chunk = rate_limiter + .throttle(|| async { join_all(futures).await }) + .await + .into_iter() + .collect::, String>>()?; + + fetched.extend(fetched_chunk); + + counter += chunk.len(); + println!("fetched {} accounts of {}", counter, accounts.len()); + } + + Ok(fetched) + } +} + +impl DatabaseRef for RpcDB { + type Error = String; + + fn basic_ref(&self, address: RevmAddress) -> Result, Self::Error> { + let address = Address::from(address.0.as_ref()); + + let account = match self.cache.borrow_mut().entry(address) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => { + println!("retrieving account info for address {address}"); + let handle = tokio::runtime::Handle::current(); + let account = tokio::task::block_in_place(|| { + handle.block_on(get_account(&self.rpc_url, self.block_number, &address, &[])) + })?; + entry.insert(account.clone()); + account + } + }; + + let account = account.map(|account| RevmAccountInfo { + nonce: account.account_state.nonce, + balance: RevmU256::from_limbs(account.account_state.balance.0), + code_hash: RevmB256::from(account.account_state.code_hash.0), + code: account + .code + .map(|code| RevmBytecode::new_raw(RevmBytes(code))), + }); + + Ok(account) + } + #[allow(unused_variables)] + fn code_by_hash_ref(&self, code_hash: RevmB256) -> Result { + Ok(RevmBytecode::default()) // code is stored in account info + } + fn storage_ref(&self, address: RevmAddress, index: RevmU256) -> Result { + let address = Address::from(address.0.as_ref()); + let index = H256::from_slice(&index.to_be_bytes_vec()); + + let value = match self.cache.borrow_mut().entry(address) { + Entry::Occupied(mut entry) => { + let Some(account) = entry.get() else { + return Err("account doesn't exists".to_string()); + }; + match account.storage.get(&index) { + Some(value) => *value, + None => { + println!("retrieving storage value for address {address} and key {index}"); + let handle = tokio::runtime::Handle::current(); + let account = tokio::task::block_in_place(|| { + handle.block_on(get_account( + &self.rpc_url, + self.block_number, + &address, + &account + .storage + .keys() + .chain(&[index]) + .cloned() + .collect::>(), + )) + })? + .expect("previously downloaded account doesn't exists"); + let value = *account + .storage + .get(&index) + .expect("rpc account response didn't include requested storage value"); + entry.insert(Some(account)); + value + } + } + } + Entry::Vacant(entry) => { + let handle = tokio::runtime::Handle::current(); + let account = tokio::task::block_in_place(|| { + handle.block_on(get_account( + &self.rpc_url, + self.block_number, + &address, + &[index], + )) + })?; + + if let Some(account) = entry.insert(account) { + *account + .storage + .get(&index) + .expect("rpc account response didn't include requested storage value") + } else { + return Err("account doesn't exists".to_string()); + } + } + }; + + Ok(RevmU256::from_limbs(value.0)) + } + fn block_hash_ref(&self, number: u64) -> Result { + let hash = match self.block_hashes.borrow_mut().entry(number) { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => { + println!("retrieving block hash for block number {number}"); + let handle = tokio::runtime::Handle::current(); + let hash = tokio::task::block_in_place(|| { + handle.block_on(get_block(&self.rpc_url, number as usize)) + }) + .map(|block| block.hash())?; + entry.insert(hash); + hash + } + }; + + Ok(RevmB256::from(hash.0)) + } +} + +impl ToExecDB for RpcDB { + fn to_exec_db( + &self, + block: &Block, + ) -> Result { + let parent_hash = block.header.parent_hash; + let chain_config = CANCUN_CONFIG; + + // pre-execute and get all downloaded accounts + let CacheDB { db, .. } = ExecutionDB::pre_execute( + block, + chain_config.chain_id, + spec_id(&chain_config, block.header.timestamp), + self, + ) + .unwrap(); // TODO: remove unwrap + let cache = db.cache.borrow(); + + let cache_iter = cache.iter().filter_map(|(address, account)| { + if let Some(account) = account { + Some((address, account)) + } else { + None + } + }); + + let accounts: HashMap<_, _> = cache_iter + .clone() + .map(|(address, account)| { + ( + *address, + AccountInfo { + code_hash: account.account_state.code_hash, + balance: account.account_state.balance, + nonce: account.account_state.nonce, + }, + ) + }) + .collect(); + let code = cache_iter + .clone() + .map(|(_, account)| { + ( + account.account_state.code_hash, + account.code.clone().unwrap_or_default(), + ) + }) + .collect(); + let storage = cache_iter + .clone() + .map(|(address, account)| (*address, account.storage.clone())) + .collect(); + let block_hashes = self + .block_hashes + .borrow() + .iter() + .map(|(num, hash)| (*num, *hash)) + .collect(); + + let storage_proofs = cache_iter + .clone() + .map(|(address, account)| { + let storage_root = account + .storage_proofs + .first() + .and_then(|nodes| nodes.first()) + .cloned(); + let other_storage_nodes: Vec = account + .storage_proofs + .iter() + .flat_map(|proofs| proofs.iter().skip(1).cloned()) + .collect(); + (*address, (storage_root, other_storage_nodes)) + }) + .collect(); + + let state_root = cache_iter + .clone() + .next() + .clone() + .and_then(|(_, account)| account.account_proof.first().cloned()); + let other_state_nodes = cache_iter + .flat_map(|(_, account)| account.account_proof.iter().skip(1).cloned()) + .collect(); + let state_proofs = (state_root, other_state_nodes); + + Ok(ExecutionDB { + accounts, + code, + storage, + block_hashes, + chain_config, + state_proofs, + storage_proofs, + }) + } +} diff --git a/crates/l2/prover/bench/src/rpc/mod.rs b/crates/l2/prover/bench/src/rpc/mod.rs new file mode 100644 index 0000000000..f94967c450 --- /dev/null +++ b/crates/l2/prover/bench/src/rpc/mod.rs @@ -0,0 +1,337 @@ +use std::time::Duration; +use std::{collections::HashMap, future::Future}; + +use again::{RetryPolicy, Task}; +use tokio::time::timeout; + +use bytes::Bytes; +use ethrex_core::{ + types::{AccountState, Block, EMPTY_KECCACK_HASH}, + Address, H256, U256, +}; +use ethrex_rlp::decode::RLPDecode; +use ethrex_trie::Trie; +use ethrex_storage::hash_address; + +use serde::de::DeserializeOwned; +use serde::Deserialize; +use serde_json::json; + +use lazy_static::lazy_static; + +pub mod db; + +pub type NodeRLP = Vec; + +lazy_static! { + static ref CLIENT: reqwest::Client = reqwest::Client::new(); +} + +#[derive(Clone)] +pub struct Account { + pub account_state: AccountState, + pub storage: HashMap, + pub account_proof: Vec, + pub storage_proofs: Vec>, + pub code: Option, +} + +pub async fn get_latest_block_number(rpc_url: &str) -> Result { + let request = &json!({ + "id": 1, + "jsonrpc": "2.0", + "method": "eth_blockNumber", + "params": [] + }); + + 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(get_result) + .and_then(decode_hex) + .and_then(|mut bytes| { + bytes.reverse(); + bytes.resize(8, 0); + bytes + .try_into() + .map_err(|_| "failed to deserialize block number".to_string()) + .map(usize::from_le_bytes) + }) +} + +pub async fn get_block(rpc_url: &str, block_number: usize) -> Result { + 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(get_result) + .and_then(decode_hex) + .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, + storage_keys: &[H256], +) -> Result, String> { + let block_number_str = format!("0x{block_number:x}"); + let address_str = format!("0x{address:x}"); + let storage_keys = storage_keys + .iter() + .map(|key| format!("0x{key:x}")) + .collect::>(); + + let request = &json!( + { + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getProof", + "params":[address_str, storage_keys, block_number_str] + } + ); + 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, + storage_proof: Vec, + account_proof: Vec, + } + + #[derive(Deserialize)] + struct StorageProof { + key: String, + value: String, + proof: Vec, + } + + let AccountProof { + balance, + code_hash, + nonce, + storage_hash, + storage_proof, + account_proof, + } = response + .json::() + .await + .map_err(|err| err.to_string()) + .and_then(get_result)?; + + let account_proof = account_proof + .into_iter() + .map(decode_hex) + .collect::, String>>()?; + + // check that account exists + let root = account_proof + .first() + .ok_or("account proof is empty".to_string())?; + let other: Vec<_> = account_proof + .iter() + .skip(1) + .cloned() + .collect(); + let trie = Trie::from_nodes(Some(root), &other) + .map_err(|err| format!("failed to build account proof trie: {err}"))?; + if trie.get(&hash_address(address)) + .map_err(|err| format!("failed get account from proof trie: {err}"))?.is_none() { + return Ok(None); + } + + let (storage, storage_proofs) = storage_proof + .into_iter() + .map(|proof| -> Result<_, String> { + let key: H256 = proof + .key + .parse() + .map_err(|_| "failed to parse storage key".to_string())?; + let value: U256 = proof + .value + .parse() + .map_err(|_| "failed to parse storage value".to_string())?; + let proofs = proof + .proof + .into_iter() + .map(decode_hex) + .collect::, _>>()?; + Ok(((key, value), proofs)) + }) + .collect::, Vec<_>), _>>()?; + + let account_state = AccountState { + nonce: u64::from_str_radix(nonce.trim_start_matches("0x"), 16) + .map_err(|_| "failed to parse nonce".to_string())?, + balance: balance + .parse() + .map_err(|_| "failed to parse balance".to_string())?, + storage_root: storage_hash + .parse() + .map_err(|_| "failed to parse storage root".to_string())?, + code_hash: code_hash + .parse() + .map_err(|_| "failed to parse code hash".to_string())?, + }; + + let code = if account_state.code_hash != *EMPTY_KECCACK_HASH { + Some(get_code(rpc_url, block_number, address).await?) + } else { + None + }; + + Ok(Some(Account { + account_state, + storage, + account_proof, + storage_proofs, + code, + })) +} + +pub async fn get_storage( + rpc_url: &str, + block_number: usize, + address: &Address, + storage_key: H256, +) -> Result { + let block_number_str = format!("0x{block_number:x}"); + let address_str = format!("0x{address:x}"); + let storage_key = format!("0x{storage_key:x}"); + + let request = &json!( + { + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getStorageAt", + "params":[address_str, storage_key, block_number_str] + } + ); + 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(get_result) +} + +pub async fn retry(mut fut: F) -> Result +where + F: Task, +{ + let policy = RetryPolicy::exponential(Duration::from_secs(1)); + policy.retry(|| fut.call()).await +} + +async fn get_code(rpc_url: &str, block_number: usize, address: &Address) -> Result { + let block_number = format!("0x{block_number:x}"); + let address = format!("0x{address:x}"); + let request = &json!({ + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getCode", + "params": [address, 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(get_result) + .and_then(decode_hex) + .map(Bytes::from_owner) +} + +fn get_result(response: serde_json::Value) -> Result { + match response.get("result") { + Some(result) => serde_json::from_value(result.clone()).map_err(|err| err.to_string()), + None => Err(format!("result not found, response is: {response}")), + } +} + +fn decode_hex(hex: String) -> Result, String> { + let mut trimmed = hex.trim_start_matches("0x").to_string(); + if trimmed.len() % 2 != 0 { + trimmed = "0".to_string() + &trimmed; + } + hex::decode(trimmed).map_err(|err| format!("failed to decode hex string: {err}")) +} + +#[cfg(test)] +mod test { + use ethrex_core::Address; + + use super::*; + + const BLOCK_NUMBER: usize = 21315830; + const RPC_URL: &str = ""; + const VITALIK_ADDR: &str = "d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + + #[ignore = "needs to manually set rpc url in constant"] + #[tokio::test] + async fn get_block_works() { + get_block(RPC_URL, BLOCK_NUMBER).await.unwrap(); + } + + #[ignore = "needs to manually set rpc url in constant"] + #[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/l2/prover/tests/perf_zkvm.rs b/crates/l2/prover/tests/perf_zkvm.rs index db3d80679e..ceb19f3302 100644 --- a/crates/l2/prover/tests/perf_zkvm.rs +++ b/crates/l2/prover/tests/perf_zkvm.rs @@ -7,7 +7,7 @@ use tracing::info; use ethrex_blockchain::add_block; use ethrex_prover_lib::prover::{Prover, Risc0Prover, Sp1Prover}; use ethrex_storage::{EngineType, Store}; -use ethrex_vm::execution_db::ExecutionDB; +use ethrex_vm::execution_db::{ExecutionDB, ToExecDB}; use zkvm_interface::io::ProgramInput; #[tokio::test] @@ -80,7 +80,11 @@ async fn setup() -> (ProgramInput, Block) { } let block_to_prove = blocks.last().unwrap(); - let db = ExecutionDB::from_store(block_to_prove, store.clone()).unwrap(); + let store = StoreWrapper { + store: store.clone(), + block_hash: block_to_prove.header.parent_hash, + }; + let db = store.to_exec_db(&block_to_prove).unwrap(); let parent_block_header = store .get_block_header_by_hash(block_to_prove.header.parent_hash) diff --git a/crates/l2/prover/zkvm/interface/src/lib.rs b/crates/l2/prover/zkvm/interface/src/lib.rs index 189e926eb8..3d22cb6495 100644 --- a/crates/l2/prover/zkvm/interface/src/lib.rs +++ b/crates/l2/prover/zkvm/interface/src/lib.rs @@ -78,7 +78,10 @@ pub mod io { pub mod trie { use std::collections::HashMap; - use ethrex_core::{types::AccountState, H160, U256}; + use ethrex_core::{ + types::{AccountInfo, AccountState}, + H160, U256, + }; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, error::RLPDecodeError}; use ethrex_storage::{hash_address, hash_key, AccountUpdate}; use ethrex_trie::{Trie, TrieError}; @@ -102,42 +105,73 @@ pub mod trie { state_trie: &Trie, storage_tries: &HashMap, ) -> Result { - for (address, account_info) in &db.accounts { - let storage_trie = storage_tries - .get(address) - .ok_or(Error::MissingStorageTrie(*address))?; - let storage_root = storage_trie.hash_no_commit(); - - // verify account and storage trie are valid + // verifies that, for each stored account: + // 1. account is in state trie + // 2. account info (nonce, balance, code hash) is correct (the same as encoded in trie) + // 3. if there's any storage: + // 3.a. storage root is correct (the same as encoded in trie) + // 3.b. for each storage value: + // 3.b.1. every value is in the storage trie, except for zero values which are absent + // 3.b.2. every value in trie is correct (the same as encoded in trie) + for (address, db_account_info) in &db.accounts { + // 1. account is in state trie let trie_account_state = match state_trie.get(&hash_address(address)) { Ok(Some(encoded_state)) => AccountState::decode(&encoded_state)?, - Ok(None) | Err(TrieError::InconsistentTree) => return Ok(false), // account not in trie + Ok(None) => { + return Ok(false); + } + Err(TrieError::InconsistentTree) => { + return Ok(false); + } Err(err) => return Err(err.into()), }; - let db_account_state = AccountState { - nonce: account_info.nonce, - balance: account_info.balance, - code_hash: account_info.code_hash, - storage_root, + let trie_account_info = AccountInfo { + nonce: trie_account_state.nonce, + balance: trie_account_state.balance, + code_hash: trie_account_state.code_hash, }; - if db_account_state != trie_account_state { + + // 2. account info is correct + if db_account_info != &trie_account_info { return Ok(false); } - // verify storage - for (key, db_value) in db - .storage - .get(address) - .ok_or(Error::StorageNotFound(*address))? - { - let trie_value = match storage_trie.get(&hash_key(key)) { - Ok(Some(encoded)) => U256::decode(&encoded)?, - Ok(None) | Err(TrieError::InconsistentTree) => return Ok(false), // value not in trie - Err(err) => return Err(err.into()), - }; - if *db_value != trie_value { - return Ok(false); + // 3. if there's any storage + match db.storage.get(address) { + Some(storage) if !storage.is_empty() => { + let storage_trie = storage_tries + .get(address) + .ok_or(Error::MissingStorageTrie(*address))?; + let storage_root = storage_trie.hash_no_commit(); + + // 3.a. storage root is correct + if storage_root != trie_account_state.storage_root { + return Ok(false); + } + + for (key, db_value) in storage { + // 3.b. every value is in storage trie, except for zero values which are + // absent + let trie_value = match storage_trie.get(&hash_key(key)) { + Ok(Some(encoded)) => U256::decode(&encoded)?, + Ok(None) if db_value.is_zero() => { + // an absent value must be zero + continue; + } + Ok(None) | Err(TrieError::InconsistentTree) => { + // a non-zero value must be encoded in the trie + return Ok(false); + } + Err(err) => return Err(err.into()), + }; + + // 3.c. every value is correct + if *db_value != trie_value { + return Ok(false); + } + } } + _ => {} } } diff --git a/crates/l2/utils/test_data_io.rs b/crates/l2/utils/test_data_io.rs index 34d9c8949a..2dc85a9a19 100644 --- a/crates/l2/utils/test_data_io.rs +++ b/crates/l2/utils/test_data_io.rs @@ -5,7 +5,10 @@ use ethrex_blockchain::add_block; use ethrex_core::types::{Block, Genesis}; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; use ethrex_storage::{EngineType, Store}; -use ethrex_vm::execution_db::ExecutionDB; +use ethrex_vm::{ + db::StoreWrapper, + execution_db::{ExecutionDB, ToExecDB}, +}; use tracing::info; use zkvm_interface::io::ProgramInput; @@ -76,12 +79,15 @@ pub fn generate_program_input( add_block(&block, &store)?; } + let parent_hash = block.header.parent_hash; let parent_block_header = store .get_block_header_by_hash(block.header.parent_hash)? - .ok_or(ProverInputError::InvalidParentBlock( - block.header.parent_hash, - ))?; - let db = ExecutionDB::from_store(&block, store)?; + .ok_or(ProverInputError::InvalidParentBlock(parent_hash))?; + let store = StoreWrapper { + store, + block_hash: parent_hash, + }; + let db = store.to_exec_db(&block)?; Ok(ProgramInput { db, diff --git a/crates/vm/db.rs b/crates/vm/db.rs index 5528ee6442..0dea54d462 100644 --- a/crates/vm/db.rs +++ b/crates/vm/db.rs @@ -1,10 +1,21 @@ -use ethrex_core::{types::BlockHash, Address as CoreAddress, H256 as CoreH256}; -use ethrex_storage::{error::StoreError, Store}; +use std::collections::HashMap; + +use ethrex_core::{ + types::{Block, BlockHash}, + Address as CoreAddress, H256 as CoreH256, +}; +use ethrex_storage::{error::StoreError, hash_address, hash_key, Store}; use revm::primitives::{ AccountInfo as RevmAccountInfo, Address as RevmAddress, Bytecode as RevmBytecode, Bytes as RevmBytes, B256 as RevmB256, U256 as RevmU256, }; +use crate::{ + errors::ExecutionDBError, + execution_db::{ExecutionDB, ToExecDB}, + spec_id, EvmError, +}; + pub struct StoreWrapper { pub store: Store, pub block_hash: BlockHash, @@ -162,3 +173,120 @@ impl revm::DatabaseRef for StoreWrapper { .ok_or_else(|| StoreError::Custom(format!("Block {number} not found"))) } } + +impl ToExecDB for StoreWrapper { + fn to_exec_db(&self, block: &Block) -> Result { + let parent_hash = block.header.parent_hash; + let chain_config = self.store.get_chain_config()?; + + // pre-execute and get all state changes + let cache = ExecutionDB::pre_execute( + block, + chain_config.chain_id, + spec_id(&chain_config, block.header.timestamp), + self, + ) + .map_err(|err| Box::new(EvmError::from(err)))?; // TODO: must be a better way + let store_wrapper = cache.db; + + // fetch all read/written values from store + let already_existing_accounts = cache + .accounts + .iter() + // filter out new accounts, we're only interested in already existing accounts. + // new accounts are storage cleared, self-destructed accounts too but they're marked with "not + // existing" status instead. + .filter_map(|(address, account)| { + if !account.account_state.is_storage_cleared() { + Some((CoreAddress::from(address.0.as_ref()), account)) + } else { + None + } + }); + let accounts = already_existing_accounts + .clone() + .map(|(address, _)| { + // return error if account is missing + let account = match store_wrapper + .store + .get_account_info_by_hash(parent_hash, address) + { + Ok(None) => Err(ExecutionDBError::NewMissingAccountInfo(address)), + Ok(Some(some)) => Ok(some), + Err(err) => Err(ExecutionDBError::Store(err)), + }; + Ok((address, account?)) + }) + .collect::, ExecutionDBError>>()?; + let code = already_existing_accounts + .clone() + .map(|(_, account)| { + // return error if code is missing + let hash = CoreH256::from(account.info.code_hash.0); + Ok(( + hash, + store_wrapper + .store + .get_account_code(hash)? + .ok_or(ExecutionDBError::NewMissingCode(hash))?, + )) + }) + .collect::>()?; + let storage = already_existing_accounts + .map(|(address, account)| { + // return error if storage is missing + Ok(( + address, + account + .storage + .keys() + .map(|key| { + let key = CoreH256::from(key.to_be_bytes()); + let value = store_wrapper + .store + .get_storage_at_hash(parent_hash, address, key) + .map_err(ExecutionDBError::Store)? + .ok_or(ExecutionDBError::NewMissingStorage(address, key))?; + Ok((key, value)) + }) + .collect::, ExecutionDBError>>()?, + )) + }) + .collect::, ExecutionDBError>>()?; + let block_hashes = cache + .block_hashes + .into_iter() + .map(|(num, hash)| (num.try_into().unwrap(), CoreH256::from(hash.0))) + .collect(); + // WARN: unwrapping because revm wraps a u64 as a U256 + + // get proofs + let state_trie = self + .store + .state_trie(parent_hash)? + .ok_or(ExecutionDBError::NewMissingStateTrie(parent_hash))?; + + let state_proofs = + state_trie.get_proofs(&accounts.keys().map(hash_address).collect::>())?; + + let mut storage_proofs = HashMap::new(); + for (address, storages) in &storage { + let storage_trie = self.store.storage_trie(parent_hash, *address)?.ok_or( + ExecutionDBError::NewMissingStorageTrie(parent_hash, *address), + )?; + + let paths = storages.keys().map(hash_key).collect::>(); + storage_proofs.insert(*address, storage_trie.get_proofs(&paths)?); + } + + Ok(ExecutionDB { + accounts, + code, + storage, + block_hashes, + chain_config, + state_proofs, + storage_proofs, + }) + } +} diff --git a/crates/vm/execution_db.rs b/crates/vm/execution_db.rs index 2bf2b8cafc..dedae16030 100644 --- a/crates/vm/execution_db.rs +++ b/crates/vm/execution_db.rs @@ -6,7 +6,7 @@ use ethrex_core::{ types::{AccountInfo, Block, ChainConfig}, Address, H256, U256, }; -use ethrex_storage::{hash_address, hash_key, AccountUpdate, Store}; +use ethrex_storage::{AccountUpdate, Store}; use ethrex_trie::{NodeRLP, Trie, TrieError}; use revm::{ db::CacheDB, @@ -21,8 +21,7 @@ use revm_primitives::SpecId; use serde::{Deserialize, Serialize}; use crate::{ - block_env, db::StoreWrapper, errors::ExecutionDBError, evm_state, execute_block, - get_state_transitions, spec_id, tx_env, EvmError, + block_env, errors::ExecutionDBError, evm_state, execute_block, get_state_transitions, tx_env, }; /// In-memory EVM database for single execution data. @@ -53,126 +52,6 @@ pub struct ExecutionDB { } impl ExecutionDB { - /// Creates a database and returns the ExecutionDB by "pre-executing" a block, - /// without performing any validation, and retrieving data from a [Store]. - pub fn from_store(block: &Block, store: Store) -> Result { - let parent_hash = block.header.parent_hash; - let chain_config = store.get_chain_config()?; - let store_wrapper = StoreWrapper { - store: store.clone(), - block_hash: parent_hash, - }; - - // pre-execute and get all state changes - let cache = Self::pre_execute( - block, - chain_config.chain_id, - spec_id(&chain_config, block.header.timestamp), - store_wrapper, - ) - .map_err(|err| Box::new(EvmError::from(err)))?; // TODO: must be a better way - let store_wrapper = cache.db; - - // fetch all read/written values from store - let already_existing_accounts = cache - .accounts - .iter() - // filter out new accounts, we're only interested in already existing accounts. - // new accounts are storage cleared, self-destructed accounts too but they're marked with "not - // existing" status instead. - .filter_map(|(address, account)| { - if !account.account_state.is_storage_cleared() { - Some((Address::from(address.0.as_ref()), account)) - } else { - None - } - }); - let accounts = already_existing_accounts - .clone() - .map(|(address, _)| { - // return error if account is missing - let account = match store_wrapper - .store - .get_account_info_by_hash(parent_hash, address) - { - Ok(None) => Err(ExecutionDBError::NewMissingAccountInfo(address)), - Ok(Some(some)) => Ok(some), - Err(err) => Err(ExecutionDBError::Store(err)), - }; - Ok((address, account?)) - }) - .collect::, ExecutionDBError>>()?; - let code = already_existing_accounts - .clone() - .map(|(_, account)| { - // return error if code is missing - let hash = H256::from(account.info.code_hash.0); - Ok(( - hash, - store_wrapper - .store - .get_account_code(hash)? - .ok_or(ExecutionDBError::NewMissingCode(hash))?, - )) - }) - .collect::>()?; - let storage = already_existing_accounts - .map(|(address, account)| { - // return error if storage is missing - Ok(( - address, - account - .storage - .keys() - .map(|key| { - let key = H256::from(key.to_be_bytes()); - let value = store_wrapper - .store - .get_storage_at_hash(parent_hash, address, key) - .map_err(ExecutionDBError::Store)? - .ok_or(ExecutionDBError::NewMissingStorage(address, key))?; - Ok((key, value)) - }) - .collect::, ExecutionDBError>>()?, - )) - }) - .collect::, ExecutionDBError>>()?; - let block_hashes = cache - .block_hashes - .into_iter() - .map(|(num, hash)| (num.try_into().unwrap(), H256::from(hash.0))) - .collect(); - // WARN: unwrapping because revm wraps a u64 as a U256 - - // get proofs - let state_trie = store - .state_trie(parent_hash)? - .ok_or(ExecutionDBError::NewMissingStateTrie(parent_hash))?; - - let state_proofs = - state_trie.get_proofs(&accounts.keys().map(hash_address).collect::>())?; - - let mut storage_proofs = HashMap::new(); - for (address, storages) in &storage { - let storage_trie = store.storage_trie(parent_hash, *address)?.ok_or( - ExecutionDBError::NewMissingStorageTrie(parent_hash, *address), - )?; - - let paths = storages.keys().map(hash_key).collect::>(); - storage_proofs.insert(*address, storage_trie.get_proofs(&paths)?); - } - - Ok(Self { - accounts, - code, - storage, - block_hashes, - chain_config, - state_proofs, - storage_proofs, - }) - } - /// Gets the Vec<[AccountUpdate]>/StateTransitions obtained after executing a block. pub fn get_account_updates( block: &Block, @@ -211,7 +90,7 @@ impl ExecutionDB { } /// Execute a block and cache all state changes, returns the cache - fn pre_execute( + pub fn pre_execute( block: &Block, chain_id: u64, spec_id: SpecId, @@ -294,3 +173,9 @@ impl DatabaseRef for ExecutionDB { .ok_or(ExecutionDBError::BlockHashNotFound(number)) } } + +/// Creates an [ExecutionDB] from an initial database and a block to execute, usually via +/// pre-execution. +pub trait ToExecDB { + fn to_exec_db(&self, block: &Block) -> Result; +}