diff --git a/Cargo.lock b/Cargo.lock index f50fc3e6..3f0a58a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,6 +792,25 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "bincode_derive", + "serde", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -3624,8 +3643,10 @@ version = "2.0.0" dependencies = [ "alloy", "anyhow", + "bincode", "clap", "revm", + "rkyv", "sbv", "serde", "serde_json", @@ -3635,6 +3656,7 @@ dependencies = [ "tokio-retry", "tracing-subscriber", "url", + "zktrie-ng", ] [[package]] @@ -4220,6 +4242,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -4540,9 +4568,10 @@ dependencies = [ [[package]] name = "zktrie-ng" version = "0.1.0" -source = "git+https://github.com/scroll-tech/zktrie-ng?branch=master#300a47436f6ba51f4b0ffad8a472279b9a6de8ba" +source = "git+https://github.com/scroll-tech/zktrie-ng?branch=feat/keccak#7ff68af7b91b58579126bb7cb2ac82c0d5821c66" dependencies = [ "alloy-primitives", + "clap", "hashbrown 0.14.5", "hex", "num-derive", @@ -4551,8 +4580,10 @@ dependencies = [ "poseidon-bn254", "revm-primitives", "rkyv", + "serde", "sled", "strum", "thiserror", + "tiny-keccak", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index c4e98173..63b46002 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,11 +21,12 @@ tiny-keccak = "2.0" # dependencies from scroll-tech poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", branch = "master", features = ["bn254"] } -zktrie-ng = { git = "https://github.com/scroll-tech/zktrie-ng", branch = "master", features = ["scroll"] } +zktrie-ng = { git = "https://github.com/scroll-tech/zktrie-ng", branch = "feat/keccak", features = ["scroll"] } # binary dependencies anyhow = "1.0" async-channel = "2.2" +bincode = { version = "=2.0.0-rc.3", features = ["serde"] } clap = "4" env_logger = "0.9" futures = "0.3" @@ -102,9 +103,9 @@ alloy-primitives = { git = "https://github.com/scroll-tech/alloy-core", branch = alloy-sol-types = {git = "https://github.com/scroll-tech/alloy-core", branch = "v0.8.10" } # for local development -# [patch."https://github.com/scroll-tech/revm"] -# revm = { path = "../revm/crates/revm" } -# revm-primitives = { path = "../revm/crates/primitives" } +#[patch."https://github.com/scroll-tech/revm"] +#revm = { path = "../revm/crates/revm" } +#revm-primitives = { path = "../revm/crates/primitives" } #[profile.release] #debug-assertions = true \ No newline at end of file diff --git a/crates/bin/src/commands/run_file.rs b/crates/bin/src/commands/run_file.rs index 15208e21..bc18a052 100644 --- a/crates/bin/src/commands/run_file.rs +++ b/crates/bin/src/commands/run_file.rs @@ -1,18 +1,20 @@ use crate::utils; use anyhow::{anyhow, bail}; use clap::Args; +use sbv::primitives::zk_trie::hash::keccak::Keccak; +use sbv::primitives::zk_trie::hash::HashSchemeKind; use sbv::{ core::{BlockExecutionResult, ChunkInfo, EvmExecutorBuilder, HardforkConfig}, primitives::{ types::{BlockTrace, LegacyStorageTrace}, - zk_trie::db::kv::HashMapDb, + zk_trie::{db::kv::HashMapDb, hash::poseidon::Poseidon}, Block, B256, }, }; use serde::Deserialize; use std::panic::catch_unwind; use std::path::PathBuf; -use tiny_keccak::{Hasher, Keccak}; +use tiny_keccak::Hasher; use tokio::task::JoinSet; #[derive(Args)] @@ -23,6 +25,9 @@ pub struct RunFileCommand { /// Chunk mode #[arg(short, long)] chunk_mode: bool, + /// Hash scheme + #[arg(long, value_enum, default_value_t = HashSchemeKind::Poseidon)] + hash_scheme: HashSchemeKind, } impl RunFileCommand { @@ -44,7 +49,7 @@ impl RunFileCommand { let mut tasks = JoinSet::new(); for path in self.path.into_iter() { - tasks.spawn(run_trace(path, fork_config)); + tasks.spawn(run_trace(path, fork_config, self.hash_scheme)); } while let Some(task) = tasks.join_next().await { @@ -80,31 +85,51 @@ impl RunFileCommand { } let fork_config = fork_config(traces[0].chain_id()); - let (chunk_info, mut zktrie_db) = ChunkInfo::from_block_traces(&traces); + let (chunk_info, mut zktrie_db) = ChunkInfo::from_block_traces(&traces, self.hash_scheme); let mut code_db = HashMapDb::default(); - let mut tx_bytes_hasher = Keccak::v256(); + let mut tx_bytes_hasher = tiny_keccak::Keccak::v256(); - let mut executor = EvmExecutorBuilder::new(&mut code_db, &mut zktrie_db) + let builder = EvmExecutorBuilder::new(&mut code_db, &mut zktrie_db) .hardfork_config(fork_config) - .chain_id(traces[0].chain_id()) - .build(traces[0].root_before())?; - for trace in traces.iter() { - executor.insert_codes(trace)?; - } - - for trace in traces.iter() { - let BlockExecutionResult { tx_rlps, .. } = executor.handle_block(trace)?; - for tx_rlp in tx_rlps { - tx_bytes_hasher.update(&tx_rlp); + .chain_id(traces[0].chain_id()); + let post_state_root = match self.hash_scheme { + HashSchemeKind::Poseidon => { + let mut executor = builder + .hash_scheme(Poseidon) + .build(traces[0].root_before())?; + for trace in traces.iter() { + executor.insert_codes(trace)?; + } + + for trace in traces.iter() { + let BlockExecutionResult { tx_rlps, .. } = executor.handle_block(trace)?; + for tx_rlp in tx_rlps { + tx_bytes_hasher.update(&tx_rlp); + } + } + + executor.commit_changes()? } - } - - let post_state_root = executor.commit_changes()?; + HashSchemeKind::Keccak => { + let mut executor = builder.hash_scheme(Keccak).build(traces[0].root_before())?; + for trace in traces.iter() { + executor.insert_codes(trace)?; + } + + for trace in traces.iter() { + let BlockExecutionResult { tx_rlps, .. } = executor.handle_block(trace)?; + for tx_rlp in tx_rlps { + tx_bytes_hasher.update(&tx_rlp); + } + } + + executor.commit_changes()? + } + }; if post_state_root != chunk_info.post_state_root() { bail!("post state root mismatch"); } - drop(executor); let mut tx_bytes_hash = B256::ZERO; tx_bytes_hasher.finalize(&mut tx_bytes_hash.0); @@ -148,22 +173,24 @@ fn deserialize_may_wrapped<'de, T: Deserialize<'de>>(trace: &'de str) -> anyhow: async fn run_trace( path: PathBuf, fork_config: impl Fn(u64) -> HardforkConfig, + hash_scheme: HashSchemeKind, ) -> anyhow::Result<()> { let trace = read_block_trace(&path).await?; let fork_config = fork_config(trace.chain_id()); - if let Err(e) = - tokio::task::spawn_blocking(move || catch_unwind(|| utils::verify(&trace, &fork_config))) - .await? - .map_err(|e| { - e.downcast_ref::<&str>() + if let Err(e) = tokio::task::spawn_blocking(move || { + catch_unwind(|| utils::verify(&trace, &fork_config, hash_scheme)) + }) + .await? + .map_err(|e| { + e.downcast_ref::<&str>() + .map(|s| anyhow!("task panics with: {s}")) + .or_else(|| { + e.downcast_ref::() .map(|s| anyhow!("task panics with: {s}")) - .or_else(|| { - e.downcast_ref::() - .map(|s| anyhow!("task panics with: {s}")) - }) - .unwrap_or_else(|| anyhow!("task panics")) }) - .and_then(|r| r.map_err(anyhow::Error::from)) + .unwrap_or_else(|| anyhow!("task panics")) + }) + .and_then(|r| r.map_err(anyhow::Error::from)) { dev_error!( "Error occurs when verifying block ({}): {:?}", diff --git a/crates/bin/src/commands/run_rpc.rs b/crates/bin/src/commands/run_rpc.rs index ec4ff3e8..c79dbb98 100644 --- a/crates/bin/src/commands/run_rpc.rs +++ b/crates/bin/src/commands/run_rpc.rs @@ -3,6 +3,7 @@ use alloy::providers::{Provider, ProviderBuilder}; use clap::Args; use futures::future::OptionFuture; use sbv::primitives::types::LegacyStorageTrace; +use sbv::primitives::zk_trie::hash::HashSchemeKind; use sbv::{ core::HardforkConfig, primitives::{types::BlockTrace, Block}, @@ -115,10 +116,12 @@ impl RunRpcCommand { l2_trace.block_hash() ); - tokio::task::spawn_blocking(move || utils::verify(&l2_trace, &fork_config)) - .await - .expect("failed to spawn blocking task") - .map_err(|e| (block_number, e.into()))?; + tokio::task::spawn_blocking(move || { + utils::verify(&l2_trace, &fork_config, HashSchemeKind::Poseidon) + }) + .await + .expect("failed to spawn blocking task") + .map_err(|e| (block_number, e.into()))?; } Ok::<_, (u64, anyhow::Error)>(()) }); diff --git a/crates/bin/src/utils.rs b/crates/bin/src/utils.rs index 6bc12316..0d1fe884 100644 --- a/crates/bin/src/utils.rs +++ b/crates/bin/src/utils.rs @@ -1,22 +1,26 @@ use sbv::primitives::zk_trie::db::NodeDb; +use sbv::primitives::zk_trie::hash::keccak::Keccak; +use sbv::primitives::zk_trie::hash::HashSchemeKind; use sbv::{ core::{EvmExecutorBuilder, HardforkConfig, VerificationError}, - primitives::{zk_trie::db::kv::HashMapDb, Block}, + primitives::{zk_trie::db::kv::HashMapDb, zk_trie::hash::poseidon::Poseidon, Block}, }; pub fn verify( l2_trace: T, fork_config: &HardforkConfig, + hash_scheme: HashSchemeKind, ) -> Result<(), VerificationError> { measure_duration_millis!( total_block_verification_duration_milliseconds, - verify_inner(l2_trace, fork_config) + verify_inner(l2_trace, fork_config, hash_scheme) ) } fn verify_inner( l2_trace: T, fork_config: &HardforkConfig, + hash_scheme: HashSchemeKind, ) -> Result<(), VerificationError> { dev_trace!("{l2_trace:#?}"); let root_before = l2_trace.root_before(); @@ -42,7 +46,9 @@ fn verify_inner( let mut zktrie_db = NodeDb::new(HashMapDb::default()); measure_duration_millis!( build_zktrie_db_duration_milliseconds, - l2_trace.build_zktrie_db(&mut zktrie_db).unwrap() + l2_trace + .build_zktrie_db(&mut zktrie_db, hash_scheme) + .unwrap() ); zktrie_db }, @@ -50,27 +56,52 @@ fn verify_inner( ); let mut code_db = HashMapDb::default(); - let mut executor = EvmExecutorBuilder::new(&mut code_db, &mut zktrie_db) + let builder = EvmExecutorBuilder::new(&mut code_db, &mut zktrie_db) .hardfork_config(*fork_config) - .chain_id(l2_trace.chain_id()) - .build(root_before)?; + .chain_id(l2_trace.chain_id()); - executor.insert_codes(&l2_trace)?; + let revm_root_after = match hash_scheme { + HashSchemeKind::Poseidon => { + let mut executor = builder.hash_scheme(Poseidon).build(root_before)?; - // TODO: change to Result::inspect_err when sp1 toolchain >= 1.76 - #[allow(clippy::map_identity)] - #[allow(clippy::manual_inspect)] - executor.handle_block(&l2_trace).map_err(|e| { - dev_error!( - "Error occurs when executing block #{}({:?}): {e:?}", - l2_trace.number(), - l2_trace.block_hash() - ); + executor.insert_codes(&l2_trace)?; - update_metrics_counter!(verification_error); - e - })?; - let revm_root_after = executor.commit_changes()?; + // TODO: change to Result::inspect_err when sp1 toolchain >= 1.76 + #[allow(clippy::map_identity)] + #[allow(clippy::manual_inspect)] + executor.handle_block(&l2_trace).map_err(|e| { + dev_error!( + "Error occurs when executing block #{}({:?}): {e:?}", + l2_trace.number(), + l2_trace.block_hash() + ); + + update_metrics_counter!(verification_error); + e + })?; + executor.commit_changes()? + } + HashSchemeKind::Keccak => { + let mut executor = builder.hash_scheme(Keccak).build(root_before)?; + + executor.insert_codes(&l2_trace)?; + + // TODO: change to Result::inspect_err when sp1 toolchain >= 1.76 + #[allow(clippy::map_identity)] + #[allow(clippy::manual_inspect)] + executor.handle_block(&l2_trace).map_err(|e| { + dev_error!( + "Error occurs when executing block #{}({:?}): {e:?}", + l2_trace.number(), + l2_trace.block_hash() + ); + + update_metrics_counter!(verification_error); + e + })?; + executor.commit_changes()? + } + }; #[cfg(feature = "profiling")] if let Ok(report) = guard.report().build() { diff --git a/crates/core/src/chunk.rs b/crates/core/src/chunk.rs index 3847200e..f845f779 100644 --- a/crates/core/src/chunk.rs +++ b/crates/core/src/chunk.rs @@ -1,5 +1,6 @@ use revm::primitives::B256; use sbv_primitives::zk_trie::db::NodeDb; +use sbv_primitives::zk_trie::hash::HashSchemeKind; use sbv_primitives::{zk_trie::db::kv::HashMapDb, Block}; use tiny_keccak::{Hasher, Keccak}; @@ -22,7 +23,10 @@ pub struct ChunkInfo { impl ChunkInfo { /// Construct by block traces - pub fn from_block_traces(traces: &[T]) -> (Self, NodeDb) { + pub fn from_block_traces( + traces: &[T], + hash_scheme: HashSchemeKind, + ) -> (Self, NodeDb) { let chain_id = traces.first().unwrap().chain_id(); let prev_state_root = traces .first() @@ -48,7 +52,7 @@ impl ChunkInfo { for trace in traces.iter() { measure_duration_millis!( build_zktrie_db_duration_milliseconds, - trace.build_zktrie_db(&mut zktrie_db).unwrap() + trace.build_zktrie_db(&mut zktrie_db, hash_scheme).unwrap() ); } cycle_tracker_end!("Block::build_zktrie_db"); diff --git a/crates/core/src/database.rs b/crates/core/src/database.rs index b7cbcfc6..17c17f7f 100644 --- a/crates/core/src/database.rs +++ b/crates/core/src/database.rs @@ -10,7 +10,7 @@ use sbv_primitives::{ kv::{KVDatabase, KVDatabaseItem}, NodeDb, }, - hash::{key_hasher::NoCacheHasher, poseidon::Poseidon, ZkHash}, + hash::{key_hasher::NoCacheHasher, HashScheme, ZkHash}, scroll_types::Account, trie::ZkTrie, }, @@ -21,7 +21,7 @@ use std::{cell::RefCell, collections::HashMap, fmt}; type Result = std::result::Result; /// A database that consists of account and storage information. -pub struct EvmDatabase<'a, CodeDb, ZkDb> { +pub struct EvmDatabase<'a, CodeDb, ZkDb, H> { /// Map of code hash to bytecode. pub(crate) code_db: &'a mut CodeDb, /// Cache of analyzed code @@ -30,16 +30,16 @@ pub struct EvmDatabase<'a, CodeDb, ZkDb> { storage_root_caches: RefCell>, /// Storage trie cache, avoid re-creating trie for the same account. /// Need to invalidate before `update`, otherwise the trie root may be outdated - storage_trie_caches: RefCell>>>, + storage_trie_caches: RefCell>>>, /// Current uncommitted zkTrie root based on the block trace. committed_zktrie_root: B256, /// The underlying zkTrie database. pub(crate) zktrie_db: &'a mut NodeDb, /// Current view of zkTrie database. - zktrie: ZkTrie, + zktrie: ZkTrie, } -impl fmt::Debug for EvmDatabase<'_, CodeDb, Db> { +impl fmt::Debug for EvmDatabase<'_, CodeDb, Db, HashScheme> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EvmDatabase") .field("committed_zktrie_root", &self.committed_zktrie_root) @@ -47,7 +47,9 @@ impl fmt::Debug for EvmDatabase<'_, CodeDb, Db> { } } -impl<'a, CodeDb: KVDatabase, ZkDb: KVDatabase + 'static> EvmDatabase<'a, CodeDb, ZkDb> { +impl<'a, CodeDb: KVDatabase, ZkDb: KVDatabase + 'static, H: HashScheme> + EvmDatabase<'a, CodeDb, ZkDb, H> +{ /// Initialize an EVM database from a zkTrie root. pub fn new_from_root( committed_zktrie_root: B256, @@ -79,7 +81,7 @@ impl<'a, CodeDb: KVDatabase, ZkDb: KVDatabase + 'static> EvmDatabase<'a, CodeDb, } #[inline] - pub(crate) fn update_storage_root_cache(&self, address: Address, storage_root: ZkTrie) { + pub(crate) fn update_storage_root_cache(&self, address: Address, storage_root: ZkTrie) { let new_root = *storage_root.root().unwrap_ref(); let old = self .storage_root_caches @@ -142,7 +144,9 @@ impl<'a, CodeDb: KVDatabase, ZkDb: KVDatabase + 'static> EvmDatabase<'a, CodeDb, } } -impl DatabaseRef for EvmDatabase<'_, CodeDb, ZkDb> { +impl DatabaseRef + for EvmDatabase<'_, CodeDb, ZkDb, H> +{ type Error = DatabaseError; /// Get basic account information. diff --git a/crates/core/src/executor/builder.rs b/crates/core/src/executor/builder.rs index 3214634f..391a546e 100644 --- a/crates/core/src/executor/builder.rs +++ b/crates/core/src/executor/builder.rs @@ -1,30 +1,39 @@ use crate::{error::DatabaseError, EvmDatabase, EvmExecutor, HardforkConfig}; use revm::db::CacheDB; use sbv_primitives::{ - alloy_primitives::ChainId, zk_trie::db::kv::KVDatabase, zk_trie::db::NodeDb, B256, + alloy_primitives::ChainId, + zk_trie::{ + db::{kv::KVDatabase, NodeDb}, + hash::HashScheme, + }, + B256, }; use std::fmt::{self, Debug}; /// Builder for EVM executor. -pub struct EvmExecutorBuilder<'a, H, C, CodeDb, ZkDb> { - hardfork_config: H, +pub struct EvmExecutorBuilder<'a, HC, C, CodeDb, ZkDb, H> { + hardfork_config: HC, chain_id: C, code_db: &'a mut CodeDb, zktrie_db: &'a mut NodeDb, + hash_scheme: H, } -impl Debug for EvmExecutorBuilder<'_, H, C, CodeDb, ZkDb> { +impl Debug + for EvmExecutorBuilder<'_, HC, C, CodeDb, ZkDb, H> +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EvmExecutorBuilder") .field("hardfork_config", &self.hardfork_config) .field("chain_id", &self.chain_id) .field("code_db", &"...") .field("zktrie_db", &"...") + .field("hash_scheme", &self.hash_scheme) .finish() } } -impl<'a, CodeDb, ZkDb> EvmExecutorBuilder<'a, (), (), CodeDb, ZkDb> { +impl<'a, CodeDb, ZkDb> EvmExecutorBuilder<'a, (), (), CodeDb, ZkDb, ()> { /// Create a new builder. pub fn new(code_db: &'a mut CodeDb, zktrie_db: &'a mut NodeDb) -> Self { Self { @@ -32,31 +41,34 @@ impl<'a, CodeDb, ZkDb> EvmExecutorBuilder<'a, (), (), CodeDb, ZkDb> { chain_id: (), code_db, zktrie_db, + hash_scheme: (), } } } -impl<'a, H, C, CodeDb, ZkDb> EvmExecutorBuilder<'a, H, C, CodeDb, ZkDb> { +impl<'a, HC, C, CodeDb, ZkDb, H> EvmExecutorBuilder<'a, HC, C, CodeDb, ZkDb, H> { /// Set hardfork config. pub fn hardfork_config

( self, hardfork_config: H1, - ) -> EvmExecutorBuilder<'a, H1, C, CodeDb, ZkDb> { + ) -> EvmExecutorBuilder<'a, H1, C, CodeDb, ZkDb, H> { EvmExecutorBuilder { hardfork_config, chain_id: self.chain_id, code_db: self.code_db, zktrie_db: self.zktrie_db, + hash_scheme: self.hash_scheme, } } /// Set chain id. - pub fn chain_id(self, chain_id: C1) -> EvmExecutorBuilder<'a, H, C1, CodeDb, ZkDb> { + pub fn chain_id(self, chain_id: C1) -> EvmExecutorBuilder<'a, HC, C1, CodeDb, ZkDb, H> { EvmExecutorBuilder { hardfork_config: self.hardfork_config, chain_id, code_db: self.code_db, zktrie_db: self.zktrie_db, + hash_scheme: self.hash_scheme, } } @@ -64,12 +76,13 @@ impl<'a, H, C, CodeDb, ZkDb> EvmExecutorBuilder<'a, H, C, CodeDb, ZkDb> { pub fn code_db( self, code_db: &'a mut CodeDb1, - ) -> EvmExecutorBuilder<'a, H, C, CodeDb1, ZkDb> { + ) -> EvmExecutorBuilder<'a, HC, C, CodeDb1, ZkDb, H> { EvmExecutorBuilder { hardfork_config: self.hardfork_config, chain_id: self.chain_id, code_db, zktrie_db: self.zktrie_db, + hash_scheme: self.hash_scheme, } } @@ -77,21 +90,36 @@ impl<'a, H, C, CodeDb, ZkDb> EvmExecutorBuilder<'a, H, C, CodeDb, ZkDb> { pub fn zktrie_db( self, zktrie_db: &'a mut NodeDb, - ) -> EvmExecutorBuilder { + ) -> EvmExecutorBuilder { EvmExecutorBuilder { hardfork_config: self.hardfork_config, chain_id: self.chain_id, code_db: self.code_db, zktrie_db, + hash_scheme: self.hash_scheme, + } + } + + /// Set hash scheme. + pub fn hash_scheme

( + self, + hash_scheme: H1, + ) -> EvmExecutorBuilder<'a, HC, C, CodeDb, ZkDb, H1> { + EvmExecutorBuilder { + hardfork_config: self.hardfork_config, + chain_id: self.chain_id, + code_db: self.code_db, + zktrie_db: self.zktrie_db, + hash_scheme, } } } -impl<'a, CodeDb: KVDatabase, ZkDb: KVDatabase + 'static> - EvmExecutorBuilder<'a, HardforkConfig, ChainId, CodeDb, ZkDb> +impl<'a, CodeDb: KVDatabase, ZkDb: KVDatabase + 'static, H: HashScheme> + EvmExecutorBuilder<'a, HardforkConfig, ChainId, CodeDb, ZkDb, H> { /// Initialize an EVM executor from a block trace as the initial state. - pub fn build(self, root: B256) -> Result, DatabaseError> { + pub fn build(self, root: B256) -> Result, DatabaseError> { let db = cycle_track!( CacheDB::new(EvmDatabase::new_from_root( root, diff --git a/crates/core/src/executor/mod.rs b/crates/core/src/executor/mod.rs index 23d5ef80..b1f99b36 100644 --- a/crates/core/src/executor/mod.rs +++ b/crates/core/src/executor/mod.rs @@ -12,7 +12,7 @@ use sbv_primitives::{ alloy_primitives::Bytes, zk_trie::{ db::kv::KVDatabase, - hash::{key_hasher::NoCacheHasher, poseidon::Poseidon}, + hash::{key_hasher::NoCacheHasher, HashScheme}, scroll_types::Account, trie::ZkTrie, }, @@ -24,10 +24,10 @@ mod builder; pub use builder::EvmExecutorBuilder; /// EVM executor that handles the block. -pub struct EvmExecutor<'db, CodeDb, ZkDb> { +pub struct EvmExecutor<'db, CodeDb, ZkDb, H> { chain_id: ChainId, hardfork_config: HardforkConfig, - db: CacheDB>, + db: CacheDB>, } /// Block execution result @@ -39,9 +39,11 @@ pub struct BlockExecutionResult { pub tx_rlps: Vec, } -impl EvmExecutor<'_, CodeDb, ZkDb> { +impl + EvmExecutor<'_, CodeDb, ZkDb, H> +{ /// Get reference to the DB - pub fn db(&self) -> &CacheDB> { + pub fn db(&self) -> &CacheDB> { &self.db } @@ -190,7 +192,7 @@ impl EvmExecutor<'_, CodeDb, ZkD } fn commit_changes_inner(&mut self) -> Result { - let mut zktrie = ZkTrie::::new_with_root( + let mut zktrie = ZkTrie::::new_with_root( self.db.db.zktrie_db, NoCacheHasher, self.db.db.committed_zktrie_root(), @@ -245,7 +247,7 @@ impl EvmExecutor<'_, CodeDb, ZkD // get storage tire cycle_tracker_start!("update storage_tire"); let mut storage_trie = cycle_track!( - ZkTrie::::new_with_root( + ZkTrie::::new_with_root( self.db.db.zktrie_db, NoCacheHasher, storage_root_before, @@ -357,7 +359,7 @@ impl EvmExecutor<'_, CodeDb, ZkD } } -impl Debug for EvmExecutor<'_, CodeDb, ZkDb> { +impl Debug for EvmExecutor<'_, CodeDb, ZkDb, H> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("EvmExecutor").field("db", &self.db).finish() } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 69ba30a2..5f207622 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -10,6 +10,7 @@ use alloy::{ }; use std::fmt::Debug; use zktrie_ng::db::kv::KVDatabase; +use zktrie_ng::db::NodeDb; /// Predeployed contracts pub mod predeployed; @@ -21,12 +22,16 @@ pub use alloy::consensus::Transaction; pub use alloy::primitives as alloy_primitives; pub use alloy::primitives::{Address, B256, U256}; pub use zktrie_ng as zk_trie; -use zktrie_ng::db::NodeDb; +use zktrie_ng::hash::HashSchemeKind; /// Node proof trait pub trait NodeProof { /// Import itself into zktrie db - fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error>; + fn import_node( + &self, + db: &mut NodeDb, + hash_scheme: HashSchemeKind, + ) -> Result<(), Db::Error>; } /// Blanket trait for block trace extensions. @@ -88,9 +93,13 @@ pub trait Block: Debug { /// Update zktrie state from trace #[inline] - fn build_zktrie_db(&self, db: &mut NodeDb) -> Result<(), Db::Error> { + fn build_zktrie_db( + &self, + db: &mut NodeDb, + hash_scheme: HashSchemeKind, + ) -> Result<(), Db::Error> { for node in self.node_proofs() { - node.import_node(db)?; + node.import_node(db, hash_scheme)?; } Ok(()) } @@ -166,10 +175,10 @@ pub trait TxTrace { fn gas_price(&self) -> u128; /// Get `max_fee_per_gas` - fn max_fee_per_gas(&self) -> u128; + fn max_fee_per_gas(&self) -> Option; /// Get `max_priority_fee_per_gas` - fn max_priority_fee_per_gas(&self) -> u128; + fn max_priority_fee_per_gas(&self) -> Option; /// Get `from` without checking /// @@ -238,8 +247,8 @@ pub trait TxTrace { let tx = TxEip1559 { chain_id: self.chain_id().unwrap(), nonce: self.nonce(), - max_fee_per_gas: self.max_fee_per_gas(), - max_priority_fee_per_gas: self.max_priority_fee_per_gas(), + max_fee_per_gas: self.max_fee_per_gas().unwrap_or_default(), + max_priority_fee_per_gas: self.max_priority_fee_per_gas().unwrap_or_default(), gas_limit: self.gas_limit(), to: self.to(), value: self.value(), @@ -367,11 +376,11 @@ impl TxTrace for &T { (*self).gas_price() } - fn max_fee_per_gas(&self) -> u128 { + fn max_fee_per_gas(&self) -> Option { (*self).max_fee_per_gas() } - fn max_priority_fee_per_gas(&self) -> u128 { + fn max_priority_fee_per_gas(&self) -> Option { (*self).max_priority_fee_per_gas() } diff --git a/crates/primitives/src/types/mod.rs b/crates/primitives/src/types/mod.rs index 11b74627..a25ce2a5 100644 --- a/crates/primitives/src/types/mod.rs +++ b/crates/primitives/src/types/mod.rs @@ -1,4 +1,4 @@ -use crate::{Block, NodeProof}; +use crate::{Block, NodeProof, TxTrace}; use alloy::primitives::{Address, Bytes, B256, U256}; use rkyv::vec::ArchivedVec; use rkyv::{rancor, Archive}; @@ -9,10 +9,13 @@ use std::fmt::Debug; use std::hash::{Hash, Hasher}; use zktrie_ng::db::kv::KVDatabase; use zktrie_ng::db::NodeDb; +use zktrie_ng::hash::keccak::Keccak; use zktrie_ng::hash::poseidon::Poseidon; +use zktrie_ng::hash::HashSchemeKind; use zktrie_ng::trie::{ArchivedNode, Node, MAGIC_NODE_BYTES}; mod tx; +use crate::alloy_primitives::{TxKind, U64}; pub use tx::{ AlloyTransaction, ArchivedTransactionTrace, TransactionTrace, TxL1Msg, TypedTransaction, }; @@ -198,6 +201,65 @@ where pub withdraw_trie_root: B256, } +impl BlockTrace { + /// Create a new block trace from alloy block + pub fn new_from_alloy( + chain_id: u64, + codes: Vec, + storage_trace: StorageTrace, + block: &alloy::rpc::types::Block, + ) -> BlockTrace { + BlockTrace { + chain_id, + coinbase: Coinbase { + address: block.coinbase(), + }, + header: BlockHeader { + number: U256::from(block.number()), + hash: block.block_hash(), + timestamp: block.timestamp(), + gas_limit: block.gas_limit(), + gas_used: block.gas_used(), + base_fee_per_gas: block.base_fee_per_gas(), + difficulty: block.difficulty(), + mix_hash: block.prevrandao(), + }, + transactions: block + .transactions() + .map(|tx| { + let sig = tx.signature().unwrap(); + TransactionTrace { + tx_hash: tx.tx_hash(), + ty: tx.ty(), + nonce: tx.nonce(), + gas: tx.gas_limit(), + gas_price: U256::from(tx.gas_price()), + gas_tip_cap: tx.max_priority_fee_per_gas().map(U256::from), + gas_fee_cap: tx.max_fee_per_gas().map(U256::from), + from: unsafe { tx.get_from_unchecked() }, + to: match tx.to() { + TxKind::Call(to) => Some(to), + TxKind::Create => None, + }, + chain_id: U64::from(chain_id), + value: tx.value(), + data: tx.data(), + is_create: tx.to().is_create(), + access_list: tx.access_list(), + v: U64::from(sig.v().to_u64()), + r: sig.r(), + s: sig.s(), + } + }) + .collect(), + codes, + storage_trace, + start_l1_queue_index: 0, + withdraw_trie_root: Default::default(), + } + } +} + impl Hash for ArchivedNodeBytes { fn hash(&self, state: &mut H) { state.write_usize(self.0.len()); @@ -258,22 +320,27 @@ impl<'de> Deserialize<'de> for StorageTrace { } } -impl From for StorageTrace { - fn from(trace: StorageTrace) -> Self { +impl StorageTrace { + /// turn the proofs to archived + fn to_archived(self, hash_scheme: HashSchemeKind) -> StorageTrace { StorageTrace { - root_before: trace.root_before, - root_after: trace.root_after, - flatten_proofs: trace + root_before: self.root_before, + root_after: self.root_after, + flatten_proofs: self .flatten_proofs .into_iter() .filter(|proof| proof.as_ref() != MAGIC_NODE_BYTES) .map(|proof| { - ArchivedNodeBytes( - Node::::try_from(proof.as_ref()) + ArchivedNodeBytes(match hash_scheme { + HashSchemeKind::Poseidon => Node::::try_from(proof.as_ref()) .expect("invalid node") .archived() .to_vec(), - ) + HashSchemeKind::Keccak => Node::::try_from(proof.as_ref()) + .expect("invalid node") + .archived() + .to_vec(), + }) }) .collect(), } @@ -301,17 +368,21 @@ impl From for StorageTrace { } } -impl From for BlockTrace> { - fn from(trace: BlockTrace) -> Self { +impl BlockTrace { + /// turn the proofs to archived + pub fn to_archived_nodes( + self: BlockTrace, + hash_scheme: HashSchemeKind, + ) -> BlockTrace> { BlockTrace { - chain_id: trace.chain_id, - coinbase: trace.coinbase, - header: trace.header, - transactions: trace.transactions, - codes: trace.codes, - storage_trace: trace.storage_trace.into(), - start_l1_queue_index: trace.start_l1_queue_index, - withdraw_trie_root: trace.withdraw_trie_root, + chain_id: self.chain_id, + coinbase: self.coinbase, + header: self.header, + transactions: self.transactions, + codes: self.codes, + storage_trace: self.storage_trace.to_archived(hash_scheme), + start_l1_queue_index: self.start_l1_queue_index, + withdraw_trie_root: self.withdraw_trie_root, } } } @@ -569,25 +640,43 @@ impl StorageTraceExt for LegacyStorageTrace { fn import_serialized_node, Db: KVDatabase>( node: N, db: &mut NodeDb, + hash_scheme: HashSchemeKind, ) -> Result<(), Db::Error> { let bytes = node.as_ref(); if bytes == MAGIC_NODE_BYTES { return Ok(()); } - let node = - cycle_track!(Node::::try_from(bytes), "Node::try_from").expect("invalid node"); - cycle_track!( - node.get_or_calculate_node_hash(), - "Node::get_or_calculate_node_hash" - ) - .expect("infallible"); - dev_trace!("put zktrie node: {:?}", node); - cycle_track!(db.put_node(node), "NodeDb::put_node") + match hash_scheme { + HashSchemeKind::Poseidon => { + let node = cycle_track!(Node::::try_from(bytes), "Node::try_from") + .expect("invalid node"); + cycle_track!( + node.get_or_calculate_node_hash(), + "Node::get_or_calculate_node_hash" + ) + .expect("infallible"); + dev_trace!("put zktrie node: {:?}", node); + cycle_track!(db.put_node(node), "NodeDb::put_node") + } + HashSchemeKind::Keccak => { + let node = cycle_track!(Node::::try_from(bytes), "Node::try_from") + .expect("invalid node"); + + cycle_track!( + node.get_or_calculate_node_hash(), + "Node::get_or_calculate_node_hash" + ) + .expect("infallible"); + dev_trace!("put zktrie node: {:?}", node); + cycle_track!(db.put_node(node), "NodeDb::put_node") + } + } } fn import_archived_node, Db: KVDatabase>( node: N, db: &mut NodeDb, + hash_scheme: HashSchemeKind, ) -> Result<(), Db::Error> { let bytes = node.as_ref(); let node = cycle_track!( @@ -595,11 +684,18 @@ fn import_archived_node, Db: KVDatabase>( "rkyv::access" ) .expect("invalid node"); - let node_hash = cycle_track!( - node.calculate_node_hash::(), - "Node::calculate_node_hash" - ) - .expect("infallible"); + let node_hash = match hash_scheme { + HashSchemeKind::Poseidon => cycle_track!( + node.calculate_node_hash::(), + "Node::calculate_node_hash" + ) + .expect("infallible"), + HashSchemeKind::Keccak => cycle_track!( + node.calculate_node_hash::(), + "Node::calculate_node_hash" + ) + .expect("infallible"), + }; dev_trace!("put zktrie node: {:?}", node); cycle_track!( unsafe { db.put_archived_node_unchecked(node_hash, bytes.to_owned()) }, @@ -608,26 +704,42 @@ fn import_archived_node, Db: KVDatabase>( } impl NodeProof for Bytes { - fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error> { - import_serialized_node(self, db) + fn import_node( + &self, + db: &mut NodeDb, + hash_scheme: HashSchemeKind, + ) -> Result<(), Db::Error> { + import_serialized_node(self, db, hash_scheme) } } impl NodeProof for ArchivedVec { - fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error> { - import_serialized_node(self, db) + fn import_node( + &self, + db: &mut NodeDb, + hash_scheme: HashSchemeKind, + ) -> Result<(), Db::Error> { + import_serialized_node(self, db, hash_scheme) } } impl NodeProof for ArchivedNodeBytes { - fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error> { - import_archived_node(&self.0, db) + fn import_node( + &self, + db: &mut NodeDb, + hash_scheme: HashSchemeKind, + ) -> Result<(), Db::Error> { + import_archived_node(&self.0, db, hash_scheme) } } impl NodeProof for ArchivedArchivedNodeBytes { - fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error> { - import_archived_node(&self.0, db) + fn import_node( + &self, + db: &mut NodeDb, + hash_scheme: HashSchemeKind, + ) -> Result<(), Db::Error> { + import_archived_node(&self.0, db, hash_scheme) } } diff --git a/crates/primitives/src/types/tx.rs b/crates/primitives/src/types/tx.rs index 7973a26a..13b486df 100644 --- a/crates/primitives/src/types/tx.rs +++ b/crates/primitives/src/types/tx.rs @@ -149,12 +149,12 @@ impl TxTrace for TransactionTrace { self.gas_price.to() } - fn max_fee_per_gas(&self) -> u128 { - self.gas_fee_cap.map(|v| v.to()).unwrap_or_default() + fn max_fee_per_gas(&self) -> Option { + self.gas_fee_cap.map(|v| v.to()) } - fn max_priority_fee_per_gas(&self) -> u128 { - self.gas_tip_cap.map(|v| v.to()).unwrap_or_default() + fn max_priority_fee_per_gas(&self) -> Option { + self.gas_tip_cap.map(|v| v.to()) } unsafe fn get_from_unchecked(&self) -> Address { @@ -221,24 +221,18 @@ impl TxTrace for ArchivedTransactionTrace { gas_price.to() } - fn max_fee_per_gas(&self) -> u128 { - self.gas_fee_cap - .as_ref() - .map(|g| { - let gas_fee_cap: U256 = g.into(); - gas_fee_cap.to() - }) - .unwrap_or_default() + fn max_fee_per_gas(&self) -> Option { + self.gas_fee_cap.as_ref().map(|g| { + let gas_fee_cap: U256 = g.into(); + gas_fee_cap.to() + }) } - fn max_priority_fee_per_gas(&self) -> u128 { - self.gas_tip_cap - .as_ref() - .map(|g| { - let gas_tip_cap: U256 = g.into(); - gas_tip_cap.to() - }) - .unwrap_or_default() + fn max_priority_fee_per_gas(&self) -> Option { + self.gas_tip_cap.as_ref().map(|g| { + let gas_tip_cap: U256 = g.into(); + gas_tip_cap.to() + }) } unsafe fn get_from_unchecked(&self) -> Address { @@ -413,15 +407,15 @@ impl TxTrace for AlloyTransaction { } fn gas_price(&self) -> u128 { - self.gas_price.unwrap_or(0) + self.gas_price.unwrap_or_default() } - fn max_fee_per_gas(&self) -> u128 { - self.max_fee_per_gas.unwrap_or(0) + fn max_fee_per_gas(&self) -> Option { + self.max_fee_per_gas } - fn max_priority_fee_per_gas(&self) -> u128 { - self.max_priority_fee_per_gas.unwrap_or(0) + fn max_priority_fee_per_gas(&self) -> Option { + self.max_priority_fee_per_gas } unsafe fn get_from_unchecked(&self) -> Address { diff --git a/crates/stateful/Cargo.toml b/crates/stateful/Cargo.toml index 96cbab23..18dc0ad0 100644 --- a/crates/stateful/Cargo.toml +++ b/crates/stateful/Cargo.toml @@ -9,11 +9,21 @@ authors.workspace = true license.workspace = true repository.workspace = true +[[bin]] +name = "stateful-block-verifier" +path = "src/bin/verifier.rs" + +[[bin]] +name = "stateful-block-tracer" +path = "src/bin/tracer.rs" + [dependencies] alloy = { workspace = true, features = ["provider-http", "serde", "transport-http"] } anyhow.workspace = true clap = { workspace = true, features = ["derive"] } +bincode.workspace = true revm.workspace = true +rkyv.workspace = true sled.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true @@ -21,8 +31,9 @@ thiserror.workspace = true tokio = { workspace = true, features = ["fs", "io-util", "macros", "rt-multi-thread", "signal", "sync", "time"] } tokio-retry.workspace = true url.workspace = true +zktrie-ng = { workspace = true, features = ["clap", "serde"] } -sbv = { workspace = true, features = ["sled"]} +sbv = { workspace = true, features = ["sled"] } tracing-subscriber = { workspace = true, optional = true } diff --git a/crates/stateful/src/bin/tracer.rs b/crates/stateful/src/bin/tracer.rs new file mode 100644 index 00000000..634eb74a --- /dev/null +++ b/crates/stateful/src/bin/tracer.rs @@ -0,0 +1,123 @@ +//! trace dumper +use clap::Parser; +use rkyv::rancor; +use sbv::core::{EvmExecutorBuilder, HardforkConfig}; +use sbv::primitives::alloy_primitives::Bytes; +use sbv::primitives::types::{BlockTrace, BytecodeTrace, StorageTrace}; +use stateful_block_verifier::Metadata; +use std::path::PathBuf; +use zktrie_ng::db::kv::middleware::RecorderMiddleware; +use zktrie_ng::db::NodeDb; +use zktrie_ng::hash::keccak::Keccak; +use zktrie_ng::hash::poseidon::Poseidon; +use zktrie_ng::hash::HashSchemeKind; +use zktrie_ng::trie::ArchivedNode; + +#[derive(Parser)] +struct Cli { + block_number: u64, + output: Option, + + /// Path to the sled database + #[arg(short, long)] + db: PathBuf, + /// Chain ID + #[arg(short, long)] + chain_id: u64, + /// Hash scheme + #[arg(long, value_enum, default_value_t = HashSchemeKind::Poseidon)] + hash_scheme: HashSchemeKind, +} + +fn main() -> anyhow::Result<()> { + let Cli { + block_number, + output, + db, + chain_id, + hash_scheme, + } = Cli::parse(); + + let db = sled::open(db)?; + let metadata = Metadata::open(&db, chain_id)?; + + if metadata.latest_block_number() < block_number { + eprintln!("Block {} has not been imported yet", block_number); + std::process::exit(1); + } + + let block_db = metadata.open_block_db(&db)?; + let block = block_db.get_block(block_number)?.unwrap(); + + let hardfork_config = HardforkConfig::default_from_chain_id(chain_id); + + let history_db = match hash_scheme { + HashSchemeKind::Poseidon => metadata.open_history_db(&db, HashSchemeKind::Poseidon)?, + HashSchemeKind::Keccak => metadata.open_history_db(&db, HashSchemeKind::Keccak)?, + }; + + let mut code_db = metadata.open_code_db(&db)?; + let zktrie_db = match hash_scheme { + HashSchemeKind::Poseidon => metadata + .open_zktrie_db(&db, HashSchemeKind::Poseidon)? + .into_inner(), + HashSchemeKind::Keccak => metadata + .open_zktrie_db(&db, HashSchemeKind::Keccak)? + .into_inner(), + }; + let mut zktrie_db = NodeDb::new(RecorderMiddleware::new(zktrie_db)); + + let storage_root_before = history_db + .get_block_storage_root(block_number - 1)? + .expect("prev block storage root not found"); + + let builder = EvmExecutorBuilder::new(&mut code_db, &mut zktrie_db) + .chain_id(chain_id) + .hardfork_config(hardfork_config); + + let (codes, post_root) = match hash_scheme { + HashSchemeKind::Poseidon => { + let mut evm = builder.hash_scheme(Poseidon).build(storage_root_before)?; + evm.handle_block(&block)?; + (evm.db().contracts.clone(), evm.commit_changes()?) + } + HashSchemeKind::Keccak => { + let mut evm = builder.hash_scheme(Keccak).build(storage_root_before)?; + evm.handle_block(&block)?; + (evm.db().contracts.clone(), evm.commit_changes()?) + } + }; + + let storage_root_after = history_db.get_block_storage_root(block_number)?.unwrap(); + assert_eq!(storage_root_after, post_root); + + let trace = BlockTrace::new_from_alloy( + chain_id, + codes + .into_values() + .map(|code| BytecodeTrace { + code: code.original_bytes(), + }) + .collect(), + StorageTrace { + root_before: storage_root_before, + root_after: storage_root_after, + flatten_proofs: zktrie_db + .into_inner() + .take_read_items() + .into_iter() + .map(|(_, v)| { + let node = rkyv::access::(v.as_ref()).unwrap(); + Bytes::from(node.canonical_value(false)) + }) + .collect(), + }, + &block, + ); + + let output = output.unwrap_or_else(|| PathBuf::from(format!("block-{}.json", block_number))); + let output = std::fs::File::create(output)?; + serde_json::to_writer_pretty(output, &trace)?; + + Ok(()) +} diff --git a/crates/stateful/src/main.rs b/crates/stateful/src/bin/verifier.rs similarity index 100% rename from crates/stateful/src/main.rs rename to crates/stateful/src/bin/verifier.rs diff --git a/crates/stateful/src/error.rs b/crates/stateful/src/error.rs index 1e05fa7f..7ace2bd8 100644 --- a/crates/stateful/src/error.rs +++ b/crates/stateful/src/error.rs @@ -1,5 +1,8 @@ use alloy::transports::{RpcError, TransportErrorKind}; -use sbv::primitives::zk_trie::{hash::poseidon::PoseidonError, trie::ZkTrieError}; +use sbv::primitives::zk_trie::{ + hash::{keccak::KeccakError, poseidon::PoseidonError, HashSchemeKind}, + trie::ZkTrieError, +}; /// Stateful block verifier error #[derive(thiserror::Error, Debug)] @@ -10,15 +13,32 @@ pub enum Error { /// Sled error #[error(transparent)] Sled(#[from] sled::Error), + /// Json error + #[error(transparent)] + Json(#[from] serde_json::Error), + /// Bincode error + #[error(transparent)] + BincodeEncode(#[from] bincode::error::EncodeError), + /// Bincode error + #[error(transparent)] + BincodeDecode(#[from] bincode::error::DecodeError), /// Zktrie error #[error(transparent)] - Zktrie(#[from] ZkTrieError), + PoseidonZktrie(#[from] ZkTrieError), + /// Zktrie error + #[error(transparent)] + KeccakZktrie(#[from] ZkTrieError), /// Evm database error #[error(transparent)] EvmDatabase(#[from] sbv::core::DatabaseError), /// Evm verification error - #[error(transparent)] - EvmVerification(#[from] sbv::core::VerificationError), + #[error("{hash_scheme_kind:?} evm verification error: {source}")] + EvmVerification { + /// + hash_scheme_kind: HashSchemeKind, + /// + source: sbv::core::VerificationError, + }, /// Invalid block number #[error("expected sequential block")] ExpectedSequentialBlock, diff --git a/crates/stateful/src/lib.rs b/crates/stateful/src/lib.rs index d98d9349..41f902f3 100644 --- a/crates/stateful/src/lib.rs +++ b/crates/stateful/src/lib.rs @@ -12,7 +12,10 @@ use sbv::{ types::AlloyTransaction, zk_trie::{ db::{kv::SledDb, NodeDb}, - hash::{key_hasher::NoCacheHasher, poseidon::Poseidon, ZkHash}, + hash::{ + keccak::Keccak, key_hasher::NoCacheHasher, poseidon::Poseidon, HashSchemeKind, + ZkHash, + }, }, }, }; @@ -45,9 +48,12 @@ pub struct StatefulBlockExecutor { metadata: Metadata, - history_db: HistoryDb, + block_db: BlockDb, + poseidon_history_db: HistoryDb, + keccak_history_db: HistoryDb, code_db: SledDb, - zktrie_db: NodeDb, + poseidon_zktrie_db: NodeDb, + keccak_zktrie_db: NodeDb, pipeline_rx: tokio::sync::mpsc::Receiver>, shutdown: Arc, @@ -65,15 +71,24 @@ impl StatefulBlockExecutor { dev_info!("hardfork_config: {hardfork_config:?}"); let metadata = Metadata::open(&db, chain_id)?; - let history_db = metadata.open_history_db(&db)?; + let block_db = metadata.open_block_db(&db)?; + let poseidon_history_db = metadata.open_history_db(&db, HashSchemeKind::Poseidon)?; + let keccak_history_db = metadata.open_history_db(&db, HashSchemeKind::Keccak)?; let mut code_db = metadata.open_code_db(&db)?; - let mut zktrie_db = metadata.open_zktrie_db(&db)?; + let mut poseidon_zktrie_db = metadata.open_zktrie_db(&db, HashSchemeKind::Poseidon)?; + let mut keccak_zktrie_db = metadata.open_zktrie_db(&db, HashSchemeKind::Keccak)?; if metadata.needs_init() { + dev_info!("initializing db"); genesis_config.init_code_db(&mut code_db)?; - let zktrie = - genesis_config.init_zktrie::(&mut zktrie_db, NoCacheHasher)?; - history_db.set_block_storage_root(0, *zktrie.root().unwrap_ref())?; + + let poseidon_zktrie = genesis_config + .init_zktrie::(&mut poseidon_zktrie_db, NoCacheHasher)?; + poseidon_history_db.set_block_storage_root(0, *poseidon_zktrie.root().unwrap_ref())?; + + let keccak_zktrie = + genesis_config.init_zktrie::(&mut keccak_zktrie_db, NoCacheHasher)?; + keccak_history_db.set_block_storage_root(0, *keccak_zktrie.root().unwrap_ref())?; } let shutdown = Arc::new(AtomicBool::new(false)); @@ -94,9 +109,12 @@ impl StatefulBlockExecutor { genesis_config, hardfork_config, metadata, - history_db, + block_db, + poseidon_history_db, + keccak_history_db, code_db, - zktrie_db, + poseidon_zktrie_db, + keccak_zktrie_db, pipeline_rx, shutdown, }) @@ -109,19 +127,52 @@ impl StatefulBlockExecutor { } let block_number = block.header.number; - let storage_root_before = self - .history_db - .get_block_storage_root(block_number - 1)? - .expect("prev block storage root not found"); - - let mut evm = EvmExecutorBuilder::new(&mut self.code_db, &mut self.zktrie_db) - .chain_id(self.chain_id) - .hardfork_config(self.hardfork_config) - .build(storage_root_before)?; - evm.handle_block(&block)?; - let storage_root_after = evm.commit_changes()?; - self.history_db - .set_block_storage_root(block_number, storage_root_after)?; + + self.block_db.set_block(block_number, block)?; + + let storage_root_after = { + let storage_root_before = self + .poseidon_history_db + .get_block_storage_root(block_number - 1)? + .expect("prev block storage root not found"); + + let mut evm = EvmExecutorBuilder::new(&mut self.code_db, &mut self.poseidon_zktrie_db) + .chain_id(self.chain_id) + .hardfork_config(self.hardfork_config) + .hash_scheme(Poseidon) + .build(storage_root_before)?; + evm.handle_block(&block) + .map_err(|e| Error::EvmVerification { + hash_scheme_kind: HashSchemeKind::Poseidon, + source: e, + })?; + let storage_root_after = evm.commit_changes()?; + self.poseidon_history_db + .set_block_storage_root(block_number, storage_root_after)?; + dev_info!("block#{block_number} poseidon stateful done"); + storage_root_after + }; + + { + let storage_root_before = self + .keccak_history_db + .get_block_storage_root(block_number - 1)? + .expect("prev block storage root not found"); + let mut evm = EvmExecutorBuilder::new(&mut self.code_db, &mut self.keccak_zktrie_db) + .chain_id(self.chain_id) + .hardfork_config(self.hardfork_config) + .hash_scheme(Keccak) + .build(storage_root_before)?; + evm.handle_block(&block) + .map_err(|e| Error::EvmVerification { + hash_scheme_kind: HashSchemeKind::Keccak, + source: e, + })?; + let storage_root_after = evm.commit_changes()?; + self.keccak_history_db + .set_block_storage_root(block_number, storage_root_after)?; + dev_info!("block#{block_number} keccak stateful done"); + } if block.header.state_root != storage_root_after { return Err(Error::PostStateRootMismatch); @@ -151,7 +202,7 @@ impl StatefulBlockExecutor { &self.provider, self.chain_id, self.hardfork_config, - self.history_db + self.poseidon_history_db .get_block_storage_root(block_number - 1)? .unwrap(), &block, @@ -244,12 +295,6 @@ impl StatefulBlockExecutor { pub fn metadata(&self) -> &Metadata { &self.metadata } - - /// Get the history db - #[inline(always)] - pub fn history_db(&self) -> &HistoryDb { - &self.history_db - } } /// Metadata @@ -307,20 +352,38 @@ impl Metadata { )) } + /// Open the block db + #[inline(always)] + pub fn open_block_db(&self, db: &sled::Db) -> Result { + Ok(BlockDb { + db: db.open_tree(format!("block_db_chain_{}", self.chain_id))?, + }) + } + /// Open the zktrie db #[inline(always)] - pub fn open_zktrie_db(&self, db: &sled::Db) -> Result> { + pub fn open_zktrie_db( + &self, + db: &sled::Db, + hash_scheme: HashSchemeKind, + ) -> Result> { Ok(NodeDb::new(SledDb::new( true, - db.open_tree(format!("zktrie_db_chain_{}", self.chain_id))?, + db.open_tree(format!( + "zktrie_db_chain_{}_hash_{:?}", + self.chain_id, hash_scheme + ))?, ))) } /// Open the history db #[inline(always)] - pub fn open_history_db(&self, db: &sled::Db) -> Result { + pub fn open_history_db(&self, db: &sled::Db, hash_scheme: HashSchemeKind) -> Result { Ok(HistoryDb { - db: db.open_tree(format!("history_db_chain_{}", self.chain_id))?, + db: db.open_tree(format!( + "history_db_chain_{}_hash_{:?}", + self.chain_id, hash_scheme + ))?, }) } } @@ -349,3 +412,29 @@ impl HistoryDb { .map(|v| ZkHash::from_slice(v.as_ref()))) } } + +/// Block database +#[derive(Debug)] +pub struct BlockDb { + db: Tree, +} + +impl BlockDb { + /// Set the block + #[inline(always)] + pub fn set_block(&self, block_number: u64, block: &Block) -> Result<()> { + self.db + .insert(block_number.to_le_bytes(), serde_json::to_vec(block)?)?; + Ok(()) + } + + /// Get the block + #[inline(always)] + pub fn get_block(&self, block_number: u64) -> Result>> { + Ok(self + .db + .get(block_number.to_le_bytes())? + .map(|v| serde_json::from_slice(v.as_ref())) + .transpose()?) + } +} diff --git a/crates/stateful/src/sanity_check.rs b/crates/stateful/src/sanity_check.rs index 4f307797..beedbc75 100644 --- a/crates/stateful/src/sanity_check.rs +++ b/crates/stateful/src/sanity_check.rs @@ -1,4 +1,4 @@ -use crate::{retry_if_transport_error, Result}; +use crate::{retry_if_transport_error, Error, Result}; use alloy::primitives::ChainId; use alloy::providers::{Provider, ReqwestProvider}; use revm::primitives::BlockEnv; @@ -6,7 +6,10 @@ use sbv::{ core::{EvmExecutorBuilder, HardforkConfig}, primitives::{ types::{AlloyTransaction, BlockTrace, LegacyStorageTrace}, - zk_trie::db::{kv::HashMapDb, NodeDb}, + zk_trie::{ + db::{kv::HashMapDb, NodeDb}, + hash::{poseidon::Poseidon, HashSchemeKind}, + }, Block, Transaction, TxTrace, B256, U256, }, }; @@ -115,13 +118,21 @@ pub async fn check_stateless( { let mut code_db = HashMapDb::default(); let mut zktrie_db = NodeDb::new(HashMapDb::default()); - l2_trace.build_zktrie_db(&mut zktrie_db).unwrap(); + l2_trace + .build_zktrie_db(&mut zktrie_db, HashSchemeKind::Poseidon) + .unwrap(); let mut executor = EvmExecutorBuilder::new(&mut code_db, &mut zktrie_db) .hardfork_config(hardfork_config) .chain_id(chain_id) + .hash_scheme(Poseidon) .build(root_before)?; executor.insert_codes(&l2_trace)?; - executor.handle_block(&l2_trace)?; + executor + .handle_block(&l2_trace) + .map_err(|e| Error::EvmVerification { + hash_scheme_kind: HashSchemeKind::Poseidon, + source: e, + })?; let revm_root_after = executor.commit_changes()?; assert_eq!(root_after, revm_root_after); dev_info!("block#{block_number} stateless check ok");