diff --git a/Cargo.lock b/Cargo.lock index 8e69ac136f5..734f41e364d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3379,6 +3379,7 @@ dependencies = [ "rkyv", "secp256k1", "serde", + "serde_bytes", "serde_json", "sha2", "sha3", @@ -3821,6 +3822,7 @@ dependencies = [ "lazy_static", "proptest", "rand 0.8.5", + "rkyv", "rocksdb", "serde", "serde_json", diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index bfe33e4c3d4..922b1205e4c 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -7,6 +7,7 @@ mod smoke_test; pub mod tracing; pub mod vm; +use ethrex_common::rkyv_utils::SizedNode; use ::tracing::{debug, info}; use constants::{MAX_INITCODE_SIZE, MAX_TRANSACTION_DATA_SIZE, POST_OSAKA_GAS_LIMIT_CAP}; use error::MempoolError; @@ -397,7 +398,7 @@ impl Blockchain { ChainError::WitnessGeneration("Failed to lock storage trie witness".to_string()) })?; let witness = std::mem::take(&mut *witness); - let witness = witness.into_iter().collect::>(); + let witness = witness.into_values().collect::>(); used_trie_nodes.extend_from_slice(&witness); touched_account_storage_slots.entry(address).or_default(); } @@ -426,20 +427,22 @@ impl Blockchain { })? .iter() { - accumulated_state_trie_witness.insert(state_trie_witness.clone()); + accumulated_state_trie_witness + .insert(state_trie_witness.0.clone(), state_trie_witness.1.clone()); } current_trie_witness = new_state_trie_witness; } - used_trie_nodes - .extend_from_slice(&Vec::from_iter(accumulated_state_trie_witness.into_iter())); + used_trie_nodes.extend_from_slice(&Vec::from_iter( + accumulated_state_trie_witness.into_values(), + )); // If the witness is empty at least try to store the root if used_trie_nodes.is_empty() && let Some(root) = root_node { - used_trie_nodes.push(root.encode_to_vec()); + used_trie_nodes.push((*root).clone()); } let mut needed_block_numbers = block_hashes.keys().collect::>(); @@ -479,7 +482,7 @@ impl Blockchain { let chain_config = self.storage.get_chain_config(); - let nodes = used_trie_nodes.into_iter().collect::>(); + let nodes = used_trie_nodes.into_iter().map(SizedNode::from).collect::>(); let mut keys = Vec::new(); @@ -497,6 +500,7 @@ impl Blockchain { chain_config, nodes, keys, + guest_program_state: None }) } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 59438e6d10d..06841a81509 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -31,6 +31,7 @@ lazy_static.workspace = true rayon.workspace = true url.workspace = true rkyv.workspace = true +serde_bytes = "0.11.9" [dev-dependencies] hex-literal.workspace = true diff --git a/crates/common/rkyv_utils.rs b/crates/common/rkyv_utils.rs index e873663b181..287957e855f 100644 --- a/crates/common/rkyv_utils.rs +++ b/crates/common/rkyv_utils.rs @@ -1,5 +1,9 @@ use bytes::Bytes; use ethereum_types::{Bloom, H160, H256, U256}; +use ethrex_trie::{ + Nibbles, Node, NodeHash, NodeRef, ValueRLP, + node::{BranchNode, ExtensionNode, LeafNode}, +}; use rkyv::{ Archive, Archived, Deserialize, Serialize, rancor::{Fallible, Source}, @@ -258,6 +262,79 @@ where } } +/// Node representation that only references childs via hashes instead of pointers. +#[derive(Archive, serde::Serialize, serde::Deserialize, Serialize, Deserialize, Clone)] +pub enum SizedNode { + Branch { + choices: [NodeHash; 16], + value: ValueRLP, // 0 + }, + Extension { + child: NodeHash, + prefix: [u8; 32], // max 32 bytes + }, + Leaf { + partial: [u8; 32], // max 32 bytes + #[serde(with = "serde_bytes")] + value: [u8; 108], // max 108 bytes for an account state + }, +} + +impl From for Node { + fn from(value: SizedNode) -> Self { + match value { + SizedNode::Branch { choices, value } => { + let mut ref_choices = Vec::with_capacity(16); + for choice in choices { + ref_choices.push(NodeRef::Hash(choice)); + } + let choices = ref_choices.try_into().unwrap(); + Node::Branch(Box::new(BranchNode { choices, value })) + } + SizedNode::Extension { child, prefix } => { + let child = NodeRef::Hash(child); + let prefix = Nibbles::from_bytes(&prefix); + Node::Extension(ExtensionNode { child, prefix }) + } + SizedNode::Leaf { partial, value } => { + let partial = Nibbles::from_bytes(&partial); + let value = Vec::from(value); + Node::Leaf(LeafNode { partial, value }) + } + } + } +} + +impl From for SizedNode { + fn from(value: Node) -> Self { + match value { + Node::Branch(n) => { + let BranchNode { choices, value } = *n; + let mut ref_choices = Vec::with_capacity(16); + for choice in choices { + ref_choices.push(choice.compute_hash()); + } + let choices = ref_choices.try_into().unwrap(); + SizedNode::Branch { choices, value } + } + Node::Extension(ExtensionNode { child, prefix }) => { + let child = child.compute_hash(); + let mut prefix = prefix.into_vec(); + prefix.resize(32, 0); + SizedNode::Extension { child, prefix: prefix.try_into().unwrap() } + } + Node::Leaf(LeafNode { partial, mut value }) => { + let mut partial = partial.into_vec(); + value.resize(108, 0); + partial.resize(32, 0); + let partial = partial.try_into().unwrap(); + let value = value.try_into().unwrap(); + SizedNode::Leaf { partial, value } + }, + } + } +} + #[cfg(test)] mod test { use ethereum_types::{H160, H256}; diff --git a/crates/common/trie/Cargo.toml b/crates/common/trie/Cargo.toml index 8c9c95b4912..b2f3fca2d93 100644 --- a/crates/common/trie/Cargo.toml +++ b/crates/common/trie/Cargo.toml @@ -23,6 +23,7 @@ smallvec = { version = "1.10.0", features = ["const_generics", "union"] } digest = "0.10.6" lazy_static.workspace = true crossbeam.workspace = true +rkyv.workspace = true [features] default = [] diff --git a/crates/common/trie/db.rs b/crates/common/trie/db.rs index ee86f0c7d1a..215da466ffd 100644 --- a/crates/common/trie/db.rs +++ b/crates/common/trie/db.rs @@ -59,23 +59,6 @@ impl InMemoryTrieDB { } } - pub fn from_nodes( - root_hash: H256, - state_nodes: &BTreeMap, - ) -> Result { - let mut embedded_root = Trie::get_embedded_root(state_nodes, root_hash)?; - let mut hashed_nodes = vec![]; - embedded_root.commit(Nibbles::default(), &mut hashed_nodes); - - let hashed_nodes = hashed_nodes - .into_iter() - .map(|(k, v)| (k.into_vec(), v)) - .collect(); - - let in_memory_trie = Arc::new(Mutex::new(hashed_nodes)); - Ok(Self::new(in_memory_trie)) - } - fn apply_prefix(&self, path: Nibbles) -> Nibbles { match &self.prefix { Some(prefix) => prefix.concat(&path), diff --git a/crates/common/trie/logger.rs b/crates/common/trie/logger.rs index 8bb24885e2f..00eeb9083cb 100644 --- a/crates/common/trie/logger.rs +++ b/crates/common/trie/logger.rs @@ -1,13 +1,13 @@ use std::{ - collections::HashSet, + collections::HashMap, sync::{Arc, Mutex}, }; -use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; +use ethrex_rlp::decode::RLPDecode; -use crate::{Nibbles, Node, Trie, TrieDB, TrieError}; +use crate::{Nibbles, Node, NodeHash, Trie, TrieDB, TrieError}; -pub type TrieWitness = Arc>>>; +pub type TrieWitness = Arc>>; pub struct TrieLogger { inner_db: Box, @@ -15,7 +15,7 @@ pub struct TrieLogger { } impl TrieLogger { - pub fn get_witness(&self) -> Result>, TrieError> { + pub fn get_witness(&self) -> Result, TrieError> { let lock = self.witness.lock().map_err(|_| TrieError::LockError)?; Ok(lock.clone()) } @@ -23,7 +23,7 @@ impl TrieLogger { pub fn open_trie(trie: Trie) -> (TrieWitness, Trie) { let root = trie.hash_no_commit(); let db = trie.db; - let witness = Arc::new(Mutex::new(HashSet::new())); + let witness = Arc::new(Mutex::new(HashMap::new())); let logger = TrieLogger { inner_db: db, witness: witness.clone(), @@ -39,7 +39,7 @@ impl TrieDB for TrieLogger { && let Ok(decoded) = Node::decode(result) { let mut lock = self.witness.lock().map_err(|_| TrieError::LockError)?; - lock.insert(decoded.encode_to_vec()); + lock.insert(decoded.compute_hash(), decoded); } Ok(result) } diff --git a/crates/common/trie/nibbles.rs b/crates/common/trie/nibbles.rs index 6d17e488519..61c5e86192c 100644 --- a/crates/common/trie/nibbles.rs +++ b/crates/common/trie/nibbles.rs @@ -1,4 +1,5 @@ use std::{cmp, mem}; +use rkyv::{Archive, Deserialize as RDeserialize, Serialize as RSerialize}; use ethrex_rlp::{ decode::RLPDecode, @@ -6,11 +7,12 @@ use ethrex_rlp::{ error::RLPDecodeError, structs::{Decoder, Encoder}, }; +use serde::{Deserialize, Serialize}; // TODO: move path-tracking logic somewhere else // PERF: try using a stack-allocated array /// Struct representing a list of nibbles (half-bytes) -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, RDeserialize, RSerialize, Archive, Serialize, Deserialize)] pub struct Nibbles { data: Vec, /// Parts of the path that have already been consumed (used for tracking diff --git a/crates/common/trie/node.rs b/crates/common/trie/node.rs index b7a9650201e..e79678e3bc5 100644 --- a/crates/common/trie/node.rs +++ b/crates/common/trie/node.rs @@ -2,12 +2,17 @@ mod branch; mod extension; mod leaf; -use std::sync::{Arc, OnceLock}; +use std::{ + collections::BTreeMap, + sync::{Arc, OnceLock}, +}; pub use branch::BranchNode; +use ethereum_types::H256; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; pub use extension::ExtensionNode; pub use leaf::LeafNode; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{TrieDB, error::TrieError, nibbles::Nibbles}; @@ -130,6 +135,42 @@ impl NodeRef { hash.take(); } } + + pub fn resolve_subtrie( + &mut self, + all_nodes: &mut BTreeMap>, + ) -> Result<(), TrieError> { + // this will be a reference in the future, to avoid the memcpy here + let finalized_hash = self.compute_hash().finalize(); + + // remove and later re-insert instead of get_mut because we can't keep the mut borrow + // maybe there is a better solution, this remove might be expensive (memmove) + let Some(mut node_ref) = all_nodes.remove(&finalized_hash) else { + return Ok(()); + }; + + match Arc::make_mut(&mut node_ref) { + Node::Branch(node) => { + for choice in &mut node.choices { + if let NodeRef::Hash(hash) = choice + && hash.is_valid() + { + choice.resolve_subtrie(all_nodes)?; + } + } + } + Node::Extension(node) => { + if let NodeRef::Hash(_) = node.child { + node.child.resolve_subtrie(all_nodes)?; + } + } + Node::Leaf(_) => {} + }; + + *self = node_ref.clone().into(); + all_nodes.insert(finalized_hash, node_ref); + Ok(()) + } } impl Default for NodeRef { @@ -144,6 +185,12 @@ impl From for NodeRef { } } +impl From> for NodeRef { + fn from(value: Arc) -> Self { + Self::Node(value, OnceLock::new()) + } +} + impl From for NodeRef { fn from(value: NodeHash) -> Self { Self::Hash(value) @@ -173,8 +220,8 @@ impl From for ValueOrHash { } } -/// A Node in an Ethereum Compatible Patricia Merkle Trie #[derive(Debug, Clone, PartialEq)] +/// A Node in an Ethereum Compatible Patricia Merkle Trie pub enum Node { Branch(Box), Extension(ExtensionNode), @@ -289,3 +336,23 @@ pub enum NodeRemoveResult { Mutated, New(Node), } + +impl Serialize for Node { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let rlp = self.encode_to_vec(); + serializer.serialize_bytes(&rlp) + } +} +impl<'de> Deserialize<'de> for Node { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + let rlp: Vec = Deserialize::deserialize(d)?; + Node::decode(&rlp) + .map_err(|e| serde::de::Error::custom(format!("could not decode rlp bytes: {e}"))) + } +} diff --git a/crates/common/trie/node/leaf.rs b/crates/common/trie/node/leaf.rs index 4d1aa2383eb..7223dc8dbe9 100644 --- a/crates/common/trie/node/leaf.rs +++ b/crates/common/trie/node/leaf.rs @@ -13,7 +13,7 @@ use crate::{ use super::{ExtensionNode, Node, ValueOrHash}; /// Leaf Node of an an Ethereum Compatible Patricia Merkle Trie /// Contains the node's hash, value & path -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct LeafNode { pub partial: Nibbles, pub value: ValueRLP, diff --git a/crates/common/trie/node_hash.rs b/crates/common/trie/node_hash.rs index f7743bdf205..b834cf332c4 100644 --- a/crates/common/trie/node_hash.rs +++ b/crates/common/trie/node_hash.rs @@ -1,14 +1,18 @@ use ethereum_types::H256; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, error::RLPDecodeError, structs::Encoder}; +use rkyv::{Archive, Deserialize as RDeserialize, Serialize as RSerialize}; +use serde::{Deserialize, Serialize}; use sha3::{Digest, Keccak256}; /// Struct representing a trie node hash /// If the encoded node is less than 32 bits, contains the encoded node itself // TODO: Check if we can omit the Inline variant, as nodes will always be bigger than 32 bits in our use case // TODO: Check if making this `Copy` can make the code less verbose at a reasonable performance cost -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive( + Debug, Clone, Copy, PartialEq, Hash, PartialOrd, Ord, Eq, RSerialize, RDeserialize, Archive, Serialize, Deserialize +)] pub enum NodeHash { - Hashed(H256), + Hashed(#[rkyv(with=crate::rkyv_utils::H256Wrapper)] H256), // Inline is always len < 32. We need to store the length of the data, a u8 is enough. Inline(([u8; 31], u8)), } diff --git a/crates/common/trie/rkyv_utils.rs b/crates/common/trie/rkyv_utils.rs new file mode 100644 index 00000000000..e10fdeed09d --- /dev/null +++ b/crates/common/trie/rkyv_utils.rs @@ -0,0 +1,41 @@ +use ethereum_types::H256; +use rkyv::{Archive, Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; + +#[derive( + Archive, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, +)] +#[rkyv(remote = H256)] +pub struct H256Wrapper([u8; 32]); + +impl From for H256 { + fn from(value: H256Wrapper) -> Self { + Self(value.0) + } +} + +impl PartialEq for ArchivedH256Wrapper { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl PartialOrd for ArchivedH256Wrapper { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ArchivedH256Wrapper { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl Eq for ArchivedH256Wrapper {} + +impl Hash for ArchivedH256Wrapper { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} diff --git a/crates/common/trie/trie.rs b/crates/common/trie/trie.rs index c5df4d3f58d..978799698b2 100644 --- a/crates/common/trie/trie.rs +++ b/crates/common/trie/trie.rs @@ -4,6 +4,7 @@ pub mod logger; mod nibbles; pub mod node; mod node_hash; +pub mod rkyv_utils; mod rlp; #[cfg(test)] mod test_utils; @@ -55,6 +56,17 @@ pub struct Trie { pending_removal: HashSet, } +// temp +impl Clone for Trie { + fn clone(&self) -> Self { + Trie { + db: Box::new(InMemoryTrieDB::new_empty()), + root: self.root.clone(), + pending_removal: self.pending_removal.clone() + } + } +} + impl Default for Trie { fn default() -> Self { Self::new_temp() @@ -295,59 +307,16 @@ impl Trie { /// Gets node with embedded references to child nodes, all in just one `Node`. pub fn get_embedded_root( - all_nodes: &BTreeMap>, + all_nodes: &mut BTreeMap>, root_hash: H256, ) -> Result { // If the root hash is of the empty trie then we can get away by setting the NodeRef to default if root_hash == *EMPTY_TRIE_HASH { return Ok(NodeRef::default()); } - - let root_rlp = all_nodes.get(&root_hash).ok_or_else(|| { - TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(root_hash))) - })?; - - fn get_embedded_node( - all_nodes: &BTreeMap>, - cur_node_rlp: &[u8], - ) -> Result { - let cur_node = Node::decode(cur_node_rlp)?; - - Ok(match cur_node { - Node::Branch(mut node) => { - for choice in &mut node.choices { - let NodeRef::Hash(hash) = *choice else { - unreachable!() - }; - - if hash.is_valid() { - *choice = match all_nodes.get(&hash.finalize()) { - Some(rlp) => get_embedded_node(all_nodes, rlp)?.into(), - None => hash.into(), - }; - } - } - - (*node).into() - } - Node::Extension(mut node) => { - let NodeRef::Hash(hash) = node.child else { - unreachable!() - }; - - node.child = match all_nodes.get(&hash.finalize()) { - Some(rlp) => get_embedded_node(all_nodes, rlp)?.into(), - None => hash.into(), - }; - - node.into() - } - Node::Leaf(node) => node.into(), - }) - } - - let root = get_embedded_node(all_nodes, root_rlp)?; - Ok(root.into()) + let mut root = NodeRef::Hash(root_hash.into()); + root.resolve_subtrie(all_nodes)?; + Ok(root) } /// Builds a trie from a set of nodes with an empty InMemoryTrieDB as a backend because the nodes are embedded in the root. @@ -359,7 +328,7 @@ impl Trie { /// root node are considered dangling. pub fn from_nodes( root_hash: H256, - state_nodes: &BTreeMap, + state_nodes: &mut BTreeMap>, ) -> Result { let mut trie = Trie::new(Box::new(InMemoryTrieDB::default())); let root = Self::get_embedded_root(state_nodes, root_hash)?; diff --git a/crates/common/types/block_execution_witness.rs b/crates/common/types/block_execution_witness.rs index 3f950a097f0..3c2107355a6 100644 --- a/crates/common/types/block_execution_witness.rs +++ b/crates/common/types/block_execution_witness.rs @@ -1,18 +1,21 @@ use std::collections::BTreeMap; use std::fmt; use std::str::FromStr; +use std::sync::Arc; +use crate::rkyv_utils::SizedNode; use crate::types::{Block, Code}; use crate::{ H160, constants::EMPTY_KECCACK_HASH, types::{AccountState, AccountUpdate, BlockHeader, ChainConfig}, - utils::{decode_hex, keccak}, + utils::decode_hex, }; use bytes::Bytes; use ethereum_types::{Address, H256, U256}; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; -use ethrex_trie::{EMPTY_TRIE_HASH, NodeRLP, Trie}; +use ethrex_trie::{EMPTY_TRIE_HASH, Node, Trie}; +use rkyv::with::Skip; use rkyv::{Archive, Deserialize as RDeserialize, Serialize as RSerialize}; use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; @@ -25,12 +28,13 @@ use sha3::{Digest, Keccak256}; /// Some data is prepared before the stateless validation, and some data is /// built on-demand during the stateless validation. /// This struct must be instantiated, filled, and consumed inside the zkVM. +#[derive(Clone)] pub struct GuestProgramState { /// Map of node hash to RLP-encoded node. /// This is computed during guest program execution inside the zkVM, /// before the stateless validation. /// It is used to rebuild the state trie and storage tries. - pub nodes_hashed: BTreeMap, + pub nodes_hashed: BTreeMap>, /// Map of code hashes to their corresponding bytecode. /// This is computed during guest program execution inside the zkVM, /// before the stateless validation. @@ -67,8 +71,7 @@ pub struct GuestProgramState { /// /// It is essentially an `RpcExecutionWitness` but it also contains `ChainConfig`, /// and `first_block_number`. -#[derive(Serialize, Deserialize, Default, RSerialize, RDeserialize, Archive, Clone)] -#[serde(rename_all = "camelCase")] +#[derive(Serialize, Deserialize, RSerialize, RDeserialize, Archive, Clone)] pub struct ExecutionWitness { // Contract bytecodes needed for stateless execution. #[rkyv(with = crate::rkyv_utils::VecVecWrapper)] @@ -81,12 +84,15 @@ pub struct ExecutionWitness { // The chain config. pub chain_config: ChainConfig, /// RLP-encoded trie nodes needed for stateless execution. - #[rkyv(with = crate::rkyv_utils::VecVecWrapper)] - pub nodes: Vec>, + pub nodes: Vec, /// Flattened map of account addresses and storage keys whose values /// are needed for stateless execution. #[rkyv(with = crate::rkyv_utils::VecVecWrapper)] pub keys: Vec>, + // temp + #[rkyv(with = Skip)] + #[serde(skip)] + pub guest_program_state: Option } #[derive(thiserror::Error, Debug)] @@ -109,49 +115,48 @@ pub enum GuestProgramStateError { Custom(String), } -impl TryFrom for GuestProgramState { - type Error = GuestProgramStateError; - - fn try_from(value: ExecutionWitness) -> Result { - let block_headers: BTreeMap = value +impl ExecutionWitness { + pub fn init_state(&mut self) { + let block_headers: BTreeMap = self .block_headers_bytes - .into_iter() + .iter() + .cloned() .map(|bytes| BlockHeader::decode(bytes.as_ref())) .collect::, _>>() .map_err(|e| { GuestProgramStateError::Custom(format!("Failed to decode block headers: {}", e)) - })? + }).unwrap() .into_iter() .map(|header| (header.number, header)) .collect(); let parent_number = - value + self .first_block_number .checked_sub(1) .ok_or(GuestProgramStateError::Custom( "First block number cannot be zero".to_string(), - ))?; + )).unwrap(); let parent_header = block_headers.get(&parent_number).cloned().ok_or( - GuestProgramStateError::MissingParentHeaderOf(value.first_block_number), - )?; + GuestProgramStateError::MissingParentHeaderOf(self.first_block_number), + ).unwrap(); // hash nodes - let nodes_hashed = value + let nodes_hashed = self .nodes - .into_iter() - .map(|node| { - let node = node.to_vec(); - (keccak(&node), node) - }) + .iter() + .cloned() + .map(Node::from) + .map(|node| (node.compute_hash().finalize(), Arc::new(node))) .collect(); // hash codes // TODO: codes here probably needs to be Vec, rather than recomputing here. This requires rkyv implementation. - let codes_hashed = value + let codes_hashed = self .codes - .into_iter() + .iter() + .cloned() .map(|code| { let code = Code::from_bytecode(code.into()); (code.hash, code) @@ -164,8 +169,8 @@ impl TryFrom for GuestProgramState { storage_tries: BTreeMap::new(), block_headers, parent_block_header: parent_header, - first_block_number: value.first_block_number, - chain_config: value.chain_config, + first_block_number: self.first_block_number, + chain_config: self.chain_config, nodes_hashed, account_hashes_by_address: BTreeMap::new(), }; @@ -174,10 +179,11 @@ impl TryFrom for GuestProgramState { GuestProgramStateError::RebuildTrie( "Failed to rebuild state trie from execution witness".to_owned(), ) - })?; + }).unwrap(); - Ok(guest_program_state) + self.guest_program_state = Some(guest_program_state) } + } impl GuestProgramState { @@ -188,7 +194,7 @@ impl GuestProgramState { return Ok(()); } - let state_trie = Trie::from_nodes(self.parent_block_header.state_root, &self.nodes_hashed) + let state_trie = Trie::from_nodes(self.parent_block_header.state_root, &mut self.nodes_hashed) .map_err(|e| { GuestProgramStateError::RebuildTrie(format!("Failed to build state trie {e}")) })?; @@ -211,7 +217,7 @@ impl GuestProgramState { let account_state = AccountState::decode(&account_state_rlp).ok()?; - Trie::from_nodes(account_state.storage_root, &self.nodes_hashed).ok() + Trie::from_nodes(account_state.storage_root, &mut self.nodes_hashed).ok() } /// Helper function to apply account updates to the execution witness diff --git a/crates/l2/prover/src/guest_program/src/execution.rs b/crates/l2/prover/src/guest_program/src/execution.rs index 6d966038e23..e0106a0568f 100644 --- a/crates/l2/prover/src/guest_program/src/execution.rs +++ b/crates/l2/prover/src/guest_program/src/execution.rs @@ -18,8 +18,11 @@ use ethrex_common::{ }; #[cfg(feature = "l2")] use ethrex_l2_common::l1_messages::L1Message; +#[cfg(feature = "l2")] +use ethrex_trie::Node; use ethrex_vm::{Evm, EvmError, GuestProgramStateWrapper, VmDatabase}; use std::collections::{BTreeMap, HashMap}; +use std::sync::Arc; #[cfg(feature = "l2")] use ethrex_common::types::{ @@ -96,7 +99,7 @@ pub enum StatelessExecutionError { Internal(String), } -pub fn execution_program(input: ProgramInput) -> Result { +pub fn execution_program(input: &mut ProgramInput) -> Result { let ProgramInput { blocks, execution_witness, @@ -115,7 +118,7 @@ pub fn execution_program(input: ProgramInput) -> Result Result Result { @@ -138,7 +141,7 @@ pub fn stateless_validation_l1( last_block_hash, non_privileged_count, .. - } = execute_stateless(blocks, execution_witness, elasticity_multiplier, None)?; + } = execute_stateless(blocks, execution_witness, elasticity_multiplier, &mut None)?; Ok(ProgramOutput { initial_state_hash, @@ -158,11 +161,11 @@ pub fn stateless_validation_l1( #[cfg(feature = "l2")] pub fn stateless_validation_l2( blocks: &[Block], - execution_witness: ExecutionWitness, + execution_witness: &mut ExecutionWitness, elasticity_multiplier: u64, - fee_configs: Option>, - blob_commitment: Commitment, - blob_proof: Proof, + fee_configs: &mut Option>, + blob_commitment: &mut Commitment, + blob_proof: &mut Proof, chain_id: u64, ) -> Result { let initial_db = execution_witness.clone(); @@ -195,7 +198,7 @@ pub fn stateless_validation_l2( )?; // TODO: this could be replaced with something like a ProverConfig in the future. - let validium = (blob_commitment, blob_proof) == ([0; 48], [0; 48]); + let validium = (*blob_commitment, *blob_proof) == ([0; 48], [0; 48]); // Check state diffs are valid let blob_versioned_hash = if !validium { @@ -232,9 +235,11 @@ pub fn stateless_validation_l2( account_updates.values().cloned().collect(), )?; - verify_blob(state_diff, blob_commitment, blob_proof)? + //verify_blob(state_diff, blob_commitment, blob_proof)? + todo!(); } else { - H256::zero() + todo!(); + //H256::zero() }; Ok(ProgramOutput { @@ -262,7 +267,7 @@ struct StatelessResult { // We return them to avoid recomputing when comparing the initial state // with the final state after block execution. #[cfg(feature = "l2")] - pub nodes_hashed: BTreeMap>, + pub nodes_hashed: BTreeMap>, #[cfg(feature = "l2")] pub codes_hashed: BTreeMap>, #[cfg(feature = "l2")] @@ -271,31 +276,29 @@ struct StatelessResult { fn execute_stateless( blocks: &[Block], - execution_witness: ExecutionWitness, + execution_witness: &mut ExecutionWitness, elasticity_multiplier: u64, - fee_configs: Option>, + fee_configs: &mut Option>, ) -> Result { - let guest_program_state: GuestProgramState = execution_witness - .try_into() - .map_err(StatelessExecutionError::GuestProgramState)?; + execution_witness.init_state(); // Cache these L2-specific state fields for later state diff blob validation // to avoid expensive recomputation after the guest_program_state is moved // to the wrapper #[cfg(feature = "l2")] - let nodes_hashed = guest_program_state.nodes_hashed.clone(); + let nodes_hashed = execution_witness.guest_program_state.as_ref().unwrap().nodes_hashed.clone(); #[cfg(feature = "l2")] - let codes_hashed = guest_program_state + let codes_hashed = execution_witness.guest_program_state.as_ref().unwrap() .codes_hashed .iter() .map(|(h, c)| (*h, c.bytecode.to_vec())) .collect(); #[cfg(feature = "l2")] - let parent_block_header_clone = guest_program_state.parent_block_header.clone(); + let parent_block_header_clone = execution_witness.guest_program_state.as_ref().unwrap().parent_block_header.clone(); #[cfg(feature = "l2")] - let fee_configs = fee_configs.ok_or_else(|| StatelessExecutionError::FeeConfigNotFound)?; + let fee_configs = fee_configs.clone().ok_or_else(|| StatelessExecutionError::FeeConfigNotFound)?; - let mut wrapped_db = GuestProgramStateWrapper::new(guest_program_state); + let mut wrapped_db = GuestProgramStateWrapper::new(execution_witness.guest_program_state.as_ref().unwrap().clone()); let chain_config = wrapped_db.get_chain_config().map_err(|_| { StatelessExecutionError::Internal("No chain config in execution witness".to_string()) })?; diff --git a/crates/l2/prover/src/guest_program/src/input.rs b/crates/l2/prover/src/guest_program/src/input.rs index ae00592a4c3..f3cd558a349 100644 --- a/crates/l2/prover/src/guest_program/src/input.rs +++ b/crates/l2/prover/src/guest_program/src/input.rs @@ -29,18 +29,3 @@ pub struct ProgramInput { #[serde_as(as = "[_; 48]")] pub blob_proof: blobs_bundle::Proof, } - -impl Default for ProgramInput { - fn default() -> Self { - Self { - blocks: Default::default(), - execution_witness: ExecutionWitness::default(), - elasticity_multiplier: Default::default(), - fee_configs: None, - #[cfg(feature = "l2")] - blob_commitment: [0; 48], - #[cfg(feature = "l2")] - blob_proof: [0; 48], - } - } -} diff --git a/crates/l2/prover/src/guest_program/src/sp1/Cargo.lock b/crates/l2/prover/src/guest_program/src/sp1/Cargo.lock index 970bbda6f49..51f1b707215 100644 --- a/crates/l2/prover/src/guest_program/src/sp1/Cargo.lock +++ b/crates/l2/prover/src/guest_program/src/sp1/Cargo.lock @@ -1132,6 +1132,7 @@ dependencies = [ "ethrex-threadpool", "hex", "lazy_static", + "rkyv", "serde", "serde_json", "sha3", diff --git a/crates/l2/prover/src/guest_program/src/sp1/src/main.rs b/crates/l2/prover/src/guest_program/src/sp1/src/main.rs index 6c9477a5286..9314cd24319 100644 --- a/crates/l2/prover/src/guest_program/src/sp1/src/main.rs +++ b/crates/l2/prover/src/guest_program/src/sp1/src/main.rs @@ -1,18 +1,20 @@ #![no_main] -use guest_program::{execution::execution_program, input::ProgramInput}; +use guest_program::{execution::execution_program, input::{ProgramInput, ArchivedProgramInput}}; use rkyv::rancor::Error; sp1_zkvm::entrypoint!(main); pub fn main() { println!("cycle-tracker-report-start: read_input"); - let input = sp1_zkvm::io::read_vec(); - let input = rkyv::from_bytes::(&input).unwrap(); + let mut input_bytes = sp1_zkvm::io::read_vec(); + let mut input_archived = rkyv::access_mut::(&mut input_bytes).unwrap(); + rkyv::munge::munge!(let ArchivedProgramInput { mut input, .. } = input_archived); + println!("cycle-tracker-report-end: read_input"); println!("cycle-tracker-report-start: execution"); - let output = execution_program(input).unwrap(); + let output = execution_program(&mut input).unwrap(); println!("cycle-tracker-report-end: execution"); println!("cycle-tracker-report-start: commit_public_inputs"); diff --git a/crates/networking/rpc/debug/execution_witness.rs b/crates/networking/rpc/debug/execution_witness.rs index 2bcd285155e..8fd30be2809 100644 --- a/crates/networking/rpc/debug/execution_witness.rs +++ b/crates/networking/rpc/debug/execution_witness.rs @@ -1,11 +1,14 @@ use bytes::Bytes; use ethrex_common::{ + rkyv_utils::SizedNode, serde_utils, types::{ ChainConfig, block_execution_witness::{ExecutionWitness, GuestProgramStateError}, }, }; +use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; +use ethrex_trie::Node; use serde::{Deserialize, Serialize}; use serde_json::Value; use tracing::debug; @@ -39,7 +42,12 @@ pub struct RpcExecutionWitness { impl From for RpcExecutionWitness { fn from(value: ExecutionWitness) -> Self { Self { - state: value.nodes.into_iter().map(Bytes::from).collect(), + state: value + .nodes + .into_iter() + .map(Node::from) + .map(|n| Bytes::from(n.encode_to_vec())) + .collect(), keys: value.keys.into_iter().map(Bytes::from).collect(), codes: value.codes.into_iter().map(Bytes::from).collect(), headers: value @@ -66,8 +74,16 @@ pub fn execution_witness_from_rpc_chain_config( .into_iter() .map(|b| b.to_vec()) .collect(), - nodes: rpc_witness.state.into_iter().map(|b| b.to_vec()).collect(), + nodes: rpc_witness + .state + .into_iter() + .map(|b| Node::decode(&b.to_vec()).map(SizedNode::from)) + .collect::>() + .map_err(|e| { + GuestProgramStateError::Custom(format!("failed to rlp decode nodes: {e}")) + })?, keys: rpc_witness.keys.into_iter().map(|b| b.to_vec()).collect(), + guest_program_state: None }; Ok(witness)