diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0606958..660b52f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: matrix: network: [ "mainnet" ] hardfork: [ "pre-bernoulli", "bernoulli", "curie", "darwin" ] - rust: [ "1.75", "nightly-2024-07-07" ] + rust: [ "1.81", "nightly-2024-07-07" ] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index b29b4fc..507e817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add a new struct `LegacyStorageTrace` to support legacy storage trace support + ([#58](https://github.com/scroll-tech/stateless-block-verifier/pull/58)) + +### Changed + +- `flatten_proofs` in `StorageTrace` is changed from `Vec<(B256, Bytes)` to `Vec` + since the node hash will be recalculated when adding to zktrie + ([#58](https://github.com/scroll-tech/stateless-block-verifier/pull/58)) +- `BlockTrace` now has a generic parameter `S` for the storage trace type, default to `StorageTrace` + ([#58](https://github.com/scroll-tech/stateless-block-verifier/pull/58)) +- rpc mode defaults to use legacy storage trace, using the flag `--flatten-proofs` to enable support of flatten proofs + ([#58](https://github.com/scroll-tech/stateless-block-verifier/pull/58)) + + ## [2.0.0] - 2024-09-04 ### Added diff --git a/Cargo.toml b/Cargo.toml index 978df76..f8de82e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [workspace.package] version = "2.0.0" edition = "2021" -rust-version = "1.75" +rust-version = "1.81" authors = ["Scroll developers"] license = "MIT OR Apache-2.0" homepage = "https://github.com/scroll-tech/stateless-block-verifier" @@ -15,14 +15,13 @@ repository = "https://github.com/scroll-tech/stateless-block-verifier" alloy = "0.3" hex = "0.4" once_cell = "1.19" -rkyv = { version = "0.7", features = ["validation"] } +rkyv = "0.8" thiserror = "1.0" 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 = { path = "../zktrie-ng", features = ["scroll"] } +zktrie-ng = { git = "https://github.com/scroll-tech/zktrie-ng", branch = "master", features = ["scroll"] } # binary dependencies anyhow = "1.0" @@ -81,13 +80,13 @@ features = [ ] [workspace.lints.rust] -#missing-docs = "deny" +missing-docs = "deny" missing-debug-implementations = "deny" [patch.crates-io] ff = { git = "https://github.com/scroll-tech/ff", branch = "feat/sp1" } -# patched add rkyv support & MSRV 1.75 +# patched add rkyv support & MSRV 1.77 ruint = { git = "https://github.com/scroll-tech/uint.git", branch = "v1.12.3" } alloy = { git = "https://github.com/scroll-tech/alloy.git", branch = "v0.3.0" } @@ -96,8 +95,9 @@ alloy-eips = { git = "https://github.com/scroll-tech/alloy.git", branch = "v0.3. alloy-eip2930 = { git = "https://github.com/scroll-tech/alloy-eips", branch = "v0.1.0" } alloy-eip7702 = { git = "https://github.com/scroll-tech/alloy-eips", branch = "v0.1.0" } -alloy-core = { git = "https://github.com/scroll-tech/alloy-core", branch = "v0.8.0"} -alloy-primitives = { git = "https://github.com/scroll-tech/alloy-core", branch = "v0.8.0"} +alloy-core = { git = "https://github.com/scroll-tech/alloy-core", branch = "v0.8.0" } +alloy-primitives = { git = "https://github.com/scroll-tech/alloy-core", branch = "v0.8.0" } +alloy-sol-types = {git = "https://github.com/scroll-tech/alloy-core", branch = "v0.8.0" } # for local development # [patch."https://github.com/scroll-tech/revm"] diff --git a/crates/bin/src/commands/run_file.rs b/crates/bin/src/commands/run_file.rs index c8badcf..114ac2f 100644 --- a/crates/bin/src/commands/run_file.rs +++ b/crates/bin/src/commands/run_file.rs @@ -1,11 +1,12 @@ use crate::utils; -use anyhow::bail; +use anyhow::{anyhow, bail}; use clap::Args; +use sbv::primitives::types::LegacyStorageTrace; use sbv::{ core::{ChunkInfo, EvmExecutorBuilder, HardforkConfig}, - primitives::{types::BlockTrace, zk_trie::db::HashMapDb, Block, B256}, + primitives::{types::BlockTrace, zk_trie::db::kv::HashMapDb, Block, B256}, }; -use std::rc::Rc; +use std::panic::catch_unwind; use std::{cell::RefCell, path::PathBuf}; use tiny_keccak::{Hasher, Keccak}; use tokio::task::JoinSet; @@ -44,7 +45,7 @@ impl RunFileCommand { while let Some(task) = tasks.join_next().await { if let Err(err) = task? { - bail!("{:?}", err); + dev_error!("{:?}", err); } } @@ -75,12 +76,11 @@ impl RunFileCommand { } let fork_config = fork_config(traces[0].chain_id()); - let (chunk_info, zktrie_db) = ChunkInfo::from_block_traces(&traces); - let zktrie_db = Rc::new(RefCell::new(zktrie_db)); + let (chunk_info, mut zktrie_db) = ChunkInfo::from_block_traces(&traces); let tx_bytes_hasher = RefCell::new(Keccak::v256()); - let mut executor = EvmExecutorBuilder::new(HashMapDb::default(), zktrie_db.clone()) + let mut executor = EvmExecutorBuilder::new(HashMapDb::default(), &mut zktrie_db) .hardfork_config(fork_config) .with_hooks(&traces[0], |hooks| { hooks.add_tx_rlp_handler(|_, rlp| { @@ -94,7 +94,7 @@ impl RunFileCommand { executor.handle_block(trace)?; } - let post_state_root = executor.commit_changes(zktrie_db.clone())?; + let post_state_root = executor.commit_changes()?; if post_state_root != chunk_info.post_state_root() { bail!("post state root mismatch"); } @@ -116,19 +116,21 @@ async fn read_block_trace(path: &PathBuf) -> anyhow::Result { } fn deserialize_block_trace(trace: &str) -> anyhow::Result { + // Try to deserialize `BlockTrace` from JSON. In case of failure, try to + // deserialize `BlockTrace` from a JSON-RPC response that has the actual block + // trace nested in the value of the key "result". Ok( - // Try to deserialize `BlockTrace` from JSON. In case of failure, try to - // deserialize `BlockTrace` from a JSON-RPC response that has the actual block - // trace nested in the value of the key "result". - serde_json::from_str::(trace).or_else(|_| { - #[derive(serde::Deserialize, Default, Debug, Clone)] - pub struct BlockTraceJsonRpcResult { - pub result: BlockTrace, - } - Ok::<_, serde_json::Error>( - serde_json::from_str::(trace)?.result, - ) - })?, + serde_json::from_str::>(trace) + .or_else(|_| { + #[derive(serde::Deserialize, Default, Debug, Clone)] + pub struct BlockTraceJsonRpcResult { + pub result: BlockTrace, + } + Ok::<_, serde_json::Error>( + serde_json::from_str::(trace)?.result, + ) + })? + .into(), ) } @@ -138,6 +140,26 @@ async fn run_trace( ) -> anyhow::Result<()> { let trace = read_block_trace(&path).await?; let fork_config = fork_config(trace.chain_id()); - tokio::task::spawn_blocking(move || utils::verify(&trace, &fork_config)).await??; + if let Err(e) = + tokio::task::spawn_blocking(move || catch_unwind(|| utils::verify(&trace, &fork_config))) + .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}")) + }) + .unwrap_or_else(|| anyhow!("task panics")) + }) + .and_then(|r| r.map_err(anyhow::Error::from)) + { + dev_error!( + "Error occurs when verifying block ({}): {:?}", + path.display(), + e + ); + return Err(e); + } Ok(()) } diff --git a/crates/bin/src/commands/run_rpc.rs b/crates/bin/src/commands/run_rpc.rs index 27553ef..ec4ff3e 100644 --- a/crates/bin/src/commands/run_rpc.rs +++ b/crates/bin/src/commands/run_rpc.rs @@ -2,6 +2,7 @@ use crate::utils; use alloy::providers::{Provider, ProviderBuilder}; use clap::Args; use futures::future::OptionFuture; +use sbv::primitives::types::LegacyStorageTrace; use sbv::{ core::HardforkConfig, primitives::{types::BlockTrace, Block}, @@ -19,6 +20,9 @@ pub struct RunRpcCommand { /// RPC URL #[arg(short, long, default_value = "http://localhost:8545")] url: Url, + /// Enable flatten proofs + #[arg(short, long)] + flatten_proofs: bool, /// Start Block number #[arg(short, long, default_value = "latest")] start_block: StartBlockSpec, @@ -49,7 +53,11 @@ pub enum StartBlockSpec { impl RunRpcCommand { pub async fn run(self, fork_config: impl Fn(u64) -> HardforkConfig) -> anyhow::Result<()> { - dev_info!("Running RPC command with url: {}", self.url); + dev_info!( + "Running RPC command with url: {}, flatten proofs support: {}", + self.url, + self.flatten_proofs + ); let provider = ProviderBuilder::new().on_http(self.url); let chain_id = provider.get_chain_id().await?; @@ -75,21 +83,32 @@ impl RunRpcCommand { let rx = rx.clone(); handles.spawn(async move { while let Ok(block_number) = rx.recv().await { - let l2_trace = _provider - .raw_request::<_, BlockTrace>( - "scroll_getBlockTraceByNumberOrHash".into(), - ( - format!("0x{:x}", block_number), - serde_json::json!({ - "ExcludeExecutionResults": true, - "ExcludeTxStorageTraces": true, - "StorageProofFormat": "flatten", - "FlattenProofsOnly": true - }), - ), - ) - .await - .map_err(|e| (block_number, e.into()))?; + let l2_trace: BlockTrace = if !self.flatten_proofs { + let trace = _provider + .raw_request::<_, BlockTrace>( + "scroll_getBlockTraceByNumberOrHash".into(), + (format!("0x{:x}", block_number),), + ) + .await + .map_err(|e| (block_number, e.into()))?; + trace.into() + } else { + _provider + .raw_request::<_, BlockTrace>( + "scroll_getBlockTraceByNumberOrHash".into(), + ( + format!("0x{:x}", block_number), + serde_json::json!({ + "ExcludeExecutionResults": true, + "ExcludeTxStorageTraces": true, + "StorageProofFormat": "flatten", + "FlattenProofsOnly": true + }), + ), + ) + .await + .map_err(|e| (block_number, e.into()))? + }; dev_info!( "worker#{_idx}: load trace for block #{block_number}({})", diff --git a/crates/bin/src/utils.rs b/crates/bin/src/utils.rs index 36ab091..7782a80 100644 --- a/crates/bin/src/utils.rs +++ b/crates/bin/src/utils.rs @@ -1,9 +1,8 @@ +use sbv::primitives::zk_trie::db::NodeDb; use sbv::{ core::{EvmExecutorBuilder, HardforkConfig, VerificationError}, - primitives::{zk_trie::db::HashMapDb, Block}, + primitives::{zk_trie::db::kv::HashMapDb, Block}, }; -use std::cell::RefCell; -use std::rc::Rc; pub fn verify( l2_trace: T, @@ -37,19 +36,19 @@ fn verify_inner( .build() .unwrap(); - let zktrie_db = cycle_track!( + let mut zktrie_db = cycle_track!( { - let mut zktrie_db = HashMapDb::default(); + 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() ); - Rc::new(RefCell::new(zktrie_db)) + zktrie_db }, "build ZktrieState" ); - let mut executor = EvmExecutorBuilder::new(HashMapDb::default(), zktrie_db.clone()) + let mut executor = EvmExecutorBuilder::new(HashMapDb::default(), &mut zktrie_db) .hardfork_config(*fork_config) .build(&l2_trace)?; @@ -66,7 +65,7 @@ fn verify_inner( update_metrics_counter!(verification_error); e })?; - let revm_root_after = executor.commit_changes(zktrie_db.clone())?; + let revm_root_after = 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 e04aeea..0fb3578 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::HashMapDb, Block}; +use sbv_primitives::zk_trie::db::NodeDb; +use sbv_primitives::{zk_trie::db::kv::HashMapDb, Block}; use tiny_keccak::{Hasher, Keccak}; /// A chunk is a set of continuous blocks. @@ -21,7 +22,7 @@ pub struct ChunkInfo { impl ChunkInfo { /// Construct by block traces - pub fn from_block_traces(traces: &[T]) -> (Self, HashMapDb) { + pub fn from_block_traces(traces: &[T]) -> (Self, NodeDb) { let chain_id = traces.first().unwrap().chain_id(); let prev_state_root = traces .first() @@ -30,6 +31,7 @@ impl ChunkInfo { let post_state_root = traces.last().expect("at least 1 block needed").root_after(); let withdraw_root = traces.last().unwrap().withdraw_root(); + cycle_tracker_start!("Keccak::v256"); let mut data_hasher = Keccak::v256(); for trace in traces.iter() { trace.hash_da_header(&mut data_hasher); @@ -39,14 +41,17 @@ impl ChunkInfo { } let mut data_hash = B256::ZERO; data_hasher.finalize(&mut data_hash.0); + cycle_tracker_end!("Keccak::v256"); - let mut zktrie_db = HashMapDb::default(); + let mut zktrie_db = NodeDb::new(HashMapDb::default()); + cycle_tracker_start!("Block::build_zktrie_db"); for trace in traces.iter() { measure_duration_millis!( build_zktrie_db_duration_milliseconds, trace.build_zktrie_db(&mut zktrie_db).unwrap() ); } + cycle_tracker_end!("Block::build_zktrie_db"); let info = ChunkInfo { chain_id, @@ -116,7 +121,6 @@ mod tests { use revm::primitives::b256; use sbv_primitives::types::BlockTrace; use std::cell::RefCell; - use std::rc::Rc; const TRACES_STR: [&str; 4] = [ include_str!("../../../testdata/mainnet_blocks/8370400.json"), @@ -138,12 +142,11 @@ mod tests { }); let fork_config = HardforkConfig::default_from_chain_id(traces[0].chain_id()); - let (chunk_info, zktrie_db) = ChunkInfo::from_block_traces(&traces); - let zktrie_db = Rc::new(RefCell::new(zktrie_db)); + let (chunk_info, mut zktrie_db) = ChunkInfo::from_block_traces(&traces); let tx_bytes_hasher = RefCell::new(Keccak::v256()); - let mut executor = EvmExecutorBuilder::new(HashMapDb::default(), zktrie_db.clone()) + let mut executor = EvmExecutorBuilder::new(HashMapDb::default(), &mut zktrie_db) .hardfork_config(fork_config) .with_hooks(&traces[0], |hooks| { hooks.add_tx_rlp_handler(|_, rlp| { @@ -158,7 +161,7 @@ mod tests { executor.handle_block(trace).unwrap(); } - let post_state_root = executor.commit_changes(zktrie_db.clone()).unwrap(); + let post_state_root = executor.commit_changes().unwrap(); assert_eq!(post_state_root, chunk_info.post_state_root); drop(executor); // drop executor to release Rc diff --git a/crates/core/src/database.rs b/crates/core/src/database.rs index 870fc67..32cce45 100644 --- a/crates/core/src/database.rs +++ b/crates/core/src/database.rs @@ -1,12 +1,13 @@ use crate::error::DatabaseError; -use once_cell::sync::Lazy; use revm::{ - db::{AccountState, DatabaseRef}, + db::DatabaseRef, primitives::{AccountInfo, Address, Bytecode, B256, U256}, }; +use sbv_primitives::zk_trie::db::NodeDb; +use sbv_primitives::zk_trie::hash::ZkHash; use sbv_primitives::{ zk_trie::{ - db::{KVDatabase, KVDatabaseItem}, + db::kv::{KVDatabase, KVDatabaseItem}, hash::{key_hasher::NoCacheHasher, poseidon::Poseidon}, scroll_types::Account, trie::ZkTrie, @@ -17,28 +18,24 @@ use std::{cell::RefCell, collections::HashMap, fmt}; type Result = std::result::Result; -type StorageTrieLazyFn = Box ZkTrie>; -type LazyStorageTrie = Lazy, StorageTrieLazyFn>; - /// A database that consists of account and storage information. -pub struct EvmDatabase { +pub struct EvmDatabase<'a, CodeDb, ZkDb> { /// Map of code hash to bytecode. code_db: CodeDb, - /// The initial storage roots of accounts, used for after commit. - /// Need to be updated after zkTrie commit. - prev_storage_roots: RefCell>, + /// Storage root cache, avoid re-query account when storage root is needed + 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_refs: RefCell>>, + /// Need to invalidate before `update`, otherwise the trie root may be outdated + storage_trie_caches: RefCell>>>, /// Current uncommitted zkTrie root based on the block trace. committed_zktrie_root: B256, /// The underlying zkTrie database. - zktrie_db: ZkDb, + pub(crate) zktrie_db: &'a mut NodeDb, /// Current view of zkTrie database. - zktrie: ZkTrie, + zktrie: ZkTrie, } -impl fmt::Debug for EvmDatabase { +impl fmt::Debug for EvmDatabase<'_, CodeDb, Db> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EvmDatabase") .field("committed_zktrie_root", &self.committed_zktrie_root) @@ -46,31 +43,12 @@ impl fmt::Debug for EvmDatabase { } } -impl EvmDatabase { +impl<'a, CodeDb: KVDatabase, ZkDb: KVDatabase + 'static> EvmDatabase<'a, CodeDb, ZkDb> { /// Initialize an EVM database from a block trace. - pub fn new_with_root( - committed_zktrie_root: B256, - code_db: CodeDb, - zktrie_db: ZkDb, - ) -> Result { - let zktrie = ZkTrie::new_with_root(zktrie_db.clone(), NoCacheHasher, committed_zktrie_root) - .map_err(DatabaseError::zk_trie)?; - - Ok(EvmDatabase { - code_db, - prev_storage_roots: Default::default(), - storage_trie_refs: Default::default(), - committed_zktrie_root, - zktrie_db, - zktrie, - }) - } - - /// Initialize an EVM database from a block trace. - pub fn new_from_trace( + pub fn new( l2_trace: T, mut code_db: CodeDb, - zktrie_db: ZkDb, + zktrie_db: &'a mut NodeDb, ) -> Result { cycle_tracker_start!("insert CodeDB"); for code in l2_trace.codes() { @@ -81,33 +59,47 @@ impl EvmDatabase Option { - self.prev_storage_roots - .borrow_mut() - .insert(address, storage_root) + let zktrie = ZkTrie::new_with_root(zktrie_db, NoCacheHasher, committed_zktrie_root) + .map_err(DatabaseError::zk_trie)?; + + Ok(EvmDatabase { + code_db, + storage_root_caches: Default::default(), + storage_trie_caches: Default::default(), + committed_zktrie_root, + zktrie_db, + zktrie, + }) } /// Get the previous storage root of an account. #[inline] pub(crate) fn prev_storage_root(&self, address: &Address) -> B256 { - self.prev_storage_roots + self.storage_root_caches .borrow() .get(address) .copied() .unwrap_or_default() } + #[inline] + 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 + .borrow_mut() + .insert(address, new_root); + + let mut storage_trie_caches = self.storage_trie_caches.borrow_mut(); + if let Some(old) = old { + storage_trie_caches.remove(&old); + } + + storage_trie_caches.insert(new_root, Some(storage_root)); + } + /// Get the committed zkTrie root. #[inline] pub(crate) fn committed_zktrie_root(&self) -> B256 { @@ -135,61 +127,33 @@ impl EvmDatabase, - ) { - let mut storage_trie_refs = self.storage_trie_refs.borrow_mut(); - for (address, account_state) in account_states { - if account_state != AccountState::None { - storage_trie_refs.remove(&address); - } - } - } } -impl DatabaseRef - for EvmDatabase -{ +impl DatabaseRef for EvmDatabase<'_, CodeDb, ZkDb> { type Error = DatabaseError; /// Get basic account information. fn basic_ref(&self, address: Address) -> Result> { let Some(account) = measure_duration_micros!( zktrie_get_duration_microseconds, - self.zktrie.get::(address) + self.zktrie.get::<_, Account, _>(self.zktrie_db, address) ) .map_err(DatabaseError::zk_trie)? else { return Ok(None); }; - dev_trace!("get account {:?}", account); - self.prev_storage_roots + self.storage_root_caches .borrow_mut() - .entry(address) - .or_insert(account.storage_root); - let zktrie_db = self.zktrie_db.clone(); - self.storage_trie_refs - .borrow_mut() - .entry(address) - .or_insert_with(|| { - Lazy::new(Box::new(move || { - ZkTrie::new_with_root(zktrie_db.clone(), NoCacheHasher, account.storage_root) - .expect("storage trie associated with account not found") - })) - }); + .insert(address, account.storage_root); let mut info = AccountInfo::from(account); info.code = self @@ -197,7 +161,6 @@ impl DatabaseRef .get(&account.code_hash) .map_err(DatabaseError::code_db)? .map(|v| Bytecode::new_legacy(v.into_bytes().into())); - dev_trace!("get account info {:?}", info); Ok(Some(info)) } @@ -224,32 +187,42 @@ impl DatabaseRef /// Get storage value of address at index. fn storage_ref(&self, address: Address, index: U256) -> Result { dev_trace!("get storage of {:?} at index {:?}", address, index); - let mut storage_trie_refs = self.storage_trie_refs.borrow_mut(); - let trie = storage_trie_refs + let storage_root = *self + .storage_root_caches + .borrow_mut() .entry(address) .or_insert_with_key(|address| { - let storage_root = measure_duration_micros!( - zktrie_get_duration_microseconds, - self.zktrie.get::(address) - ) - .expect("unexpected zktrie error") - .map(|acc| acc.storage_root) - .unwrap_or_default(); + self.zktrie + .get::<_, Account, _>(self.zktrie_db, address) + .expect("unexpected zktrie error") + .map(|acc| acc.storage_root) + .unwrap_or_default() + }); + + let mut storage_trie_caches = self.storage_trie_caches.borrow_mut(); + + let trie = storage_trie_caches + .entry(storage_root) + .or_insert_with_key(|storage_root| { dev_debug!("storage root of {:?} is {:?}", address, storage_root); - let zktrie_db = self.zktrie_db.clone(); - Lazy::new(Box::new(move || { - ZkTrie::new_with_root(zktrie_db.clone(), NoCacheHasher, storage_root) - .expect("storage trie associated with account not found") - })) + ZkTrie::new_with_root(self.zktrie_db, NoCacheHasher, *storage_root) + .inspect_err(|e| { + dev_warn!("storage trie associated with account({address}) not found: {e}") + }) + .ok() }); + if trie.is_none() { + return Err(DatabaseError::NotIncluded); + } + let trie = trie.as_mut().unwrap(); #[cfg(debug_assertions)] { let current_root = trie.root().unwrap_ref(); let expected_root = self .zktrie - .get::(address) + .get::<_, Account, _>(self.zktrie_db, address) .expect("unexpected zktrie error") .map(|acc| acc.storage_root) .unwrap_or_default(); @@ -258,7 +231,7 @@ impl DatabaseRef Ok(measure_duration_micros!( zktrie_get_duration_microseconds, - trie.get::(index.to_be_bytes::<32>()) + trie.get::<_, U256, _>(self.zktrie_db, index.to_be_bytes::<32>()) ) .map_err(DatabaseError::zk_trie)? .unwrap_or_default()) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 322f6c5..7406a35 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -4,10 +4,15 @@ use std::error::Error; /// Error variants encountered during manipulation of a zkTrie. #[derive(Debug, thiserror::Error)] pub enum DatabaseError { + /// Error encountered from code db. #[error("error encountered from code db: {0}")] CodeDb(Box), + /// Error encountered from zkTrie. #[error("error encountered from zkTrie: {0}")] ZkTrie(Box), + /// Error when try to load un-included account/storage. + #[error("account/storage not included in zkTrie")] + NotIncluded, } /// Error variants encountered during verification of transactions in a L2 block. diff --git a/crates/core/src/executor/builder.rs b/crates/core/src/executor/builder.rs index e7d19ab..076f147 100644 --- a/crates/core/src/executor/builder.rs +++ b/crates/core/src/executor/builder.rs @@ -1,118 +1,116 @@ use crate::error::DatabaseError; use crate::{executor::hooks::ExecuteHooks, EvmDatabase, EvmExecutor, HardforkConfig}; use revm::db::CacheDB; -use revm::primitives::alloy_primitives::ChainId; -use sbv_primitives::zk_trie::db::KVDatabase; -use sbv_primitives::{Block, B256}; +use sbv_primitives::zk_trie::db::kv::KVDatabase; +use sbv_primitives::zk_trie::db::NodeDb; +use sbv_primitives::Block; use std::fmt::{self, Debug}; +use sbv_primitives::alloy_primitives::ChainId; /// Builder for EVM executor. -pub struct EvmExecutorBuilder { +pub struct EvmExecutorBuilder<'a, H, C, CodeDb, ZkDb> { hardfork_config: H, - evm_db: EvmDb, chain_id: C, + code_db: CodeDb, + zktrie_db: &'a mut NodeDb, } -impl Debug for EvmExecutorBuilder { +impl Debug for EvmExecutorBuilder<'_, H, C, CodeDb, ZkDb> { 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", &"...") .finish() } } -impl EvmExecutorBuilder<(), (), ()> { +impl<'a, CodeDb, ZkDb> EvmExecutorBuilder<'a, (), (), CodeDb, ZkDb> { /// Create a new builder. - pub fn new() -> Self { + pub fn new(code_db: CodeDb, zktrie_db: &'a mut NodeDb) -> Self { Self { hardfork_config: (), - evm_db: (), chain_id: (), + code_db, + zktrie_db, } } } -impl EvmExecutorBuilder { +impl<'a, H, C, CodeDb, ZkDb> EvmExecutorBuilder<'a, H, C, CodeDb, ZkDb> { /// Set hardfork config. - pub fn hardfork_config

(self, hardfork_config: H1) -> EvmExecutorBuilder { + pub fn hardfork_config

( + self, + hardfork_config: H1, + ) -> EvmExecutorBuilder<'a, H1, C, CodeDb, ZkDb> { EvmExecutorBuilder { hardfork_config, - evm_db: self.evm_db, chain_id: self.chain_id, + code_db: self.code_db, + zktrie_db: self.zktrie_db, } } - /// Set evm db. - pub fn evm_db(self, evm_db: EvmDb1) -> EvmExecutorBuilder { + /// Set code db. + pub fn code_db(self, code_db: CodeDb1) -> EvmExecutorBuilder<'a, H, C, CodeDb1, ZkDb> { EvmExecutorBuilder { hardfork_config: self.hardfork_config, - evm_db, chain_id: self.chain_id, + code_db, + zktrie_db: self.zktrie_db, } } - /// Build evm executor from a block trace. - pub fn evm_db_from_trace( - self, - l2_trace: &T, - code_db: CodeDb, - zktrie_db: ZkDb, - ) -> Result, C>, DatabaseError> { - Ok(EvmExecutorBuilder { - hardfork_config: self.hardfork_config, - evm_db: EvmDatabase::new_from_trace(l2_trace, code_db, zktrie_db)?, - chain_id: self.chain_id, - }) - } - - /// Build evm executor from a block trace. - pub fn evm_db_from_root( + /// Set zktrie db. + pub fn zktrie_db( self, - committed_zktrie_root: B256, - code_db: CodeDb, - zktrie_db: ZkDb, - ) -> Result, C>, DatabaseError> { - Ok(EvmExecutorBuilder { - hardfork_config: self.hardfork_config, - evm_db: EvmDatabase::new_with_root(committed_zktrie_root, code_db, zktrie_db)?, - chain_id: self.chain_id, - }) - } - - /// Set chain id. - pub fn chain_id(self, chain_id: C1) -> EvmExecutorBuilder { + zktrie_db: &mut NodeDb, + ) -> EvmExecutorBuilder { EvmExecutorBuilder { hardfork_config: self.hardfork_config, - evm_db: self.evm_db, - chain_id, + chain_id: self.chain_id, + code_db: self.code_db, + zktrie_db, } } } -impl - EvmExecutorBuilder, ChainId> +impl<'a, CodeDb: KVDatabase, ZkDb: KVDatabase + 'static> + EvmExecutorBuilder<'a, HardforkConfig, ChainId, CodeDb, ZkDb> { /// Initialize an EVM executor from a block trace as the initial state. - pub fn with_hooks<'e, F: FnOnce(&mut ExecuteHooks<'e, CodeDb, ZkDb>)>( + pub fn with_hooks<'h, T: Block, F: FnOnce(&mut ExecuteHooks<'h, CodeDb, ZkDb>)>( self, + l2_trace: &T, with_execute_hooks: F, - ) -> EvmExecutor<'e, CodeDb, ZkDb> { + ) -> Result, DatabaseError> { let mut execute_hooks = ExecuteHooks::new(); with_execute_hooks(&mut execute_hooks); - let db = CacheDB::new(self.evm_db); + let block_number = l2_trace.number(); + let spec_id = self.hardfork_config.get_spec_id(block_number); - EvmExecutor { - chain_id: self.chain_id, + dev_trace!("use spec id {:?}", spec_id); + + let db = cycle_track!( + CacheDB::new(EvmDatabase::new(l2_trace, self.code_db, self.zktrie_db)?), + "build ReadOnlyDB" + ); + + Ok(EvmExecutor { hardfork_config: self.hardfork_config, + chain_id: self.chain_id, db, hooks: execute_hooks, - } + }) } /// Initialize an EVM executor from a block trace as the initial state. - pub fn build<'e>(self) -> EvmExecutor<'e, CodeDb, ZkDb> { - self.with_hooks(|_| {}) + pub fn build<'e, T: Block>( + self, + l2_trace: &T, + ) -> Result, DatabaseError> { + self.with_hooks(l2_trace, |_| {}) } } diff --git a/crates/core/src/executor/mod.rs b/crates/core/src/executor/mod.rs index c9a6ce3..03e4bc4 100644 --- a/crates/core/src/executor/mod.rs +++ b/crates/core/src/executor/mod.rs @@ -10,11 +10,12 @@ use revm::{ }; use sbv_primitives::{ zk_trie::{ - db::KVDatabase, + db::kv::KVDatabase, hash::{ key_hasher::NoCacheHasher, poseidon::{Poseidon, PoseidonError}, }, + scroll_types::Account, trie::{ZkTrie, ZkTrieError}, }, Block, Transaction, TxTrace, @@ -23,20 +24,19 @@ use std::fmt::Debug; mod builder; pub use builder::EvmExecutorBuilder; -use sbv_primitives::zk_trie::scroll_types::Account; /// Execute hooks pub mod hooks; /// EVM executor that handles the block. -pub struct EvmExecutor<'a, CodeDb, ZkDb> { +pub struct EvmExecutor<'db, 'h, CodeDb, ZkDb> { chain_id: ChainId, hardfork_config: HardforkConfig, - db: CacheDB>, - hooks: hooks::ExecuteHooks<'a, CodeDb, ZkDb>, + db: CacheDB>, + hooks: hooks::ExecuteHooks<'h, CodeDb, ZkDb>, } -impl EvmExecutor<'_, CodeDb, ZkDb> { +impl EvmExecutor<'_, '_, CodeDb, ZkDb> { /// Get reference to the DB pub fn db(&self) -> &CacheDB> { &self.db @@ -44,31 +44,25 @@ impl EvmExecutor<'_, Cod /// Update the DB pub fn update_db(&mut self, l2_trace: &T) -> Result<(), DatabaseError> { - self.db.db.invalidate_storage_root_caches( - self.db - .accounts - .iter() - .map(|(addr, acc)| (*addr, acc.account_state.clone())), - ); - self.db.db.update(l2_trace) } /// Handle a block. - pub fn handle_block(&mut self, l2_trace: &T) -> Result<(), VerificationError> { - measure_duration_millis!( + pub fn handle_block(&mut self, l2_trace: &T) -> Result { + #[allow(clippy::let_and_return)] + let gas_used = measure_duration_millis!( handle_block_duration_milliseconds, - self.handle_block_inner(l2_trace) + cycle_track!(self.handle_block_inner(l2_trace), "handle_block") )?; #[cfg(feature = "metrics")] sbv_utils::metrics::REGISTRY.block_counter.inc(); - Ok(()) + Ok(gas_used) } #[inline(always)] - fn handle_block_inner(&mut self, l2_trace: &T) -> Result<(), VerificationError> { + fn handle_block_inner(&mut self, l2_trace: &T) -> Result { let spec_id = self.hardfork_config.get_spec_id(l2_trace.number()); dev_trace!("use spec id {spec_id:?}",); self.hardfork_config @@ -89,8 +83,10 @@ impl EvmExecutor<'_, Cod blob_excess_gas_and_price: None, }; + let mut gas_used = 0; + for (idx, tx) in l2_trace.transactions().enumerate() { - cycle_tracker_start!("handle tx {}", idx); + cycle_tracker_start!("handle tx"); dev_trace!("handle {idx}th tx"); @@ -151,7 +147,7 @@ impl EvmExecutor<'_, Cod dev_trace!("handler cfg: {:?}", revm.handler.cfg); - let _result = measure_duration_millis!( + let result = measure_duration_millis!( transact_commit_duration_milliseconds, cycle_track!(revm.transact_commit(), "transact_commit").map_err(|e| { VerificationError::EvmExecution { @@ -161,39 +157,32 @@ impl EvmExecutor<'_, Cod })? ); - dev_trace!("{_result:#?}"); + gas_used += result.gas_used(); + + dev_trace!("{result:#?}"); } self.hooks.post_tx_execution(self, idx); dev_debug!("handle {idx}th tx done"); - cycle_tracker_end!("handle tx {}", idx); + cycle_tracker_end!("handle tx"); } - Ok(()) + Ok(gas_used) } /// Commit pending changes in cache db to zktrie - pub fn commit_changes( - &mut self, - code_db: CodeDb, - zktrie_db: ZkDb, - ) -> Result { + pub fn commit_changes(&mut self) -> Result { measure_duration_millis!( commit_changes_duration_milliseconds, cycle_track!( - self.commit_changes_inner(code_db, zktrie_db) - .map_err(DatabaseError::zk_trie), + self.commit_changes_inner().map_err(DatabaseError::zk_trie), "commit_changes" ) ) } - fn commit_changes_inner( - &mut self, - mut code_db: CodeDb, - zktrie_db: ZkDb, - ) -> Result> { - let mut zktrie = ZkTrie::::new_with_root( - zktrie_db.clone(), + fn commit_changes_inner(&mut self) -> Result> { + let mut zktrie = ZkTrie::::new_with_root( + self.db.db.zktrie_db, NoCacheHasher, self.db.db.committed_zktrie_root(), ) @@ -220,7 +209,7 @@ impl EvmExecutor<'_, Cod } dev_trace!("committing {addr}, {:?} {db_acc:?}", db_acc.account_state); - cycle_tracker_start!("commit account {}", addr); + cycle_tracker_start!("commit account"); let mut storage_root = self.db.db.prev_storage_root(addr); @@ -229,10 +218,13 @@ impl EvmExecutor<'_, Cod let storage_root_before = storage_root; // get storage tire cycle_tracker_start!("update storage_tire"); - let mut storage_trie = ZkTrie::::new_with_root( - zktrie_db.clone(), - NoCacheHasher, - storage_root_before, + let mut storage_trie = cycle_track!( + ZkTrie::::new_with_root( + self.db.db.zktrie_db, + NoCacheHasher, + storage_root_before, + ), + "Zktrie::new_with_root" ) .expect("unable to get storage trie"); for (key, value) in db_acc.storage.iter() { @@ -240,7 +232,11 @@ impl EvmExecutor<'_, Cod measure_duration_micros!( zktrie_update_duration_microseconds, cycle_track!( - storage_trie.update(key.to_be_bytes::<32>(), value)?, + storage_trie.update( + self.db.db.zktrie_db, + key.to_be_bytes::<32>(), + value + )?, "Zktrie::update_store" ) ); @@ -248,7 +244,8 @@ impl EvmExecutor<'_, Cod measure_duration_micros!( zktrie_delete_duration_microseconds, cycle_track!( - storage_trie.delete(key.to_be_bytes::<32>())?, + storage_trie + .delete(self.db.db.zktrie_db, key.to_be_bytes::<32>())?, "Zktrie::delete" ) ); @@ -260,16 +257,16 @@ impl EvmExecutor<'_, Cod measure_duration_micros!( zktrie_commit_duration_microseconds, - storage_trie.commit()? + cycle_track!(storage_trie.commit(self.db.db.zktrie_db)?, "Zktrie::commit") ); cycle_tracker_end!("update storage_tire"); storage_root = *storage_trie.root().unwrap_ref(); + self.db.db.update_storage_root_cache(*addr, storage_trie); + #[cfg(feature = "debug-storage")] debug_recorder.record_storage_root(*addr, storage_root); - - self.db.db.set_prev_storage_root(*addr, storage_root); } if !info.is_empty() { // if account not exist, all fields will be zero. @@ -293,20 +290,24 @@ impl EvmExecutor<'_, Cod debug_recorder.record_account(*addr, info.clone(), storage_root); let acc_data = Account::from_revm_account_with_storage_root(info, storage_root); + dev_trace!("committing account {addr}: {acc_data:?}"); measure_duration_micros!( zktrie_update_duration_microseconds, cycle_track!( zktrie - .update(addr, acc_data) + .update(self.db.db.zktrie_db, addr, acc_data) .expect("failed to update account"), "Zktrie::update_account" ) ); - cycle_tracker_end!("commit account {}", addr); + cycle_tracker_end!("commit account"); } - measure_duration_micros!(zktrie_commit_duration_microseconds, zktrie.commit()?); + measure_duration_micros!( + zktrie_commit_duration_microseconds, + cycle_track!(zktrie.commit(self.db.db.zktrie_db)?, "Zktrie::commit") + ); let root_after = *zktrie.root().unwrap_ref(); @@ -316,7 +317,7 @@ impl EvmExecutor<'_, Cod } } -impl Debug for EvmExecutor<'_, CodeDb, ZkDb> { +impl Debug for EvmExecutor<'_, '_, CodeDb, ZkDb> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("EvmExecutor").field("db", &self.db).finish() } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 06a67a8..8c16077 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -11,7 +11,7 @@ mod database; pub use database::EvmDatabase; mod error; -pub use error::VerificationError; +pub use error::{DatabaseError, VerificationError}; mod executor; pub use executor::{hooks, EvmExecutor, EvmExecutorBuilder}; diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 908ab90..a6c8fb5 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -29,4 +29,6 @@ serde_json.workspace = true [features] dev = ["sbv-utils/dev"] +sp1 = [] +cycle-tracker = [] sled = ["zktrie-ng/sled"] \ No newline at end of file diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 57a86de..39e8382 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -9,11 +9,7 @@ use alloy::{ primitives::{Bytes, ChainId, Signature, SignatureError, TxKind}, }; use std::fmt::Debug; -use zktrie_ng::{ - db::KVDatabase, - hash::poseidon::Poseidon, - trie::{Node, MAGIC_NODE_BYTES}, -}; +use zktrie_ng::db::kv::KVDatabase; /// Predeployed contracts pub mod predeployed; @@ -25,9 +21,18 @@ 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; + +/// Node proof trait +pub trait NodeProof { + /// Import itself into zktrie db + fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error>; +} /// Blanket trait for block trace extensions. pub trait Block: Debug { + /// Node proof type + type Node: NodeProof; /// transaction type type Tx: TxTrace; @@ -75,21 +80,14 @@ pub trait Block: Debug { /// start l1 queue index fn start_l1_queue_index(&self) -> u64; - /// flatten proofs - fn flatten_proofs(&self) -> impl Iterator; + /// node proofs + fn node_proofs(&self) -> impl Iterator; /// Update zktrie state from trace #[inline] - fn build_zktrie_db(&self, db: &mut Db) -> Result<(), Db::Error> { - for (k, bytes) in self.flatten_proofs() { - if bytes == MAGIC_NODE_BYTES { - continue; - } - let node = Node::::try_from(bytes).expect("invalid node"); - let node_hash = node.get_or_calculate_node_hash().expect("infallible"); - debug_assert_eq!(k.as_slice(), node_hash.as_slice()); - dev_trace!("put zktrie node: {:?}", node); - db.put_owned(node_hash.as_slice(), node.canonical_value(false))?; + fn build_zktrie_db(&self, db: &mut NodeDb) -> Result<(), Db::Error> { + for node in self.node_proofs() { + node.import_node(db)?; } Ok(()) } @@ -192,6 +190,9 @@ pub trait TxTrace { /// Get `access_list`. fn access_list(&self) -> AccessList; + /// Get `v`. + fn v(&self) -> u64; + /// Get `signature`. fn signature(&self) -> Result; @@ -266,6 +267,7 @@ pub trait TxTrace { } impl Block for &T { + type Node = ::Node; type Tx = ::Tx; fn number(&self) -> u64 { @@ -332,8 +334,8 @@ impl Block for &T { (*self).start_l1_queue_index() } - fn flatten_proofs(&self) -> impl Iterator { - (*self).flatten_proofs() + fn node_proofs(&self) -> impl Iterator { + (*self).node_proofs() } } @@ -390,6 +392,10 @@ impl TxTrace for &T { (*self).access_list() } + fn v(&self) -> u64 { + (*self).v() + } + fn signature(&self) -> Result { (*self).signature() } diff --git a/crates/primitives/src/types/mod.rs b/crates/primitives/src/types/mod.rs index 7fd49ae..76e1a0a 100644 --- a/crates/primitives/src/types/mod.rs +++ b/crates/primitives/src/types/mod.rs @@ -1,7 +1,16 @@ -use crate::Block; +use crate::{Block, NodeProof}; use alloy::primitives::{Address, Bytes, B256, U256}; -use serde::{Deserialize, Serialize}; +use rkyv::vec::ArchivedVec; +use rkyv::{rancor, Archive}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_with::{serde_as, Map}; +use std::collections::{BTreeSet, HashMap}; +use std::fmt::Debug; +use std::hash::{Hash, Hasher}; +use zktrie_ng::db::kv::KVDatabase; +use zktrie_ng::db::NodeDb; +use zktrie_ng::hash::poseidon::Poseidon; +use zktrie_ng::trie::{ArchivedNode, Node, MAGIC_NODE_BYTES}; mod tx; pub use tx::{ArchivedTransactionTrace, TransactionTrace, TxL1Msg, TypedTransaction}; @@ -10,80 +19,140 @@ pub use tx::{ArchivedTransactionTrace, TransactionTrace, TxL1Msg, TypedTransacti #[derive( rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, Default, Debug, Clone, )] -#[archive(check_bytes)] -#[archive_attr(derive(Debug, Hash, PartialEq, Eq))] -struct BlockHeader { +#[rkyv(derive(Debug, Hash, PartialEq, Eq))] +pub struct BlockHeader { /// block number - number: U256, + #[rkyv(attr(doc = "block number"))] + pub number: U256, /// block hash - hash: B256, + #[rkyv(attr(doc = "block hash"))] + pub hash: B256, /// timestamp - timestamp: U256, + #[rkyv(attr(doc = "timestamp"))] + pub timestamp: U256, /// gas limit #[serde(rename = "gasLimit")] - gas_limit: U256, + #[rkyv(attr(doc = "gas limit"))] + pub gas_limit: U256, /// gas used #[serde(rename = "gasUsed")] - gas_used: U256, + #[rkyv(attr(doc = "gas used"))] + pub gas_used: U256, /// base fee per gas #[serde(rename = "baseFeePerGas")] - base_fee_per_gas: Option, + #[rkyv(attr(doc = "base fee per gas"))] + pub base_fee_per_gas: Option, /// difficulty - difficulty: U256, + #[rkyv(attr(doc = "difficulty"))] + pub difficulty: U256, /// mix hash + #[rkyv(attr(doc = "mix hash"))] #[serde(rename = "mixHash")] - mix_hash: Option, + pub mix_hash: Option, } /// Coinbase #[derive( rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, Default, Debug, Clone, )] -#[archive(check_bytes)] -#[archive_attr(derive(Debug, Hash, PartialEq, Eq))] -struct Coinbase { +#[rkyv(derive(Debug, Hash, PartialEq, Eq))] +pub struct Coinbase { /// address of coinbase - address: Address, + #[rkyv(attr(doc = "address of coinbase"))] + pub address: Address, } /// Bytecode trace #[derive( rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, Default, Debug, Clone, )] -#[archive(check_bytes)] -#[archive_attr(derive(Debug, Hash, PartialEq, Eq))] -struct BytecodeTrace { +#[rkyv(derive(Debug, Hash, PartialEq, Eq))] +pub struct BytecodeTrace { /// bytecode - code: Bytes, + #[rkyv(attr(doc = "bytecode"))] + pub code: Bytes, } /// storage trace -#[serde_as] #[derive( rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, - Deserialize, Default, Debug, Clone, + Hash, Eq, PartialEq, )] -#[archive(check_bytes)] -#[archive_attr(derive(Debug, Hash, PartialEq, Eq))] -struct StorageTrace { +#[rkyv(derive(Debug, Hash, PartialEq, Eq))] +pub struct StorageTrace +where + N: Archive, + Vec: Archive::Archived>>, + as Archive>::Archived: Debug + Hash + PartialEq + Eq, +{ /// root before + #[rkyv(attr(doc = "root before"))] #[serde(rename = "rootBefore")] - root_before: B256, + pub root_before: B256, /// root after + #[rkyv(attr(doc = "root after"))] #[serde(rename = "rootAfter")] - root_after: B256, + pub root_after: B256, /// proofs #[serde(rename = "flattenProofs")] + #[rkyv(attr(doc = "proofs"))] + pub flatten_proofs: Vec, +} + +/// rkyv serialized node bytes +#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Default, Debug, Clone)] +#[rkyv(derive(Debug, Hash, PartialEq, Eq))] +#[repr(C, align(4))] +pub struct ArchivedNodeBytes(Vec); + +/// legacy storage trace +#[serde_as] +#[derive( + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, + Serialize, + Deserialize, + Default, + Debug, + Clone, + Hash, + Eq, + PartialEq, +)] +#[rkyv(derive(Debug, Hash, PartialEq, Eq))] +#[allow(clippy::type_complexity)] +pub struct LegacyStorageTrace { + /// root before + #[rkyv(attr(doc = "root before"))] + #[serde(rename = "rootBefore")] + pub root_before: B256, + /// root after + #[rkyv(attr(doc = "root after"))] + #[serde(rename = "rootAfter")] + pub root_after: B256, + /// account proofs + #[rkyv(attr(doc = "account proofs"))] + #[serde(default)] #[serde_as(as = "Map<_, _>")] - flatten_proofs: Vec<(B256, Bytes)>, + pub proofs: Vec<(Address, Vec)>, + /// storage proofs for each account + #[rkyv(attr(doc = "storage proofs for each account"))] + #[serde(rename = "storageProofs", default)] + #[serde_as(as = "Map<_, Map<_, _>>")] + pub storage_proofs: Vec<(Address, Vec<(B256, Vec)>)>, + /// additional deletion proofs + #[rkyv(attr(doc = "additional deletion proofs"))] + #[serde(rename = "deletionProofs", default)] + pub deletion_proofs: Vec, } /// Block trace format @@ -92,31 +161,181 @@ struct StorageTrace { #[derive( rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Serialize, Deserialize, Default, Debug, Clone, )] -#[archive(check_bytes)] -#[archive_attr(derive(Debug, Hash, PartialEq, Eq))] -pub struct BlockTrace { +#[rkyv(derive(Debug, Hash, PartialEq, Eq))] +pub struct BlockTrace +where + S: Archive, + ::Archived: Debug + Hash + PartialEq + Eq, +{ /// chain id + #[rkyv(attr(doc = "chain id"))] #[serde(rename = "chainID", default)] - chain_id: u64, + pub chain_id: u64, /// coinbase - coinbase: Coinbase, + #[rkyv(attr(doc = "coinbase"))] + pub coinbase: Coinbase, /// block - header: BlockHeader, + #[rkyv(attr(doc = "block"))] + pub header: BlockHeader, /// txs - transactions: Vec, + #[rkyv(attr(doc = "txs"))] + pub transactions: Vec, /// bytecodes - codes: Vec, + #[rkyv(attr(doc = "bytecodes"))] + pub codes: Vec, /// storage trace BEFORE execution + #[rkyv(attr(doc = "storage trace BEFORE execution"))] #[serde(rename = "storageTrace")] - storage_trace: StorageTrace, + pub storage_trace: S, /// l1 tx queue + #[rkyv(attr(doc = "l1 tx queue"))] #[serde(rename = "startL1QueueIndex", default)] - start_l1_queue_index: u64, - /// Withdraw root - withdraw_trie_root: B256, + pub start_l1_queue_index: u64, + /// withdraw root + #[rkyv(attr(doc = "withdraw root"))] + pub withdraw_trie_root: B256, } -impl Block for BlockTrace { +impl Hash for ArchivedNodeBytes { + fn hash(&self, state: &mut H) { + state.write_usize(self.0.len()); + Hash::hash_slice(self.0.as_ref(), state) + } +} + +impl PartialEq for ArchivedNodeBytes { + fn eq(&self, other: &Self) -> bool { + self.0.as_slice().eq(other.0.as_slice()) + } +} + +impl Eq for ArchivedNodeBytes {} + +impl Serialize for ArchivedNodeBytes { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.as_slice().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for StorageTrace { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum FlattenProofs { + Map(HashMap), + Vec(Vec), + } + #[derive(Deserialize)] + struct StorageTraceDe { + #[serde(rename = "rootBefore")] + pub root_before: B256, + #[serde(rename = "rootAfter")] + pub root_after: B256, + #[serde(rename = "flattenProofs")] + pub flatten_proofs: FlattenProofs, + } + + let de = StorageTraceDe::deserialize(deserializer)?; + let mut flatten_proofs = match de.flatten_proofs { + FlattenProofs::Map(map) => map.into_values().collect(), + FlattenProofs::Vec(vec) => vec, + }; + flatten_proofs.sort(); + + Ok(StorageTrace { + root_before: de.root_before, + root_after: de.root_after, + flatten_proofs, + }) + } +} + +impl From for StorageTrace { + fn from(trace: StorageTrace) -> Self { + StorageTrace { + root_before: trace.root_before, + root_after: trace.root_after, + flatten_proofs: trace + .flatten_proofs + .into_iter() + .filter(|proof| proof.as_ref() != MAGIC_NODE_BYTES) + .map(|proof| { + ArchivedNodeBytes( + Node::::try_from(proof.as_ref()) + .expect("invalid node") + .archived() + .to_vec(), + ) + }) + .collect(), + } + } +} + +impl From for StorageTrace { + fn from(trace: LegacyStorageTrace) -> Self { + let mut flatten_proofs = BTreeSet::new(); + for (_, proofs) in trace.proofs { + flatten_proofs.extend(proofs); + } + for (_, proofs) in trace.storage_proofs { + for (_, proofs) in proofs { + flatten_proofs.extend(proofs); + } + } + flatten_proofs.extend(trace.deletion_proofs); + + StorageTrace { + root_before: trace.root_before, + root_after: trace.root_after, + flatten_proofs: flatten_proofs.into_iter().collect(), + } + } +} + +impl From for BlockTrace> { + fn from(trace: BlockTrace) -> Self { + 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, + } + } +} + +impl From> for BlockTrace { + fn from(trace: BlockTrace) -> Self { + 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, + } + } +} + +impl Block for BlockTrace +where + S: StorageTraceExt + Archive + Debug, + ::Archived: Debug + Hash + PartialEq + Eq, + ::Node: NodeProof, +{ + type Node = S::Node; type Tx = TransactionTrace; fn number(&self) -> u64 { @@ -160,11 +379,11 @@ impl Block for BlockTrace { } fn root_before(&self) -> B256 { - self.storage_trace.root_before + self.storage_trace.root_before() } fn root_after(&self) -> B256 { - self.storage_trace.root_after + self.storage_trace.root_after() } fn withdraw_root(&self) -> B256 { @@ -179,55 +398,59 @@ impl Block for BlockTrace { self.start_l1_queue_index } - fn flatten_proofs(&self) -> impl Iterator { - self.storage_trace - .flatten_proofs - .iter() - .map(|(k, v)| (k, v.as_ref())) + fn node_proofs(&self) -> impl Iterator { + self.storage_trace.node_proofs() } } -impl Block for ArchivedBlockTrace { +impl Block for ArchivedBlockTrace +where + S: Archive + Debug, + ::Archived: StorageTraceExt + Debug + Hash + PartialEq + Eq, + <::Archived as StorageTraceExt>::Node: NodeProof, +{ + type Node = <::Archived as StorageTraceExt>::Node; type Tx = ArchivedTransactionTrace; fn number(&self) -> u64 { - self.header.number.to() + let number: U256 = self.header.number.into(); + number.to() } fn block_hash(&self) -> B256 { - self.header.hash + self.header.hash.into() } fn chain_id(&self) -> u64 { - self.chain_id + self.chain_id.into() } fn coinbase(&self) -> Address { - self.coinbase.address + self.coinbase.address.into() } fn timestamp(&self) -> U256 { - self.header.timestamp + self.header.timestamp.into() } fn gas_limit(&self) -> U256 { - self.header.gas_limit + self.header.gas_limit.into() } fn gas_used(&self) -> U256 { - self.header.gas_used + self.header.gas_used.into() } fn base_fee_per_gas(&self) -> Option { - self.header.base_fee_per_gas.as_ref().copied() + self.header.base_fee_per_gas.as_ref().map(|p| p.into()) } fn difficulty(&self) -> U256 { - self.header.difficulty + self.header.difficulty.into() } fn prevrandao(&self) -> Option { - self.header.mix_hash.as_ref().copied() + self.header.mix_hash.as_ref().map(|p| p.into()) } fn transactions(&self) -> impl Iterator { @@ -235,15 +458,15 @@ impl Block for ArchivedBlockTrace { } fn root_before(&self) -> B256 { - self.storage_trace.root_before + self.storage_trace.root_before() } fn root_after(&self) -> B256 { - self.storage_trace.root_after + self.storage_trace.root_after() } fn withdraw_root(&self) -> B256 { - self.withdraw_trie_root + self.withdraw_trie_root.into() } fn codes(&self) -> impl ExactSizeIterator { @@ -251,14 +474,150 @@ impl Block for ArchivedBlockTrace { } fn start_l1_queue_index(&self) -> u64 { - self.start_l1_queue_index + self.start_l1_queue_index.into() } - fn flatten_proofs(&self) -> impl Iterator { - self.storage_trace - .flatten_proofs - .iter() - .map(|(k, v)| (k, v.as_ref())) + fn node_proofs(&self) -> impl Iterator { + self.storage_trace.node_proofs() + } +} + +/// Extension trait for storage trace +pub trait StorageTraceExt { + /// Node type + type Node: Debug; + + /// Get root before + fn root_before(&self) -> B256; + + /// Get root after + fn root_after(&self) -> B256; + + /// Get node proofs + fn node_proofs(&self) -> impl Iterator; +} + +impl StorageTraceExt for StorageTrace +where + N: Archive + Debug, + Vec: Archive::Archived>>, + as Archive>::Archived: Debug + Hash + PartialEq + Eq, +{ + type Node = N; + + fn root_before(&self) -> B256 { + self.root_before + } + + fn root_after(&self) -> B256 { + self.root_after + } + + fn node_proofs(&self) -> impl Iterator { + self.flatten_proofs.iter() + } +} + +impl StorageTraceExt for ArchivedStorageTrace +where + N: Archive + Debug, + ::Archived: Debug, + Vec: Archive::Archived>>, + as Archive>::Archived: Debug + Hash + PartialEq + Eq, +{ + type Node = ::Archived; + + fn root_before(&self) -> B256 { + self.root_before.into() + } + + fn root_after(&self) -> B256 { + self.root_after.into() + } + + fn node_proofs(&self) -> impl Iterator { + self.flatten_proofs.as_ref().iter() + } +} + +impl StorageTraceExt for LegacyStorageTrace { + type Node = Bytes; + + fn root_before(&self) -> B256 { + self.root_before + } + + fn root_after(&self) -> B256 { + self.root_after + } + + fn node_proofs(&self) -> impl Iterator { + self.proofs.iter().flat_map(|(_, proofs)| proofs.iter()) + } +} + +fn import_serialized_node, Db: KVDatabase>( + node: N, + db: &mut NodeDb, +) -> 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") +} + +fn import_archived_node, Db: KVDatabase>( + node: N, + db: &mut NodeDb, +) -> Result<(), Db::Error> { + let bytes = node.as_ref(); + let node = cycle_track!( + rkyv::access::(bytes), + "rkyv::access" + ) + .expect("invalid node"); + let node_hash = 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()) }, + "NodeDb::put_archived_node_unchecked" + ) +} + +impl NodeProof for Bytes { + fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error> { + import_serialized_node(self, db) + } +} + +impl NodeProof for ArchivedVec { + fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error> { + import_serialized_node(self, db) + } +} + +impl NodeProof for ArchivedNodeBytes { + fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error> { + import_archived_node(&self.0, db) + } +} + +impl NodeProof for ArchivedArchivedNodeBytes { + fn import_node(&self, db: &mut NodeDb) -> Result<(), Db::Error> { + import_archived_node(&self.0, db) } } @@ -428,23 +787,42 @@ mod tests { fn test_rkyv() { let trace = serde_json::from_str::(TRACE).unwrap()["result"].clone(); let block: BlockTrace = serde_json::from_value(trace).unwrap(); - let archived_bytes = rkyv::to_bytes::<_, 4096>(&block).unwrap(); + let archived_bytes = rkyv::to_bytes::(&block).unwrap(); let archived_block = - rkyv::check_archived_root::(archived_bytes.as_ref()).unwrap(); + rkyv::access::(archived_bytes.as_ref()).unwrap(); assert_eq!(block.chain_id, archived_block.chain_id); - assert_eq!(block.coinbase.address, archived_block.coinbase.address); + assert_eq!( + block.coinbase.address, + Address::from(archived_block.coinbase.address) + ); - assert_eq!(block.header.number, archived_block.header.number); - assert_eq!(block.header.hash, archived_block.header.hash); - assert_eq!(block.header.timestamp, archived_block.header.timestamp); - assert_eq!(block.header.gas_limit, archived_block.header.gas_limit); + assert_eq!(block.header.number, archived_block.header.number.into()); + assert_eq!(block.header.hash, B256::from(archived_block.header.hash)); + assert_eq!( + block.header.timestamp, + archived_block.header.timestamp.into() + ); + assert_eq!( + block.header.gas_limit, + archived_block.header.gas_limit.into() + ); assert_eq!( block.header.base_fee_per_gas, - archived_block.header.base_fee_per_gas + archived_block + .header + .base_fee_per_gas + .as_ref() + .map(|p| p.into()) + ); + assert_eq!( + block.header.difficulty, + archived_block.header.difficulty.into() + ); + assert_eq!( + block.header.mix_hash, + archived_block.header.mix_hash.as_ref().map(|p| p.into()) ); - assert_eq!(block.header.difficulty, archived_block.header.difficulty); - assert_eq!(block.header.mix_hash, archived_block.header.mix_hash); let txs = block .transactions @@ -464,11 +842,11 @@ mod tests { assert_eq!( block.storage_trace.root_before, - archived_block.storage_trace.root_before + B256::from(archived_block.storage_trace.root_before) ); assert_eq!( block.storage_trace.root_after, - archived_block.storage_trace.root_after + B256::from(archived_block.storage_trace.root_after) ); for (proof, archived_proof) in block .storage_trace @@ -476,8 +854,7 @@ mod tests { .iter() .zip(archived_block.storage_trace.flatten_proofs.iter()) { - assert_eq!(proof.0, archived_proof.0); - assert_eq!(proof.1.as_ref(), archived_proof.1.as_ref()); + assert_eq!(proof.as_ref(), archived_proof.as_ref()); } assert_eq!( diff --git a/crates/primitives/src/types/tx.rs b/crates/primitives/src/types/tx.rs index eb8daf8..292aac6 100644 --- a/crates/primitives/src/types/tx.rs +++ b/crates/primitives/src/types/tx.rs @@ -7,6 +7,7 @@ use alloy::{ primitives::{Address, Bytes, ChainId, Signature, SignatureError, TxKind, B256, U256, U64}, rlp::{BufMut, BytesMut, Encodable, Header}, }; +use rkyv::rancor; use serde_with::{serde_as, DefaultOnNull}; /// Wrapped Ethereum Transaction @@ -61,52 +62,69 @@ pub struct TxL1Msg { Debug, Clone, )] -#[archive(check_bytes)] -#[archive_attr(derive(Debug, Hash, PartialEq, Eq))] +#[rkyv(attr(doc = "Archived `TransactionTrace`"))] +#[rkyv(derive(Debug, Hash, PartialEq, Eq))] pub struct TransactionTrace { /// tx hash + #[rkyv(attr(doc = "tx hash"))] #[serde(default, rename = "txHash")] - pub(crate) tx_hash: B256, + pub tx_hash: B256, /// tx type (in raw from) + #[rkyv(attr(doc = "tx type (in raw from)"))] #[serde(rename = "type")] - pub(crate) ty: u8, + pub ty: u8, /// nonce - pub(crate) nonce: u64, + #[rkyv(attr(doc = "nonce"))] + pub nonce: u64, /// gas limit - pub(crate) gas: u64, - #[serde(rename = "gasPrice")] + #[rkyv(attr(doc = "gas limit"))] + pub gas: u64, /// gas price - pub(crate) gas_price: U256, - #[serde(rename = "gasTipCap")] + #[rkyv(attr(doc = "gas price"))] + #[serde(rename = "gasPrice")] + pub gas_price: U256, /// gas tip cap - pub(crate) gas_tip_cap: Option, - #[serde(rename = "gasFeeCap")] + #[rkyv(attr(doc = "gas tip cap"))] + #[serde(rename = "gasTipCap")] + pub gas_tip_cap: Option, /// gas fee cap - pub(crate) gas_fee_cap: Option, + #[rkyv(attr(doc = "gas fee cap"))] + #[serde(rename = "gasFeeCap")] + pub gas_fee_cap: Option, /// from - pub(crate) from: Address, + #[rkyv(attr(doc = "from"))] + pub from: Address, /// to, NONE for creation (0 addr) - pub(crate) to: Option
, + #[rkyv(attr(doc = "to, NONE for creation (0 addr)"))] + pub to: Option
, /// chain id + #[rkyv(attr(doc = "chain id"))] #[serde(rename = "chainId")] - pub(crate) chain_id: U64, + pub chain_id: U64, /// value amount - pub(crate) value: U256, + #[rkyv(attr(doc = "value amount"))] + pub value: U256, /// call data - pub(crate) data: Bytes, + #[rkyv(attr(doc = "call data"))] + pub data: Bytes, /// is creation + #[rkyv(attr(doc = "is creation"))] #[serde(rename = "isCreate")] - pub(crate) is_create: bool, + pub is_create: bool, /// access list + #[rkyv(attr(doc = "access list"))] #[serde(rename = "accessList")] #[serde_as(as = "DefaultOnNull")] - pub(crate) access_list: AccessList, + pub access_list: AccessList, /// signature v - pub(crate) v: U64, + #[rkyv(attr(doc = "signature v"))] + pub v: U64, /// signature r - pub(crate) r: U256, + #[rkyv(attr(doc = "signature r"))] + pub r: U256, /// signature s - pub(crate) s: U256, + #[rkyv(attr(doc = "signature s"))] + pub s: U256, } impl TxTrace for TransactionTrace { @@ -172,6 +190,10 @@ impl TxTrace for TransactionTrace { self.access_list.clone() } + fn v(&self) -> u64 { + self.v.to() + } + fn signature(&self) -> Result { Signature::from_rs_and_parity(self.r, self.s, self.v) } @@ -179,7 +201,7 @@ impl TxTrace for TransactionTrace { impl TxTrace for ArchivedTransactionTrace { fn tx_hash(&self) -> B256 { - self.tx_hash + self.tx_hash.into() } fn ty(&self) -> u8 { @@ -187,47 +209,55 @@ impl TxTrace for ArchivedTransactionTrace { } fn nonce(&self) -> u64 { - self.nonce + self.nonce.into() } fn gas_limit(&self) -> u128 { - self.gas as u128 + u64::from(self.gas) as u128 } fn gas_price(&self) -> u128 { - self.gas_price.to() + let gas_price: U256 = self.gas_price.into(); + gas_price.to() } fn max_fee_per_gas(&self) -> u128 { self.gas_fee_cap .as_ref() - .map(|v| v.to()) + .map(|g| { + let gas_fee_cap: U256 = g.into(); + gas_fee_cap.to() + }) .unwrap_or_default() } fn max_priority_fee_per_gas(&self) -> u128 { self.gas_tip_cap .as_ref() - .map(|v| v.to()) + .map(|g| { + let gas_tip_cap: U256 = g.into(); + gas_tip_cap.to() + }) .unwrap_or_default() } unsafe fn get_from_unchecked(&self) -> Address { - self.from + self.from.into() } fn to(&self) -> TxKind { if self.is_create { TxKind::Create } else { - debug_assert!(self.to.as_ref().map(|a| !a.is_zero()).unwrap_or(false)); - TxKind::Call(*self.to.as_ref().expect("to address must be present")) + let to: Address = self.to.as_ref().expect("to address must be present").into(); + debug_assert!(!to.is_zero()); + TxKind::Call(to) } } fn chain_id(&self) -> Option { let chain_id: ChainId = self.chain_id.to(); - if self.ty == 0 && chain_id < 35 { + if self.ty == 0 && self.v() < 35 { None } else { Some(chain_id) @@ -235,7 +265,7 @@ impl TxTrace for ArchivedTransactionTrace { } fn value(&self) -> U256 { - self.value + self.value.into() } fn data(&self) -> Bytes { @@ -243,12 +273,17 @@ impl TxTrace for ArchivedTransactionTrace { } fn access_list(&self) -> AccessList { - rkyv::Deserialize::::deserialize(&self.access_list, &mut rkyv::Infallible) - .unwrap() + rkyv::deserialize::<_, rancor::Error>(&self.access_list).unwrap() + } + + fn v(&self) -> u64 { + let v: U64 = self.v.into(); + v.to() } fn signature(&self) -> Result { - Signature::from_rs_and_parity(self.r, self.s, self.v) + let v: U64 = self.v.into(); + Signature::from_rs_and_parity(self.r.into(), self.s.into(), v) } } diff --git a/crates/sbv/Cargo.toml b/crates/sbv/Cargo.toml index 9dde571..8cdf622 100644 --- a/crates/sbv/Cargo.toml +++ b/crates/sbv/Cargo.toml @@ -22,6 +22,6 @@ metrics = ["sbv-core/metrics", "sbv-utils/metrics"] sled = ["sbv-primitives/sled"] # sp1 related -sp1 = ["sbv-core/sp1"] -cycle-tracker = ["sbv-core/cycle-tracker"] +sp1 = ["sbv-core/sp1", "sbv-primitives/sp1"] +cycle-tracker = ["sbv-core/cycle-tracker", "sbv-primitives/cycle-tracker"] ordered-db = ["sbv-core/ordered-db"] \ No newline at end of file diff --git a/crates/utils/src/macros.rs b/crates/utils/src/macros.rs index f8f1af1..c3b21d2 100644 --- a/crates/utils/src/macros.rs +++ b/crates/utils/src/macros.rs @@ -39,8 +39,8 @@ macro_rules! cycle_tracker_end { #[macro_export] macro_rules! dev_trace { ($($arg:tt)*) => { - #[cfg(any(feature = "dev", test))] { + #[cfg(any(feature = "dev", test))] $crate::tracing::trace!($($arg)*); } }; @@ -50,8 +50,8 @@ macro_rules! dev_trace { #[macro_export] macro_rules! dev_info { ($($arg:tt)*) => { - #[cfg(any(feature = "dev", test))] { + #[cfg(any(feature = "dev", test))] $crate::tracing::info!($($arg)*); } }; @@ -61,8 +61,8 @@ macro_rules! dev_info { #[macro_export] macro_rules! dev_error { ($($arg:tt)*) => { - #[cfg(any(feature = "dev", test))] { + #[cfg(any(feature = "dev", test))] $crate::tracing::error!($($arg)*); } }; @@ -72,8 +72,8 @@ macro_rules! dev_error { #[macro_export] macro_rules! dev_debug { ($($arg:tt)*) => { - #[cfg(any(feature = "dev", test))] { + #[cfg(any(feature = "dev", test))] $crate::tracing::debug!($($arg)*); } }; @@ -83,8 +83,8 @@ macro_rules! dev_debug { #[macro_export] macro_rules! dev_warn { ($($arg:tt)*) => { - #[cfg(any(feature = "dev", test))] { + #[cfg(any(feature = "dev", test))] $crate::tracing::warn!($($arg)*); } };