From 258e58ea4b4e1e08bca1926dde547499521675e2 Mon Sep 17 00:00:00 2001 From: frisitano Date: Tue, 29 Oct 2024 00:04:32 +0800 Subject: [PATCH 01/38] feat: introduce scroll trie hash builder --- Cargo.lock | 57 ++- Cargo.toml | 3 +- crates/scroll/trie/Cargo.toml | 22 + crates/scroll/trie/README.md | 3 + crates/scroll/trie/src/branch.rs | 138 +++++++ crates/scroll/trie/src/extension.rs | 35 ++ crates/scroll/trie/src/hash_builder.rs | 542 +++++++++++++++++++++++++ crates/scroll/trie/src/lib.rs | 10 + 8 files changed, 807 insertions(+), 3 deletions(-) create mode 100644 crates/scroll/trie/Cargo.toml create mode 100644 crates/scroll/trie/README.md create mode 100644 crates/scroll/trie/src/branch.rs create mode 100644 crates/scroll/trie/src/extension.rs create mode 100644 crates/scroll/trie/src/hash_builder.rs create mode 100644 crates/scroll/trie/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 828444080432..aec1a2a47a4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1310,6 +1310,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bn254" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/bn254.git?branch=master#db67681f5e9ae1736565a48ec294a6d0dacb4e0d" +dependencies = [ + "ff 0.13.0 (git+https://github.com/scroll-tech/ff?branch=feat/sp1)", + "getrandom 0.2.15", + "rand 0.8.5", + "rand_core 0.6.4", + "sp1-intrinsics", + "subtle", +] + [[package]] name = "boa_ast" version = "0.19.1" @@ -2590,7 +2603,7 @@ dependencies = [ "base16ct", "crypto-bigint", "digest 0.10.7", - "ff", + "ff 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "generic-array", "group", "pkcs8", @@ -3037,6 +3050,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "git+https://github.com/scroll-tech/ff?branch=feat/sp1#244b1098f6be1d19c5fd3f0ec60117ac2940e6ca" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -3373,7 +3396,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.6.4", "subtle", ] @@ -5599,6 +5622,16 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +[[package]] +name = "poseidon-bn254" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/poseidon-bn254?branch=master#a96ba028dd00987551451d8f4bd65b7ea5469a44" +dependencies = [ + "bn254", + "itertools 0.13.0", + "sp1-intrinsics", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -9684,6 +9717,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll-trie" +version = "1.1.0" +dependencies = [ + "alloy-primitives", + "alloy-trie", + "hex-literal", + "poseidon-bn254", + "tracing", + "tracing-subscriber", +] + [[package]] name = "sdd" version = "3.0.4" @@ -10136,6 +10181,14 @@ dependencies = [ "sha1", ] +[[package]] +name = "sp1-intrinsics" +version = "0.0.0" +source = "git+https://github.com/scroll-tech/sp1-intrinsics.git?branch=master#7e038e60db0b2e847f6d8f49e148ccac8c6fc394" +dependencies = [ + "cfg-if", +] + [[package]] name = "spin" version = "0.9.8" diff --git a/Cargo.toml b/Cargo.toml index e3ec1c1fb4a6..4ac372aa3245 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,8 @@ members = [ "crates/rpc/rpc-server-types/", "crates/rpc/rpc-testing-util/", "crates/rpc/rpc-types-compat/", - "crates/rpc/rpc/", + "crates/rpc/rpc/", + "crates/scroll/trie", "crates/stages/api/", "crates/stages/stages/", "crates/stages/types/", diff --git a/crates/scroll/trie/Cargo.toml b/crates/scroll/trie/Cargo.toml new file mode 100644 index 000000000000..0c6a592f87b5 --- /dev/null +++ b/crates/scroll/trie/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "scroll-trie" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +alloy-trie = { workspace = true, features = ["serde"] } +alloy-primitives.workspace = true +tracing = { version = "0.1", default-features = false } +poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", branch = "master" } + +[dev-dependencies] +tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] } +hex-literal = "0.4" + +[lints] +workspace = true diff --git a/crates/scroll/trie/README.md b/crates/scroll/trie/README.md new file mode 100644 index 000000000000..e925174614bf --- /dev/null +++ b/crates/scroll/trie/README.md @@ -0,0 +1,3 @@ +# scroll-trie + +Fast binary Merkle-Patricia Trie (BMPT) state root calculator and proof generator for prefix-sorted bits. \ No newline at end of file diff --git a/crates/scroll/trie/src/branch.rs b/crates/scroll/trie/src/branch.rs new file mode 100644 index 000000000000..b6536670c895 --- /dev/null +++ b/crates/scroll/trie/src/branch.rs @@ -0,0 +1,138 @@ +use alloy_primitives::{hex, keccak256, B256}; +use alloy_trie::TrieMask; +use core::{fmt, ops::Range, slice::Iter}; + +// #[allow(unused_imports)] +// use alloc::vec::Vec; + +/// The range of valid child indexes. +pub const CHILD_INDEX_RANGE: Range = 0..16; + +/// A reference to [BranchNode] and its state mask. +/// NOTE: The stack may contain more items that specified in the state mask. +#[derive(Clone)] +pub(crate) struct BranchNodeRef<'a> { + /// Reference to the collection of RLP encoded nodes. + /// NOTE: The referenced stack might have more items than the number of children + /// for this node. We should only ever access items starting from + /// [BranchNodeRef::first_child_index]. + pub stack: &'a [B256], + /// Reference to bitmask indicating the presence of children at + /// the respective nibble positions. + pub state_mask: TrieMask, +} + +impl fmt::Debug for BranchNodeRef<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BranchNodeRef") + .field("stack", &self.stack.iter().map(hex::encode).collect::>()) + .field("state_mask", &self.state_mask) + .field("first_child_index", &self.first_child_index()) + .finish() + } +} + +impl<'a> BranchNodeRef<'a> { + /// Create a new branch node from the stack of nodes. + #[inline] + pub(crate) const fn new(stack: &'a [B256], state_mask: TrieMask) -> Self { + Self { stack, state_mask } + } + + /// Returns the stack index of the first child for this node. + /// + /// # Panics + /// + /// If the stack length is less than number of children specified in state mask. + /// Means that the node is in inconsistent state. + #[inline] + pub(crate) fn first_child_index(&self) -> usize { + self.stack.len().checked_sub(self.state_mask.count_ones() as usize).unwrap() + } + + #[inline] + fn children(&self) -> impl Iterator)> + '_ { + BranchChildrenIter::new(self) + } + + /// Given the hash mask of children, return an iterator over stack items + /// that match the mask. + #[inline] + pub(crate) fn child_hashes(&self, hash_mask: TrieMask) -> impl Iterator + '_ { + self.children() + .filter_map(|(i, c)| c.map(|c| (i, c))) + .filter(move |(index, _)| hash_mask.is_bit_set(*index)) + .map(|(_, child)| B256::from_slice(&child[..])) + } + + pub(crate) fn hash(&self) -> B256 { + let mut children_iter = self.children(); + // TODO: use an array + let mut bytes: Vec = Vec::with_capacity(32 * 2); + + if let Some((_, Some(child))) = children_iter.next() { + bytes.extend_from_slice(child.as_slice()); + } else { + bytes.extend_from_slice(&[0u8; 32]); + } + + if let Some((_, Some(child))) = children_iter.next() { + bytes.extend_from_slice(child.as_slice()); + } else { + bytes.extend_from_slice(&[0u8; 32]); + } + + keccak256(bytes.as_slice()) + } +} + +/// Iterator over branch node children. +#[derive(Debug)] +struct BranchChildrenIter<'a> { + range: Range, + state_mask: TrieMask, + stack_iter: Iter<'a, B256>, +} + +impl<'a> BranchChildrenIter<'a> { + /// Create new iterator over branch node children. + fn new(node: &BranchNodeRef<'a>) -> Self { + Self { + range: CHILD_INDEX_RANGE, + state_mask: node.state_mask, + stack_iter: node.stack[node.first_child_index()..].iter(), + } + } +} + +impl<'a> Iterator for BranchChildrenIter<'a> { + type Item = (u8, Option<&'a B256>); + + #[inline] + fn next(&mut self) -> Option { + let i = self.range.next()?; + let value = if self.state_mask.is_bit_set(i) { + // SAFETY: `first_child_index` guarantees that `stack` is exactly + // `state_mask.count_ones()` long. + Some(unsafe { self.stack_iter.next().unwrap_unchecked() }) + } else { + None + }; + Some((i, value)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } +} + +impl core::iter::FusedIterator for BranchChildrenIter<'_> {} + +impl ExactSizeIterator for BranchChildrenIter<'_> { + #[inline] + fn len(&self) -> usize { + self.range.len() + } +} diff --git a/crates/scroll/trie/src/extension.rs b/crates/scroll/trie/src/extension.rs new file mode 100644 index 000000000000..542f1c1b856e --- /dev/null +++ b/crates/scroll/trie/src/extension.rs @@ -0,0 +1,35 @@ +use alloy_primitives::{hex, keccak256, B256}; +use alloy_trie::Nibbles; +use core::fmt; + +/// Reference to the extension node. See [ExtensionNode] from more information. +pub struct ExtensionNodeRef<'a> { + /// The key for this extension node. + pub key: &'a Nibbles, + /// A pointer to the child node. + pub child: &'a B256, +} + +impl<'a> ExtensionNodeRef<'a> { + /// Creates a new extension node with the given key and a pointer to the child. + #[inline] + pub const fn new(key: &'a Nibbles, child: &'a B256) -> Self { + Self { key, child } + } + + pub fn hash(&self) -> B256 { + let mut bytes = Vec::with_capacity(self.key.len() + 32); + bytes.extend_from_slice(&self.key.as_slice()); + bytes.extend_from_slice(self.child.as_slice()); + keccak256(bytes) + } +} + +impl fmt::Debug for ExtensionNodeRef<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExtensionNodeRef") + .field("key", &self.key) + .field("node", &hex::encode(self.child)) + .finish() + } +} diff --git a/crates/scroll/trie/src/hash_builder.rs b/crates/scroll/trie/src/hash_builder.rs new file mode 100644 index 000000000000..8e5b27564441 --- /dev/null +++ b/crates/scroll/trie/src/hash_builder.rs @@ -0,0 +1,542 @@ +use crate::branch::BranchNodeRef; +use alloy_primitives::{keccak256, map::HashMap, B256}; +use alloy_trie::{ + hash_builder::{HashBuilderValue, HashBuilderValueRef}, + nodes::LeafNodeRef, + proof::{ProofNodes, ProofRetainer}, + BranchNodeCompact, Nibbles, TrieMask, EMPTY_ROOT_HASH, +}; +use core::cmp; +use tracing::trace; + +#[derive(Debug, Default)] +#[allow(missing_docs)] +pub struct HashBuilder { + pub key: Nibbles, + pub value: HashBuilderValue, + pub stack: Vec, + + pub groups: Vec, + pub tree_masks: Vec, + pub hash_masks: Vec, + + pub stored_in_database: bool, + + pub updated_branch_nodes: Option>, + pub proof_retainer: Option, +} + +impl HashBuilder { + /// Enables the Hash Builder to store updated branch nodes. + /// + /// Call [HashBuilder::split] to get the updates to branch nodes. + pub fn with_updates(mut self, retain_updates: bool) -> Self { + self.set_updates(retain_updates); + self + } + + /// Enable specified proof retainer. + pub fn with_proof_retainer(mut self, retainer: ProofRetainer) -> Self { + self.proof_retainer = Some(retainer); + self + } + + /// Enables the Hash Builder to store updated branch nodes. + /// + /// Call [HashBuilder::split] to get the updates to branch nodes. + pub fn set_updates(&mut self, retain_updates: bool) { + if retain_updates { + self.updated_branch_nodes = Some(HashMap::default()); + } + } + + /// Splits the [HashBuilder] into a [HashBuilder] and hash builder updates. + pub fn split(mut self) -> (Self, HashMap) { + let updates = self.updated_branch_nodes.take(); + (self, updates.unwrap_or_default()) + } + + /// Take and return retained proof nodes. + pub fn take_proof_nodes(&mut self) -> ProofNodes { + self.proof_retainer.take().map(ProofRetainer::into_proof_nodes).unwrap_or_default() + } + + /// The number of total updates accrued. + /// Returns `0` if [Self::with_updates] was not called. + pub fn updates_len(&self) -> usize { + self.updated_branch_nodes.as_ref().map(|u| u.len()).unwrap_or(0) + } + + /// Print the current stack of the Hash Builder. + #[cfg(feature = "std")] + pub fn print_stack(&self) { + println!("============ STACK ==============="); + for item in &self.stack { + println!("{}", alloy_primitives::hex::encode(item)); + } + println!("============ END STACK ==============="); + } + + /// Adds a new leaf element and its value to the trie hash builder. + pub fn add_leaf(&mut self, key: Nibbles, value: &[u8]) { + let key = key.unpack_bits(); + assert!(key > self.key, "add_leaf key {:?} self.key {:?}", key, self.key); + if !self.key.is_empty() { + self.update(&key); + } + self.set_key_value(key, HashBuilderValueRef::Bytes(value)); + } + + /// Adds a new branch element and its hash to the trie hash builder. + pub fn add_branch(&mut self, key: Nibbles, value: B256, stored_in_database: bool) { + let key = key.unpack_bits(); + assert!( + key > self.key || (self.key.is_empty() && key.is_empty()), + "add_branch key {:?} self.key {:?}", + key, + self.key + ); + if !self.key.is_empty() { + self.update(&key); + } else if key.is_empty() { + self.stack.push(value); + } + self.set_key_value(key, HashBuilderValueRef::Hash(&value)); + self.stored_in_database = stored_in_database; + } + + /// Returns the current root hash of the trie builder. + pub fn root(&mut self) -> B256 { + // Clears the internal state + if !self.key.is_empty() { + self.update(&Nibbles::default()); + self.key.clear(); + self.value.clear(); + } + let root = self.current_root(); + if root == EMPTY_ROOT_HASH { + if let Some(proof_retainer) = self.proof_retainer.as_mut() { + proof_retainer.retain(&Nibbles::default(), &[]) + } + } + root + } + + #[inline] + fn set_key_value(&mut self, key: Nibbles, value: HashBuilderValueRef<'_>) { + self.log_key_value("old value"); + self.key = key; + self.value.set_from_ref(value); + self.log_key_value("new value"); + } + + fn log_key_value(&self, msg: &str) { + trace!(target: "trie::hash_builder", + key = ?self.key, + value = ?self.value, + "{msg}", + ); + } + + fn current_root(&self) -> B256 { + if let Some(node_ref) = self.stack.last() { + *node_ref + } else { + EMPTY_ROOT_HASH + } + } + + /// Given a new element, it appends it to the stack and proceeds to loop through the stack state + /// and convert the nodes it can into branch / extension nodes and hash them. This ensures + /// that the top of the stack always contains the merkle root corresponding to the trie + /// built so far. + fn update(&mut self, succeeding: &Nibbles) { + let mut build_extensions = false; + // current / self.key is always the latest added element in the trie + let mut current = self.key.clone(); + debug_assert!(!current.is_empty()); + + trace!(target: "trie::hash_builder", ?current, ?succeeding, "updating merkle tree"); + + let mut i = 0usize; + loop { + let _span = tracing::trace_span!(target: "trie::hash_builder", "loop", i, ?current, build_extensions).entered(); + + let preceding_exists = !self.groups.is_empty(); + let preceding_len = self.groups.len().saturating_sub(1); + + let common_prefix_len = succeeding.common_prefix_length(current.as_slice()); + let len = cmp::max(preceding_len, common_prefix_len); + assert!(len < current.len(), "len {} current.len {}", len, current.len()); + + trace!( + target: "trie::hash_builder", + ?len, + ?common_prefix_len, + ?preceding_len, + preceding_exists, + "prefix lengths after comparing keys" + ); + + // Adjust the state masks for branch calculation + let extra_digit = current[len]; + if self.groups.len() <= len { + let new_len = len + 1; + trace!(target: "trie::hash_builder", new_len, old_len = self.groups.len(), "scaling state masks to fit"); + self.groups.resize(new_len, TrieMask::default()); + } + self.groups[len] |= TrieMask::from_nibble(extra_digit); + trace!( + target: "trie::hash_builder", + ?extra_digit, + groups = ?self.groups, + ); + + // Adjust the tree masks for exporting to the DB + if self.tree_masks.len() < current.len() { + self.resize_masks(current.len()); + } + + let mut len_from = len; + if !succeeding.is_empty() || preceding_exists { + len_from += 1; + } + trace!(target: "trie::hash_builder", "skipping {len_from} nibbles"); + + // The key without the common prefix + let short_node_key = current.slice(len_from..); + trace!(target: "trie::hash_builder", ?short_node_key); + + // Concatenate the 2 nodes together + if !build_extensions { + match self.value.as_ref() { + HashBuilderValueRef::Bytes(leaf_value) => { + let leaf_node = LeafNodeRef::new(&short_node_key, leaf_value); + // TODO: replace with appropriate account hashing + let leaf_hash = keccak256(leaf_value); + println!("leaf hash: {:?}", leaf_hash); + trace!( + target: "trie::hash_builder", + ?leaf_node, + ?leaf_hash, + "pushing leaf node", + ); + self.stack.push(leaf_hash); + // self.retain_proof_from_stack(¤t.slice(..len_from)); + } + HashBuilderValueRef::Hash(hash) => { + trace!(target: "trie::hash_builder", ?hash, "pushing branch node hash"); + self.stack.push(*hash); + + if self.stored_in_database { + self.tree_masks[current.len() - 1] |= + TrieMask::from_nibble(current.last().unwrap()); + } + self.hash_masks[current.len() - 1] |= + TrieMask::from_nibble(current.last().unwrap()); + + build_extensions = true; + } + } + } + + if build_extensions && !short_node_key.is_empty() { + self.update_masks(¤t, len_from); + let mut extension_hash = + self.stack.pop().expect("there should be at least one stack item"); + // let extension_node = ExtensionNodeRef::new(&short_node_key, &stack_last); + + for &bit in short_node_key.as_slice().iter().rev() { + let mut bytes = [0u8; 64]; + if bit == 0 { + bytes[..32].copy_from_slice(extension_hash.as_slice()); + } else { + bytes[32..].copy_from_slice(extension_hash.as_slice()); + } + extension_hash = keccak256(&bytes); + println!("branch hash: {:?}", extension_hash); + } + + trace!( + target: "trie::hash_builder", + ?short_node_key, + ?extension_hash, + "pushing extension node", + ); + self.stack.push(extension_hash); + // self.retain_proof_from_stack(¤t.slice(..len_from)); + self.resize_masks(len_from); + } + + if preceding_len <= common_prefix_len && !succeeding.is_empty() { + trace!(target: "trie::hash_builder", "no common prefix to create branch nodes from, returning"); + return; + } + + // Insert branch nodes in the stack + if !succeeding.is_empty() || preceding_exists { + // Pushes the corresponding branch node to the stack + let children = self.push_branch_node(¤t, len); + println!("children: {:?}", children); + // Need to store the branch node in an efficient format outside of the hash builder + self.store_branch_node(¤t, len, children); + } + + self.groups.resize(len, TrieMask::default()); + self.resize_masks(len); + + if preceding_len == 0 { + trace!(target: "trie::hash_builder", "0 or 1 state masks means we have no more elements to process"); + return; + } + + current.truncate(preceding_len); + trace!(target: "trie::hash_builder", ?current, "truncated nibbles to {} bytes", preceding_len); + + trace!(target: "trie::hash_builder", groups = ?self.groups, "popping empty state masks"); + while self.groups.last() == Some(&TrieMask::default()) { + self.groups.pop(); + } + + build_extensions = true; + + i += 1; + } + } + + /// Given the size of the longest common prefix, it proceeds to create a branch node + /// from the state mask and existing stack state, and store its RLP to the top of the stack, + /// after popping all the relevant elements from the stack. + /// + /// Returns the hashes of the children of the branch node, only if `updated_branch_nodes` is + /// enabled. + fn push_branch_node(&mut self, current: &Nibbles, len: usize) -> Vec { + let state_mask = self.groups[len]; + let hash_mask = self.hash_masks[len]; + let branch_node = BranchNodeRef::new(&self.stack, state_mask); + // Avoid calculating this value if it's not needed. + let children = if self.updated_branch_nodes.is_some() { + branch_node.child_hashes(hash_mask).collect() + } else { + vec![] + }; + + let branch_hash = branch_node.hash(); + println!("branch hash: {:?}", branch_hash); + + // TODO: enable proof retention + // self.retain_proof_from_stack(¤t.slice(..len)); + + // Clears the stack from the branch node elements + let first_child_idx = self.stack.len() - state_mask.count_ones() as usize; + trace!( + target: "trie::hash_builder", + new_len = first_child_idx, + old_len = self.stack.len(), + "resizing stack to prepare branch node" + ); + self.stack.resize_with(first_child_idx, Default::default); + + // trace!(target: "trie::hash_builder", ?rlp, "pushing branch node with {state_mask:?} mask + // from stack"); + + // TODO compute branch node hash + self.stack.push(branch_hash); + children + } + + /// Given the current nibble prefix and the highest common prefix length, proceeds + /// to update the masks for the next level and store the branch node and the + /// masks in the database. We will use that when consuming the intermediate nodes + /// from the database to efficiently build the trie. + fn store_branch_node(&mut self, current: &Nibbles, len: usize, children: Vec) { + trace!(target: "trie::hash_builder", ?current, ?len, ?children, "store branch node"); + if len > 0 { + let parent_index = len - 1; + self.hash_masks[parent_index] |= TrieMask::from_nibble(current[parent_index]); + } + + let store_in_db_trie = !self.tree_masks[len].is_empty() || !self.hash_masks[len].is_empty(); + if store_in_db_trie { + if len > 0 { + let parent_index = len - 1; + self.tree_masks[parent_index] |= TrieMask::from_nibble(current[parent_index]); + } + + if self.updated_branch_nodes.is_some() { + let common_prefix = current.slice(..len); + let node = BranchNodeCompact::new( + self.groups[len], + self.tree_masks[len], + self.hash_masks[len], + children, + (len == 0).then(|| self.current_root()), + ); + trace!(target: "trie::hash_builder", ?node, "intermediate node"); + self.updated_branch_nodes.as_mut().unwrap().insert(common_prefix, node); + } + } + } + + // fn retain_proof_from_stack(&mut self, prefix: &Nibbles) { + // if let Some(proof_retainer) = self.proof_retainer.as_mut() { + // proof_retainer.retain( + // prefix, + // self.stack.last().expect("there should be at least one stack item").as_ref(), + // ); + // } + // } + + fn update_masks(&mut self, current: &Nibbles, len_from: usize) { + if len_from > 0 { + let flag = TrieMask::from_nibble(current[len_from - 1]); + + self.hash_masks[len_from - 1] &= !flag; + + if !self.tree_masks[current.len() - 1].is_empty() { + self.tree_masks[len_from - 1] |= flag; + } + } + } + + fn resize_masks(&mut self, new_len: usize) { + trace!( + target: "trie::hash_builder", + new_len, + old_tree_mask_len = self.tree_masks.len(), + old_hash_mask_len = self.hash_masks.len(), + "resizing tree/hash masks" + ); + self.tree_masks.resize(new_len, TrieMask::default()); + self.hash_masks.resize(new_len, TrieMask::default()); + } +} + +trait UnpackBits { + /// This takes the `Nibbles` representation and converts it to a bit representation in which + /// there is a byte for each bit in the nibble. + /// + /// We truncate the Nibbles such that we only have 248 bits. + fn unpack_bits(&self) -> Self; + + // TODO: introduce unpack_bits_truncated method +} + +impl UnpackBits for Nibbles { + fn unpack_bits(&self) -> Self { + const MAX_BITS: usize = 248; + const MAX_NIBBLES: usize = MAX_BITS / 4; + + let capacity = core::cmp::min(self.len() * 4, MAX_BITS); + let mut bits = Vec::with_capacity(capacity); + + for byte in self.as_slice().iter().take(MAX_NIBBLES) { + for i in (0..4).rev() { + let bit = (byte >> i) & 1; + bits.push(bit); + } + } + Nibbles::from_vec_unchecked(bits) + } +} + +#[cfg(test)] +mod test { + use super::*; + use alloc::collections::BTreeMap; + use hex_literal::hex; + + #[test] + fn test_convert_to_bit_representation() { + let nibbles = Nibbles::from_nibbles_unchecked(hex!("01020304")).unpack_bits(); + let expected = hex!("00000001000001000000010100010000"); + assert_eq!(nibbles.as_slice(), expected); + } + + #[test] + fn test_convert_to_bit_representation_truncation() { + // 64 byte nibble + let hex = hex!("0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f01020304"); + assert_eq!(hex.len(), 64); + let nibbles = Nibbles::from_nibbles_unchecked(hex).unpack_bits(); + assert_eq!(nibbles.len(), 248); + println!("{:?}", &nibbles[..]); + } + + #[test] + fn test_basic_trie() { + // Test a basic trie consisting of three key value pairs: + // - (0xF, 15) + // - (0x0, 0) + // - (0x1, 1) + // The branch associated with key 0xF will be collapsed into a single leaf. + + let mut hb = HashBuilder::default().with_updates(true); + let data = BTreeMap::from([ + // binary key: (1,1,1,1) + (hex!("0F").to_vec(), Vec::from([15u8])), + // binary key: (0,0,0,0) + (hex!("00").to_vec(), Vec::from([0u8])), + // binary key: (0,0,0,1) + (hex!("01").to_vec(), Vec::from([1u8])), + ]); + data.iter().for_each(|(key, val)| { + let nibbles = Nibbles::from_nibbles_unchecked(key); + hb.add_leaf(nibbles, val.as_ref()); + }); + let root = hb.root(); + + const EMPTY_NODE: [u8; 32] = [0u8; 32]; + let expected = { + let leaf_0 = keccak256(data.get(hex!("00").as_slice()).unwrap()); + let leaf_1 = keccak256(data.get(hex!("01").as_slice()).unwrap()); + let leaf_f = keccak256(data.get(hex!("0F").as_slice()).unwrap()); + let node_000 = keccak256([leaf_0.as_slice(), leaf_1.as_slice()].concat()); + let node_00 = keccak256([node_000.as_slice(), &EMPTY_NODE].concat()); + let node_0 = keccak256([node_00.as_slice(), &EMPTY_NODE].concat()); + keccak256([node_0.as_slice(), leaf_f.as_slice()].concat()) + }; + + assert_eq!(expected, root); + } + + #[test] + fn test_generates_branch_node() { + let mut hb = HashBuilder::default().with_updates(true); + let data = BTreeMap::from([ + // binary key: (1,1,1,1) + (hex!("0F").to_vec(), Vec::from([15u8])), + // binary key: (0,0,0,0) + (hex!("00").to_vec(), Vec::from([0u8])), + // binary key: (0,0,0,1) + (hex!("01").to_vec(), Vec::from([1u8])), + // binary key: (0,0,1,0) + (hex!("02").to_vec(), Vec::from([2u8])), + ]); + data.iter().for_each(|(key, val)| { + let nibbles = Nibbles::from_nibbles_unchecked(key); + hb.add_leaf(nibbles, val.as_ref()); + }); + let root = hb.root(); + + const EMPTY_NODE: [u8; 32] = [0u8; 32]; + let expected = { + let leaf_0 = keccak256(data.get(hex!("00").as_slice()).unwrap()); + let leaf_1 = keccak256(data.get(hex!("01").as_slice()).unwrap()); + let leaf_2 = keccak256(data.get(hex!("02").as_slice()).unwrap()); + let leaf_f = keccak256(data.get(hex!("0F").as_slice()).unwrap()); + let node_000 = keccak256([leaf_0.as_slice(), leaf_1.as_slice()].concat()); + let node_00 = keccak256([node_000.as_slice(), leaf_2.as_slice()].concat()); + let node_0 = keccak256([node_00.as_slice(), &EMPTY_NODE].concat()); + keccak256([node_0.as_slice(), leaf_f.as_slice()].concat()) + }; + + assert_eq!(root, expected); + + let (_, updates) = hb.split(); + for (key, update) in updates { + println!("key: {:?}", key); + println!("update: {:?}", update); + } + } +} diff --git a/crates/scroll/trie/src/lib.rs b/crates/scroll/trie/src/lib.rs new file mode 100644 index 000000000000..cc622f85e463 --- /dev/null +++ b/crates/scroll/trie/src/lib.rs @@ -0,0 +1,10 @@ +#![doc = include_str!("../README.md")] + +#[macro_use] +#[allow(unused_imports)] +extern crate alloc; + +mod branch; +mod hash_builder; +pub use hash_builder::HashBuilder; +mod extension; From cfd46fcafa78eb16c340c851b829c444cf9e8394 Mon Sep 17 00:00:00 2001 From: frisitano Date: Tue, 29 Oct 2024 17:25:22 +0800 Subject: [PATCH 02/38] refactor: refactor HashBuilder implementation --- crates/scroll/trie/src/branch.rs | 13 ++--- crates/scroll/trie/src/extension.rs | 35 -------------- crates/scroll/trie/src/hash_builder.rs | 66 +++++--------------------- crates/scroll/trie/src/key.rs | 32 +++++++++++++ crates/scroll/trie/src/lib.rs | 3 +- crates/scroll/trie/src/sub_tree.rs | 42 ++++++++++++++++ 6 files changed, 93 insertions(+), 98 deletions(-) delete mode 100644 crates/scroll/trie/src/extension.rs create mode 100644 crates/scroll/trie/src/key.rs create mode 100644 crates/scroll/trie/src/sub_tree.rs diff --git a/crates/scroll/trie/src/branch.rs b/crates/scroll/trie/src/branch.rs index b6536670c895..f60ab8624f32 100644 --- a/crates/scroll/trie/src/branch.rs +++ b/crates/scroll/trie/src/branch.rs @@ -6,7 +6,7 @@ use core::{fmt, ops::Range, slice::Iter}; // use alloc::vec::Vec; /// The range of valid child indexes. -pub const CHILD_INDEX_RANGE: Range = 0..16; +pub(crate) const CHILD_INDEX_RANGE: Range = 0..2; /// A reference to [BranchNode] and its state mask. /// NOTE: The stack may contain more items that specified in the state mask. @@ -67,19 +67,14 @@ impl<'a> BranchNodeRef<'a> { pub(crate) fn hash(&self) -> B256 { let mut children_iter = self.children(); - // TODO: use an array - let mut bytes: Vec = Vec::with_capacity(32 * 2); + let mut bytes: [u8; 64] = [0u8; 64]; if let Some((_, Some(child))) = children_iter.next() { - bytes.extend_from_slice(child.as_slice()); - } else { - bytes.extend_from_slice(&[0u8; 32]); + bytes[..32].copy_from_slice(child.as_slice()); } if let Some((_, Some(child))) = children_iter.next() { - bytes.extend_from_slice(child.as_slice()); - } else { - bytes.extend_from_slice(&[0u8; 32]); + bytes[32..].copy_from_slice(child.as_slice()); } keccak256(bytes.as_slice()) diff --git a/crates/scroll/trie/src/extension.rs b/crates/scroll/trie/src/extension.rs deleted file mode 100644 index 542f1c1b856e..000000000000 --- a/crates/scroll/trie/src/extension.rs +++ /dev/null @@ -1,35 +0,0 @@ -use alloy_primitives::{hex, keccak256, B256}; -use alloy_trie::Nibbles; -use core::fmt; - -/// Reference to the extension node. See [ExtensionNode] from more information. -pub struct ExtensionNodeRef<'a> { - /// The key for this extension node. - pub key: &'a Nibbles, - /// A pointer to the child node. - pub child: &'a B256, -} - -impl<'a> ExtensionNodeRef<'a> { - /// Creates a new extension node with the given key and a pointer to the child. - #[inline] - pub const fn new(key: &'a Nibbles, child: &'a B256) -> Self { - Self { key, child } - } - - pub fn hash(&self) -> B256 { - let mut bytes = Vec::with_capacity(self.key.len() + 32); - bytes.extend_from_slice(&self.key.as_slice()); - bytes.extend_from_slice(self.child.as_slice()); - keccak256(bytes) - } -} - -impl fmt::Debug for ExtensionNodeRef<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ExtensionNodeRef") - .field("key", &self.key) - .field("node", &hex::encode(self.child)) - .finish() - } -} diff --git a/crates/scroll/trie/src/hash_builder.rs b/crates/scroll/trie/src/hash_builder.rs index 8e5b27564441..963bcadd4652 100644 --- a/crates/scroll/trie/src/hash_builder.rs +++ b/crates/scroll/trie/src/hash_builder.rs @@ -1,4 +1,4 @@ -use crate::branch::BranchNodeRef; +use crate::{branch::BranchNodeRef, key::UnpackBits, sub_tree::SubTreeRef}; use alloy_primitives::{keccak256, map::HashMap, B256}; use alloy_trie::{ hash_builder::{HashBuilderValue, HashBuilderValueRef}, @@ -68,7 +68,6 @@ impl HashBuilder { } /// Print the current stack of the Hash Builder. - #[cfg(feature = "std")] pub fn print_stack(&self) { println!("============ STACK ==============="); for item in &self.stack { @@ -242,28 +241,17 @@ impl HashBuilder { if build_extensions && !short_node_key.is_empty() { self.update_masks(¤t, len_from); - let mut extension_hash = - self.stack.pop().expect("there should be at least one stack item"); - // let extension_node = ExtensionNodeRef::new(&short_node_key, &stack_last); - - for &bit in short_node_key.as_slice().iter().rev() { - let mut bytes = [0u8; 64]; - if bit == 0 { - bytes[..32].copy_from_slice(extension_hash.as_slice()); - } else { - bytes[32..].copy_from_slice(extension_hash.as_slice()); - } - extension_hash = keccak256(&bytes); - println!("branch hash: {:?}", extension_hash); - } + let stack_last = self.stack.pop().expect("there should be at least one stack item"); + let sub_tree = SubTreeRef::new(&short_node_key, &stack_last); + let sub_tree_root = sub_tree.root(); trace!( target: "trie::hash_builder", ?short_node_key, - ?extension_hash, - "pushing extension node", + ?sub_tree_root, + "pushing subtree root", ); - self.stack.push(extension_hash); + self.stack.push(sub_tree_root); // self.retain_proof_from_stack(¤t.slice(..len_from)); self.resize_masks(len_from); } @@ -310,7 +298,7 @@ impl HashBuilder { /// /// Returns the hashes of the children of the branch node, only if `updated_branch_nodes` is /// enabled. - fn push_branch_node(&mut self, current: &Nibbles, len: usize) -> Vec { + fn push_branch_node(&mut self, _current: &Nibbles, len: usize) -> Vec { let state_mask = self.groups[len]; let hash_mask = self.hash_masks[len]; let branch_node = BranchNodeRef::new(&self.stack, state_mask); @@ -412,34 +400,6 @@ impl HashBuilder { } } -trait UnpackBits { - /// This takes the `Nibbles` representation and converts it to a bit representation in which - /// there is a byte for each bit in the nibble. - /// - /// We truncate the Nibbles such that we only have 248 bits. - fn unpack_bits(&self) -> Self; - - // TODO: introduce unpack_bits_truncated method -} - -impl UnpackBits for Nibbles { - fn unpack_bits(&self) -> Self { - const MAX_BITS: usize = 248; - const MAX_NIBBLES: usize = MAX_BITS / 4; - - let capacity = core::cmp::min(self.len() * 4, MAX_BITS); - let mut bits = Vec::with_capacity(capacity); - - for byte in self.as_slice().iter().take(MAX_NIBBLES) { - for i in (0..4).rev() { - let bit = (byte >> i) & 1; - bits.push(bit); - } - } - Nibbles::from_vec_unchecked(bits) - } -} - #[cfg(test)] mod test { use super::*; @@ -460,7 +420,6 @@ mod test { assert_eq!(hex.len(), 64); let nibbles = Nibbles::from_nibbles_unchecked(hex).unpack_bits(); assert_eq!(nibbles.len(), 248); - println!("{:?}", &nibbles[..]); } #[test] @@ -473,12 +432,12 @@ mod test { let mut hb = HashBuilder::default().with_updates(true); let data = BTreeMap::from([ - // binary key: (1,1,1,1) - (hex!("0F").to_vec(), Vec::from([15u8])), // binary key: (0,0,0,0) (hex!("00").to_vec(), Vec::from([0u8])), // binary key: (0,0,0,1) (hex!("01").to_vec(), Vec::from([1u8])), + // binary key: (1,1,1,1) + (hex!("0F").to_vec(), Vec::from([15u8])), ]); data.iter().for_each(|(key, val)| { let nibbles = Nibbles::from_nibbles_unchecked(key); @@ -504,14 +463,14 @@ mod test { fn test_generates_branch_node() { let mut hb = HashBuilder::default().with_updates(true); let data = BTreeMap::from([ - // binary key: (1,1,1,1) - (hex!("0F").to_vec(), Vec::from([15u8])), // binary key: (0,0,0,0) (hex!("00").to_vec(), Vec::from([0u8])), // binary key: (0,0,0,1) (hex!("01").to_vec(), Vec::from([1u8])), // binary key: (0,0,1,0) (hex!("02").to_vec(), Vec::from([2u8])), + // binary key: (1,1,1,1) + (hex!("0F").to_vec(), Vec::from([15u8])), ]); data.iter().for_each(|(key, val)| { let nibbles = Nibbles::from_nibbles_unchecked(key); @@ -535,6 +494,7 @@ mod test { let (_, updates) = hb.split(); for (key, update) in updates { + // TODO add additional assertions println!("key: {:?}", key); println!("update: {:?}", update); } diff --git a/crates/scroll/trie/src/key.rs b/crates/scroll/trie/src/key.rs new file mode 100644 index 000000000000..11a51c6c8ad5 --- /dev/null +++ b/crates/scroll/trie/src/key.rs @@ -0,0 +1,32 @@ +use alloy_trie::Nibbles; + +/// The maximum number of bits a key can contain. +const MAX_BITS: usize = 248; + +// The maximum number of nibbles a key can contain. +const MAX_NIBBLES: usize = MAX_BITS / 4; + +pub(crate) trait UnpackBits { + /// This takes the `Nibbles` representation and converts it to a bit representation in which + /// there is a byte for each bit in the nibble. + /// + /// We truncate the Nibbles such that we only have 248 bits. + fn unpack_bits(&self) -> Self; + + // TODO: introduce unpack_bits_truncated method +} + +impl UnpackBits for Nibbles { + fn unpack_bits(&self) -> Self { + let capacity = core::cmp::min(self.len() * 4, MAX_BITS); + let mut bits = Vec::with_capacity(capacity); + + for byte in self.as_slice().iter().take(MAX_NIBBLES) { + for i in (0..4).rev() { + let bit = (byte >> i) & 1; + bits.push(bit); + } + } + Nibbles::from_vec_unchecked(bits) + } +} diff --git a/crates/scroll/trie/src/lib.rs b/crates/scroll/trie/src/lib.rs index cc622f85e463..90baac349199 100644 --- a/crates/scroll/trie/src/lib.rs +++ b/crates/scroll/trie/src/lib.rs @@ -7,4 +7,5 @@ extern crate alloc; mod branch; mod hash_builder; pub use hash_builder::HashBuilder; -mod extension; +mod key; +mod sub_tree; diff --git a/crates/scroll/trie/src/sub_tree.rs b/crates/scroll/trie/src/sub_tree.rs new file mode 100644 index 000000000000..38b5646db2d7 --- /dev/null +++ b/crates/scroll/trie/src/sub_tree.rs @@ -0,0 +1,42 @@ +use alloy_primitives::{hex, keccak256, B256}; +use alloy_trie::Nibbles; +use core::fmt; + +/// Reference to a subtree containing a single child. +pub(crate) struct SubTreeRef<'a> { + /// The key to the child node. + pub key: &'a Nibbles, + /// A pointer to the child node. + pub child: &'a B256, +} + +impl<'a> SubTreeRef<'a> { + /// Creates a new subtree with the given key and a pointer to the child. + #[inline] + pub(crate) const fn new(key: &'a Nibbles, child: &'a B256) -> Self { + Self { key, child } + } + + pub(crate) fn root(&self) -> B256 { + let mut tree_root = *self.child; + for &bit in self.key.as_slice().iter().rev() { + let mut bytes = [0u8; 64]; + if bit == 0 { + bytes[..32].copy_from_slice(tree_root.as_slice()); + } else { + bytes[32..].copy_from_slice(tree_root.as_slice()); + } + tree_root = keccak256(&bytes); + } + tree_root + } +} + +impl fmt::Debug for SubTreeRef<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SubTreeRef") + .field("key", &self.key) + .field("node", &hex::encode(self.child)) + .finish() + } +} From 51fa6bbe910854f508f32db2832007504f92f374 Mon Sep 17 00:00:00 2001 From: frisitano Date: Sat, 16 Nov 2024 19:10:50 +0800 Subject: [PATCH 03/38] feat: introduce StateCommitment in StateProviders --- .../commands/debug_cmd/in_memory_merkle.rs | 8 +- bin/reth/src/commands/debug_cmd/merkle.rs | 11 +- crates/exex/exex/src/backfill/test_utils.rs | 6 +- crates/stages/stages/src/stages/execution.rs | 12 +- .../provider/src/providers/consistent.rs | 12 +- .../provider/src/providers/database/mod.rs | 4 +- .../src/providers/database/provider.rs | 41 +++--- .../src/providers/state/historical.rs | 122 +++++++++++------- .../provider/src/providers/state/latest.rs | 49 ++++--- crates/storage/storage-api/src/state.rs | 7 + 10 files changed, 162 insertions(+), 110 deletions(-) diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index d5bb8a87b22e..ef186ca050e1 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -22,9 +22,9 @@ use reth_network_api::NetworkInfo; use reth_node_api::{NodeTypesWithDB, NodeTypesWithEngine}; use reth_node_ethereum::EthExecutorProvider; use reth_provider::{ - writer::UnifiedStorageWriter, AccountExtReader, ChainSpecProvider, HashingWriter, - HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderFactory, - StageCheckpointReader, StateWriter, StorageReader, + writer::UnifiedStorageWriter, AccountExtReader, AsLatestStateProviderRef, ChainSpecProvider, + HashingWriter, HeaderProvider, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, + StateWriter, StorageReader, }; use reth_revm::database::StateProviderDatabase; use reth_stages::StageId; @@ -133,7 +133,7 @@ impl> Command { ) .await?; - let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); + let db = StateProviderDatabase::new(provider.latest()); let executor = EthExecutorProvider::ethereum(provider_factory.chain_spec()).executor(db); diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 9c77c70abc74..4128b982ca24 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -20,9 +20,9 @@ use reth_network_p2p::full_block::FullBlockClient; use reth_node_api::{NodeTypesWithDB, NodeTypesWithEngine}; use reth_node_ethereum::EthExecutorProvider; use reth_provider::{ - writer::UnifiedStorageWriter, BlockNumReader, BlockWriter, ChainSpecProvider, - DatabaseProviderFactory, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, - ProviderError, ProviderFactory, StateWriter, + writer::UnifiedStorageWriter, AsLatestStateProviderRef, BlockNumReader, BlockWriter, + ChainSpecProvider, DatabaseProviderFactory, HeaderProvider, OriginalValuesKnown, ProviderError, + ProviderFactory, StateWriter, }; use reth_revm::database::StateProviderDatabase; use reth_stages::{ @@ -152,9 +152,8 @@ impl> Command { provider_rw.insert_block(sealed_block.clone())?; td += sealed_block.difficulty; - let mut executor = executor_provider.batch_executor(StateProviderDatabase::new( - LatestStateProviderRef::new(&provider_rw), - )); + let mut executor = + executor_provider.batch_executor(StateProviderDatabase::new(provider_rw.latest())); executor.execute_and_verify_one((&sealed_block.clone().unseal(), td).into())?; let execution_outcome = executor.finalize(); diff --git a/crates/exex/exex/src/backfill/test_utils.rs b/crates/exex/exex/src/backfill/test_utils.rs index 80af408c5c8f..222f9dc1d0d2 100644 --- a/crates/exex/exex/src/backfill/test_utils.rs +++ b/crates/exex/exex/src/backfill/test_utils.rs @@ -13,7 +13,7 @@ use reth_primitives::{ Block, BlockBody, BlockWithSenders, Receipt, SealedBlockWithSenders, Transaction, }; use reth_provider::{ - providers::ProviderNodeTypes, BlockWriter as _, ExecutionOutcome, LatestStateProviderRef, + providers::ProviderNodeTypes, AsLatestStateProviderRef, BlockWriter as _, ExecutionOutcome, ProviderFactory, }; use reth_revm::database::StateProviderDatabase; @@ -63,7 +63,7 @@ where // Execute the block to produce a block execution output let mut block_execution_output = EthExecutorProvider::ethereum(chain_spec) - .executor(StateProviderDatabase::new(LatestStateProviderRef::new(&provider))) + .executor(StateProviderDatabase::new(provider.latest())) .execute(BlockExecutionInput { block, total_difficulty: U256::ZERO })?; block_execution_output.state.reverts.sort(); @@ -189,7 +189,7 @@ where let provider = provider_factory.provider()?; let executor = EthExecutorProvider::ethereum(chain_spec) - .batch_executor(StateProviderDatabase::new(LatestStateProviderRef::new(&provider))); + .batch_executor(StateProviderDatabase::new(provider.latest())); let mut execution_outcome = executor.execute_and_verify_batch(vec![ (&block1, U256::ZERO).into(), diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 16234ad483f7..7c6d41bd536f 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -16,7 +16,7 @@ use reth_primitives_traits::{format_gas_throughput, NodePrimitives}; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, writer::UnifiedStorageWriter, - BlockHashReader, BlockReader, DBProvider, HeaderProvider, LatestStateProviderRef, + AsLatestStateProviderRef, BlockHashReader, BlockReader, DBProvider, HeaderProvider, OriginalValuesKnown, ProviderError, StateChangeWriter, StateWriter, StaticFileProviderFactory, StatsReader, TransactionVariant, }; @@ -46,8 +46,9 @@ use tracing::*; /// - [`tables::BlockBodyIndices`] to get tx number /// - [`tables::Transactions`] to execute /// -/// For state access [`LatestStateProviderRef`] provides us latest state and history state -/// For latest most recent state [`LatestStateProviderRef`] would need (Used for execution Stage): +/// For state access [`reth_provider::LatestStateProviderRef`] provides us latest state and history +/// state For latest most recent state [`reth_provider::LatestStateProviderRef`] would need (Used +/// for execution Stage): /// - [`tables::PlainAccountState`] /// - [`tables::Bytecodes`] /// - [`tables::PlainStorageState`] @@ -180,7 +181,8 @@ where + StaticFileProviderFactory + StatsReader + StateChangeWriter - + BlockHashReader, + + BlockHashReader + + AsLatestStateProviderRef, for<'a> UnifiedStorageWriter<'a, Provider, StaticFileProviderRWRefMut<'a, Provider::Primitives>>: StateWriter, { @@ -225,7 +227,7 @@ where None }; - let db = StateProviderDatabase(LatestStateProviderRef::new(provider)); + let db = StateProviderDatabase(provider.latest()); let mut executor = self.executor_provider.batch_executor(db); executor.set_tip(max_block); executor.set_prune_modes(prune_modes); diff --git a/crates/storage/provider/src/providers/consistent.rs b/crates/storage/provider/src/providers/consistent.rs index 3b2599f49991..a5980bcd7d48 100644 --- a/crates/storage/provider/src/providers/consistent.rs +++ b/crates/storage/provider/src/providers/consistent.rs @@ -1,10 +1,10 @@ use super::{DatabaseProviderRO, ProviderFactory, ProviderNodeTypes}; use crate::{ - providers::StaticFileProvider, AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, - BlockReader, BlockReaderIdExt, BlockSource, ChainSpecProvider, ChangeSetReader, EvmEnvProvider, - HeaderProvider, ProviderError, PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, - StageCheckpointReader, StateReader, StaticFileProviderFactory, TransactionVariant, - TransactionsProvider, WithdrawalsProvider, + providers::StaticFileProvider, AccountReader, AsLatestStateProviderRef, BlockHashReader, + BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, BlockSource, ChainSpecProvider, + ChangeSetReader, EvmEnvProvider, HeaderProvider, ProviderError, PruneCheckpointReader, + ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateReader, + StaticFileProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; use alloy_consensus::Header; use alloy_eips::{ @@ -109,7 +109,7 @@ impl ConsistentProvider { Ok(self.block_state_provider_ref(state)?.boxed()) } else { trace!(target: "providers::blockchain", "Using database state for latest state provider"); - self.storage_provider.latest() + Ok(self.storage_provider.latest()) } } diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 94c83bbb4422..14fc6f765f63 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -160,7 +160,9 @@ impl ProviderFactory { #[track_caller] pub fn latest(&self) -> ProviderResult { trace!(target: "providers::db", "Returning latest state provider"); - Ok(Box::new(LatestStateProvider::new(self.database_provider_ro()?))) + Ok(Box::new(LatestStateProvider::<_, N::StateCommitment>::new( + self.database_provider_ro()?, + ))) } /// Storage provider for state at that given block diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index b93112e70843..94b57c2113e4 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -6,14 +6,15 @@ use crate::{ AccountExtReader, BlockSource, ChangeSetReader, ReceiptProvider, StageCheckpointWriter, }, writer::UnifiedStorageWriter, - AccountReader, BlockExecutionWriter, BlockHashReader, BlockNumReader, BlockReader, BlockWriter, - BundleStateInit, ChainStateBlockReader, ChainStateBlockWriter, DBProvider, EvmEnvProvider, - HashingWriter, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HistoricalStateProvider, - HistoricalStateProviderRef, HistoryWriter, LatestStateProvider, LatestStateProviderRef, - OriginalValuesKnown, ProviderError, PruneCheckpointReader, PruneCheckpointWriter, RevertsInit, - StageCheckpointReader, StateChangeWriter, StateProviderBox, StateReader, StateWriter, - StaticFileProviderFactory, StatsReader, StorageReader, StorageTrieWriter, TransactionVariant, - TransactionsProvider, TransactionsProviderExt, TrieWriter, WithdrawalsProvider, + AccountReader, AsLatestStateProviderRef, BlockExecutionWriter, BlockHashReader, BlockNumReader, + BlockReader, BlockWriter, BundleStateInit, ChainStateBlockReader, ChainStateBlockWriter, + DBProvider, EvmEnvProvider, HashingWriter, HeaderProvider, HeaderSyncGap, + HeaderSyncGapProvider, HistoricalStateProvider, HistoricalStateProviderRef, HistoryWriter, + LatestStateProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, + PruneCheckpointReader, PruneCheckpointWriter, RevertsInit, StageCheckpointReader, + StateChangeWriter, StateProviderBox, StateReader, StateWriter, StaticFileProviderFactory, + StatsReader, StorageReader, StorageTrieWriter, TransactionVariant, TransactionsProvider, + TransactionsProviderExt, TrieWriter, WithdrawalsProvider, }; use alloy_consensus::Header; use alloy_eips::{ @@ -148,12 +149,6 @@ impl DatabaseProvider { } impl DatabaseProvider { - /// State provider for latest block - pub fn latest<'a>(&'a self) -> ProviderResult> { - trace!(target: "providers::db", "Returning latest state provider"); - Ok(Box::new(LatestStateProviderRef::new(self))) - } - /// Storage provider for state at that given block hash pub fn history_by_block_hash<'a>( &'a self, @@ -164,7 +159,7 @@ impl DatabaseProvider { if block_number == self.best_block_number().unwrap_or_default() && block_number == self.last_block_number().unwrap_or_default() { - return Ok(Box::new(LatestStateProviderRef::new(self))) + return Ok(Box::new(LatestStateProviderRef::<'_, _, N::StateCommitment>::new(self))) } // +1 as the changeset that we want is the one that was applied after this block. @@ -175,7 +170,8 @@ impl DatabaseProvider { let storage_history_prune_checkpoint = self.get_prune_checkpoint(PruneSegment::StorageHistory)?; - let mut state_provider = HistoricalStateProviderRef::new(self, block_number); + let mut state_provider = + HistoricalStateProviderRef::<'_, _, N::StateCommitment>::new(self, block_number); // If we pruned account or storage history, we can't return state on every historical block. // Instead, we should cap it at the latest prune checkpoint for corresponding prune segment. @@ -243,7 +239,7 @@ impl TryIntoHistoricalStateProvider for Databa if block_number == self.best_block_number().unwrap_or_default() && block_number == self.last_block_number().unwrap_or_default() { - return Ok(Box::new(LatestStateProvider::new(self))) + return Ok(Box::new(LatestStateProvider::<_, N::StateCommitment>::new(self))) } // +1 as the changeset that we want is the one that was applied after this block. @@ -254,7 +250,8 @@ impl TryIntoHistoricalStateProvider for Databa let storage_history_prune_checkpoint = self.get_prune_checkpoint(PruneSegment::StorageHistory)?; - let mut state_provider = HistoricalStateProvider::new(self, block_number); + let mut state_provider = + HistoricalStateProvider::<_, N::StateCommitment>::new(self, block_number); // If we pruned account or storage history, we can't return state on every historical block. // Instead, we should cap it at the latest prune checkpoint for corresponding prune segment. @@ -277,6 +274,14 @@ impl TryIntoHistoricalStateProvider for Databa } } +impl AsLatestStateProviderRef for DatabaseProvider { + /// State provider for latest state + fn latest<'a>(&'a self) -> Box { + trace!(target: "providers::db", "Returning latest state provider"); + Box::new(LatestStateProviderRef::<'_, _, N::StateCommitment>::new(self)) + } +} + impl + 'static> DatabaseProvider { diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 29ba70e2049e..2cc912af9791 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -27,7 +27,7 @@ use reth_trie_db::{ DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot, DatabaseTrieWitness, }; -use std::fmt::Debug; +use std::{fmt::Debug, marker::PhantomData}; /// State provider for a given block number which takes a tx reference. /// @@ -41,13 +41,15 @@ use std::fmt::Debug; /// - [`tables::AccountChangeSets`] /// - [`tables::StorageChangeSets`] #[derive(Debug)] -pub struct HistoricalStateProviderRef<'b, Provider> { +pub struct HistoricalStateProviderRef<'b, Provider, SC> { /// Database provider provider: &'b Provider, /// Block number is main index for the history state of accounts and storages. block_number: BlockNumber, /// Lowest blocks at which different parts of the state are available. lowest_available_blocks: LowestAvailableBlocks, + /// Marker to associate the `StateCommitment` type with this provider. + _marker: PhantomData, } #[derive(Debug, Eq, PartialEq)] @@ -58,10 +60,15 @@ pub enum HistoryInfo { MaybeInPlainState, } -impl<'b, Provider: DBProvider + BlockNumReader> HistoricalStateProviderRef<'b, Provider> { +impl<'b, Provider: DBProvider + BlockNumReader, SC> HistoricalStateProviderRef<'b, Provider, SC> { /// Create new `StateProvider` for historical block number pub fn new(provider: &'b Provider, block_number: BlockNumber) -> Self { - Self { provider, block_number, lowest_available_blocks: Default::default() } + Self { + provider, + block_number, + lowest_available_blocks: Default::default(), + _marker: PhantomData, + } } /// Create new `StateProvider` for historical block number and lowest block numbers at which @@ -71,7 +78,7 @@ impl<'b, Provider: DBProvider + BlockNumReader> HistoricalStateProviderRef<'b, P block_number: BlockNumber, lowest_available_blocks: LowestAvailableBlocks, ) -> Self { - Self { provider, block_number, lowest_available_blocks } + Self { provider, block_number, lowest_available_blocks, _marker: PhantomData } } /// Lookup an account in the `AccountsHistory` table @@ -233,14 +240,14 @@ impl<'b, Provider: DBProvider + BlockNumReader> HistoricalStateProviderRef<'b, P } } -impl HistoricalStateProviderRef<'_, Provider> { +impl HistoricalStateProviderRef<'_, Provider, SC> { fn tx(&self) -> &Provider::Tx { self.provider.tx_ref() } } -impl AccountReader - for HistoricalStateProviderRef<'_, Provider> +impl AccountReader + for HistoricalStateProviderRef<'_, Provider, SC> { /// Get basic account information. fn basic_account(&self, address: Address) -> ProviderResult> { @@ -263,8 +270,8 @@ impl AccountReader } } -impl BlockHashReader - for HistoricalStateProviderRef<'_, Provider> +impl BlockHashReader + for HistoricalStateProviderRef<'_, Provider, SC> { /// Get block hash by number. fn block_hash(&self, number: u64) -> ProviderResult> { @@ -280,8 +287,8 @@ impl BlockHashReader } } -impl StateRootProvider - for HistoricalStateProviderRef<'_, Provider> +impl StateRootProvider + for HistoricalStateProviderRef<'_, Provider, SC> { fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult { let mut revert_state = self.revert_state()?; @@ -316,8 +323,8 @@ impl StateRootProvider } } -impl StorageRootProvider - for HistoricalStateProviderRef<'_, Provider> +impl StorageRootProvider + for HistoricalStateProviderRef<'_, Provider, SC> { fn storage_root( &self, @@ -343,8 +350,8 @@ impl StorageRootProvider } } -impl StateProofProvider - for HistoricalStateProviderRef<'_, Provider> +impl StateProofProvider + for HistoricalStateProviderRef<'_, Provider, SC> { /// Get account and storage proofs. fn proof( @@ -377,8 +384,8 @@ impl StateProofProvider } } -impl StateProvider - for HistoricalStateProviderRef<'_, Provider> +impl StateProvider + for HistoricalStateProviderRef<'_, Provider, SC> { /// Get storage. fn storage( @@ -419,19 +426,26 @@ impl StateProvider /// State provider for a given block number. /// For more detailed description, see [`HistoricalStateProviderRef`]. #[derive(Debug)] -pub struct HistoricalStateProvider { +pub struct HistoricalStateProvider { /// Database provider. provider: Provider, /// State at the block number is the main indexer of the state. block_number: BlockNumber, /// Lowest blocks at which different parts of the state are available. lowest_available_blocks: LowestAvailableBlocks, + /// Marker to associate the `StateCommitment` type with this provider. + _marker: PhantomData, } -impl HistoricalStateProvider { +impl HistoricalStateProvider { /// Create new `StateProvider` for historical block number pub fn new(provider: Provider, block_number: BlockNumber) -> Self { - Self { provider, block_number, lowest_available_blocks: Default::default() } + Self { + provider, + block_number, + lowest_available_blocks: Default::default(), + _marker: PhantomData, + } } /// Set the lowest block number at which the account history is available. @@ -454,7 +468,7 @@ impl HistoricalStateProvider { /// Returns a new provider that takes the `TX` as reference #[inline(always)] - const fn as_ref(&self) -> HistoricalStateProviderRef<'_, Provider> { + const fn as_ref(&self) -> HistoricalStateProviderRef<'_, Provider, SC> { HistoricalStateProviderRef::new_with_lowest_available_blocks( &self.provider, self.block_number, @@ -464,7 +478,7 @@ impl HistoricalStateProvider { } // Delegates all provider impls to [HistoricalStateProviderRef] -delegate_provider_impls!(HistoricalStateProvider where [Provider: DBProvider + BlockNumReader + BlockHashReader]); +delegate_provider_impls!(HistoricalStateProvider where [Provider: DBProvider + BlockNumReader + BlockHashReader, SC: Send + Sync]); /// Lowest blocks at which different parts of the state are available. /// They may be [Some] if pruning is enabled. @@ -510,6 +524,10 @@ mod tests { use reth_primitives::{Account, StorageEntry}; use reth_storage_api::{BlockHashReader, BlockNumReader, DBProvider, DatabaseProviderFactory}; use reth_storage_errors::provider::ProviderError; + use reth_trie_db::MerklePatriciaTrie; + + type TestHistoricalStateProviderRef<'a, TX> = + HistoricalStateProviderRef<'a, TX, MerklePatriciaTrie>; const ADDRESS: Address = address!("0000000000000000000000000000000000000001"); const HIGHER_ADDRESS: Address = address!("0000000000000000000000000000000000000005"); @@ -517,8 +535,11 @@ mod tests { const fn assert_state_provider() {} #[allow(dead_code)] - const fn assert_historical_state_provider() { - assert_state_provider::>(); + const fn assert_historical_state_provider< + T: DBProvider + BlockNumReader + BlockHashReader, + SC: Send + Sync, + >() { + assert_state_provider::>(); } #[test] @@ -587,43 +608,46 @@ mod tests { let db = factory.provider().unwrap(); // run - assert_eq!(HistoricalStateProviderRef::new(&db, 1).basic_account(ADDRESS), Ok(None)); + assert_eq!(TestHistoricalStateProviderRef::new(&db, 1).basic_account(ADDRESS), Ok(None)); assert_eq!( - HistoricalStateProviderRef::new(&db, 2).basic_account(ADDRESS), + TestHistoricalStateProviderRef::new(&db, 2).basic_account(ADDRESS), Ok(Some(acc_at3)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 3).basic_account(ADDRESS), + TestHistoricalStateProviderRef::new(&db, 3).basic_account(ADDRESS), Ok(Some(acc_at3)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 4).basic_account(ADDRESS), + TestHistoricalStateProviderRef::new(&db, 4).basic_account(ADDRESS), Ok(Some(acc_at7)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 7).basic_account(ADDRESS), + TestHistoricalStateProviderRef::new(&db, 7).basic_account(ADDRESS), Ok(Some(acc_at7)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 9).basic_account(ADDRESS), + TestHistoricalStateProviderRef::new(&db, 9).basic_account(ADDRESS), Ok(Some(acc_at10)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 10).basic_account(ADDRESS), + TestHistoricalStateProviderRef::new(&db, 10).basic_account(ADDRESS), Ok(Some(acc_at10)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 11).basic_account(ADDRESS), + TestHistoricalStateProviderRef::new(&db, 11).basic_account(ADDRESS), Ok(Some(acc_at15)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 16).basic_account(ADDRESS), + TestHistoricalStateProviderRef::new(&db, 16).basic_account(ADDRESS), Ok(Some(acc_plain)) ); - assert_eq!(HistoricalStateProviderRef::new(&db, 1).basic_account(HIGHER_ADDRESS), Ok(None)); assert_eq!( - HistoricalStateProviderRef::new(&db, 1000).basic_account(HIGHER_ADDRESS), + TestHistoricalStateProviderRef::new(&db, 1).basic_account(HIGHER_ADDRESS), + Ok(None) + ); + assert_eq!( + TestHistoricalStateProviderRef::new(&db, 1000).basic_account(HIGHER_ADDRESS), Ok(Some(higher_acc_plain)) ); } @@ -681,41 +705,41 @@ mod tests { let db = factory.provider().unwrap(); // run - assert_eq!(HistoricalStateProviderRef::new(&db, 0).storage(ADDRESS, STORAGE), Ok(None)); + assert_eq!(TestHistoricalStateProviderRef::new(&db, 0).storage(ADDRESS, STORAGE), Ok(None)); assert_eq!( - HistoricalStateProviderRef::new(&db, 3).storage(ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 3).storage(ADDRESS, STORAGE), Ok(Some(U256::ZERO)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 4).storage(ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 4).storage(ADDRESS, STORAGE), Ok(Some(entry_at7.value)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 7).storage(ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 7).storage(ADDRESS, STORAGE), Ok(Some(entry_at7.value)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 9).storage(ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 9).storage(ADDRESS, STORAGE), Ok(Some(entry_at10.value)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 10).storage(ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 10).storage(ADDRESS, STORAGE), Ok(Some(entry_at10.value)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 11).storage(ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 11).storage(ADDRESS, STORAGE), Ok(Some(entry_at15.value)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 16).storage(ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 16).storage(ADDRESS, STORAGE), Ok(Some(entry_plain.value)) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 1).storage(HIGHER_ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 1).storage(HIGHER_ADDRESS, STORAGE), Ok(None) ); assert_eq!( - HistoricalStateProviderRef::new(&db, 1000).storage(HIGHER_ADDRESS, STORAGE), + TestHistoricalStateProviderRef::new(&db, 1000).storage(HIGHER_ADDRESS, STORAGE), Ok(Some(higher_entry_plain.value)) ); } @@ -727,7 +751,7 @@ mod tests { // provider block_number < lowest available block number, // i.e. state at provider block is pruned - let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( + let provider = TestHistoricalStateProviderRef::new_with_lowest_available_blocks( &db, 2, LowestAvailableBlocks { @@ -746,7 +770,7 @@ mod tests { // provider block_number == lowest available block number, // i.e. state at provider block is available - let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( + let provider = TestHistoricalStateProviderRef::new_with_lowest_available_blocks( &db, 2, LowestAvailableBlocks { @@ -762,7 +786,7 @@ mod tests { // provider block_number == lowest available block number, // i.e. state at provider block is available - let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( + let provider = TestHistoricalStateProviderRef::new_with_lowest_available_blocks( &db, 2, LowestAvailableBlocks { diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 297217acece7..bec5aa5e3ac6 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use crate::{ providers::state::macros::delegate_provider_impls, AccountReader, BlockHashReader, StateProvider, StateRootProvider, @@ -24,14 +26,15 @@ use reth_trie_db::{ /// State provider over latest state that takes tx reference. /// -/// Wraps a [`DBProvider`] to get access to database. +/// Wraps a [`DBProvider`] to get access to database and [`reth_trie_db::StateCommitment`] +/// (`PhantomData`) to get access to state commitment operations. #[derive(Debug)] -pub struct LatestStateProviderRef<'b, Provider>(&'b Provider); +pub struct LatestStateProviderRef<'b, Provider, SC>(&'b Provider, PhantomData); -impl<'b, Provider: DBProvider> LatestStateProviderRef<'b, Provider> { +impl<'b, Provider: DBProvider, SC> LatestStateProviderRef<'b, Provider, SC> { /// Create new state provider pub const fn new(provider: &'b Provider) -> Self { - Self(provider) + Self(provider, PhantomData) } fn tx(&self) -> &Provider::Tx { @@ -39,14 +42,18 @@ impl<'b, Provider: DBProvider> LatestStateProviderRef<'b, Provider> { } } -impl AccountReader for LatestStateProviderRef<'_, Provider> { +impl AccountReader + for LatestStateProviderRef<'_, Provider, SC> +{ /// Get basic account information. fn basic_account(&self, address: Address) -> ProviderResult> { self.tx().get::(address).map_err(Into::into) } } -impl BlockHashReader for LatestStateProviderRef<'_, Provider> { +impl BlockHashReader + for LatestStateProviderRef<'_, Provider, SC> +{ /// Get block hash by number. fn block_hash(&self, number: u64) -> ProviderResult> { self.0.block_hash(number) @@ -61,7 +68,9 @@ impl BlockHashReader for LatestStateProviderRef<'_, P } } -impl StateRootProvider for LatestStateProviderRef<'_, Provider> { +impl StateRootProvider + for LatestStateProviderRef<'_, Provider, SC> +{ fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult { StateRoot::overlay_root(self.tx(), hashed_state) .map_err(|err| ProviderError::Database(err.into())) @@ -89,7 +98,9 @@ impl StateRootProvider for LatestStateProviderRef<'_, Prov } } -impl StorageRootProvider for LatestStateProviderRef<'_, Provider> { +impl StorageRootProvider + for LatestStateProviderRef<'_, Provider, SC> +{ fn storage_root( &self, address: Address, @@ -110,7 +121,9 @@ impl StorageRootProvider for LatestStateProviderRef<'_, Pr } } -impl StateProofProvider for LatestStateProviderRef<'_, Provider> { +impl StateProofProvider + for LatestStateProviderRef<'_, Provider, SC> +{ fn proof( &self, input: TrieInput, @@ -138,8 +151,8 @@ impl StateProofProvider for LatestStateProviderRef<'_, Pro } } -impl StateProvider - for LatestStateProviderRef<'_, Provider> +impl StateProvider + for LatestStateProviderRef<'_, Provider, SC> { /// Get storage. fn storage( @@ -164,23 +177,23 @@ impl StateProvider /// State provider for the latest state. #[derive(Debug)] -pub struct LatestStateProvider(Provider); +pub struct LatestStateProvider(Provider, PhantomData); -impl LatestStateProvider { +impl LatestStateProvider { /// Create new state provider pub const fn new(db: Provider) -> Self { - Self(db) + Self(db, PhantomData) } /// Returns a new provider that takes the `TX` as reference #[inline(always)] - const fn as_ref(&self) -> LatestStateProviderRef<'_, Provider> { + const fn as_ref(&self) -> LatestStateProviderRef<'_, Provider, SC> { LatestStateProviderRef::new(&self.0) } } // Delegates all provider impls to [LatestStateProviderRef] -delegate_provider_impls!(LatestStateProvider where [Provider: DBProvider + BlockHashReader]); +delegate_provider_impls!(LatestStateProvider where [Provider: DBProvider + BlockHashReader, SC: Send + Sync]); #[cfg(test)] mod tests { @@ -188,7 +201,7 @@ mod tests { const fn assert_state_provider() {} #[allow(dead_code)] - const fn assert_latest_state_provider() { - assert_state_provider::>(); + const fn assert_latest_state_provider() { + assert_state_provider::>(); } } diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index d37940f04787..6c9e82cfba3b 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -91,6 +91,13 @@ pub trait TryIntoHistoricalStateProvider { ) -> ProviderResult; } +/// Trait implemented for database providers that can be converted into a latest state provider +/// reference. +pub trait AsLatestStateProviderRef { + /// Returns a [`StateProvider`] for the latest state. + fn latest<'a>(&'a self) -> Box; +} + /// Light wrapper that returns `StateProvider` implementations that correspond to the given /// `BlockNumber`, the latest state, or the pending state. /// From a3bd117e01d42674e16a55957ac64c9173ab42b5 Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 20 Nov 2024 01:25:46 +0800 Subject: [PATCH 04/38] feat: introduce binary partricia trie state components --- Cargo.lock | 145 +++- Cargo.toml | 7 + crates/scroll/primitives/Cargo.toml | 17 + crates/scroll/primitives/src/lib.rs | 42 + crates/scroll/primitives/src/poseidon.rs | 74 ++ crates/scroll/state-commitment/Cargo.toml | 46 ++ crates/scroll/state-commitment/src/key.rs | 26 + crates/scroll/state-commitment/src/lib.rs | 11 + crates/scroll/state-commitment/src/root.rs | 829 ++++++++++++++++++++ crates/scroll/state-commitment/src/value.rs | 57 ++ crates/scroll/trie/Cargo.toml | 4 +- crates/scroll/trie/src/branch.rs | 51 +- crates/scroll/trie/src/hash_builder.rs | 264 ++++--- crates/scroll/trie/src/key.rs | 32 - crates/scroll/trie/src/leaf.rs | 23 + crates/scroll/trie/src/lib.rs | 17 +- crates/scroll/trie/src/sub_tree.rs | 24 +- crates/trie/trie/Cargo.toml | 3 + crates/trie/trie/src/key.rs | 92 +++ crates/trie/trie/src/lib.rs | 3 + crates/trie/trie/src/node_iter.rs | 10 +- crates/trie/trie/src/proof.rs | 22 +- crates/trie/trie/src/state.rs | 11 +- crates/trie/trie/src/trie.rs | 10 +- crates/trie/trie/src/updates.rs | 24 +- crates/trie/trie/src/walker.rs | 6 +- crates/trie/trie/src/witness.rs | 7 +- 27 files changed, 1661 insertions(+), 196 deletions(-) create mode 100644 crates/scroll/primitives/Cargo.toml create mode 100644 crates/scroll/primitives/src/lib.rs create mode 100644 crates/scroll/primitives/src/poseidon.rs create mode 100644 crates/scroll/state-commitment/Cargo.toml create mode 100644 crates/scroll/state-commitment/src/key.rs create mode 100644 crates/scroll/state-commitment/src/lib.rs create mode 100644 crates/scroll/state-commitment/src/root.rs create mode 100644 crates/scroll/state-commitment/src/value.rs delete mode 100644 crates/scroll/trie/src/key.rs create mode 100644 crates/scroll/trie/src/leaf.rs create mode 100644 crates/trie/trie/src/key.rs diff --git a/Cargo.lock b/Cargo.lock index aec1a2a47a4f..8ef4ee89c92d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,7 +107,7 @@ dependencies = [ "num_enum", "proptest", "serde", - "strum", + "strum 0.26.3", ] [[package]] @@ -492,7 +492,7 @@ dependencies = [ "jsonwebtoken", "rand 0.8.5", "serde", - "strum", + "strum 0.26.3", ] [[package]] @@ -620,7 +620,7 @@ checksum = "9ed5047c9a241df94327879c2b0729155b58b941eae7805a7ada2e19436e6b39" dependencies = [ "alloy-sol-macro-input", "const-hex", - "heck", + "heck 0.5.0", "indexmap 2.6.0", "proc-macro-error2", "proc-macro2", @@ -638,7 +638,7 @@ checksum = "5dee02a81f529c415082235129f0df8b8e60aa1601b9c9298ffe54d75f57210b" dependencies = [ "const-hex", "dunce", - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.79", @@ -1739,7 +1739,7 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.79", @@ -1825,8 +1825,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "crossterm 0.27.0", - "strum", - "strum_macros", + "strum 0.26.3", + "strum_macros 0.26.4", "unicode-width", ] @@ -2645,7 +2645,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.79", @@ -3390,6 +3390,14 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gobuild" +version = "0.1.0-alpha.2" +source = "git+https://github.com/scroll-tech/gobuild.git#24935c2b8f677841f22acd6710957621bb294e0e" +dependencies = [ + "cc", +] + [[package]] name = "group" version = "0.13.0" @@ -3489,6 +3497,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -4312,7 +4326,7 @@ version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9a4b2eaba8cc928f49c4ccf4fcfa65b690a73997682da99ed08f3393b51f07" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro-crate", "proc-macro2", "quote", @@ -5047,6 +5061,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-format" version = "0.4.4" @@ -6085,8 +6110,8 @@ dependencies = [ "itertools 0.13.0", "lru", "paste", - "strum", - "strum_macros", + "strum 0.26.3", + "strum_macros 0.26.4", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -6807,7 +6832,7 @@ dependencies = [ "rustc-hash 2.0.0", "serde", "serde_json", - "strum", + "strum 0.26.3", "sysinfo", "tempfile", "test-fuzz", @@ -7939,7 +7964,7 @@ dependencies = [ "secp256k1", "serde", "shellexpand", - "strum", + "strum 0.26.3", "tempfile", "thiserror", "tokio", @@ -8472,7 +8497,7 @@ dependencies = [ "reth-trie", "reth-trie-db", "revm", - "strum", + "strum 0.26.3", "tempfile", "tokio", "tracing", @@ -8856,7 +8881,7 @@ dependencies = [ "reth-network-api", "reth-primitives", "serde", - "strum", + "strum 0.26.3", ] [[package]] @@ -9003,7 +9028,7 @@ dependencies = [ "clap", "derive_more 1.0.0", "serde", - "strum", + "strum 0.26.3", ] [[package]] @@ -9160,6 +9185,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "smallvec", "tokio", "tracing", "triehash", @@ -9717,6 +9743,42 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll-primitives" +version = "1.1.0" +dependencies = [ + "alloy-primitives", + "poseidon-bn254", + "reth-trie", +] + +[[package]] +name = "scroll-state-commitment" +version = "1.1.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "metrics", + "poseidon-bn254", + "proptest", + "proptest-arbitrary-interop", + "reth-db", + "reth-execution-errors", + "reth-metrics", + "reth-primitives", + "reth-provider", + "reth-trie", + "reth-trie-common", + "reth-trie-db", + "scroll-primitives", + "scroll-trie", + "tracing", + "tracing-subscriber", + "zktrie", + "zktrie_rust", +] + [[package]] name = "scroll-trie" version = "1.1.0" @@ -9724,7 +9786,9 @@ dependencies = [ "alloy-primitives", "alloy-trie", "hex-literal", - "poseidon-bn254", + "proptest-arbitrary-interop", + "reth-trie", + "scroll-primitives", "tracing", "tracing-subscriber", ] @@ -10238,13 +10302,32 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + [[package]] name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", ] [[package]] @@ -10253,7 +10336,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", @@ -10430,7 +10513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7e6b4c7391a38f0f026972ec2200bcfd1ec45533aa266fdae5858d011afc500" dependencies = [ "darling", - "heck", + "heck 0.5.0", "itertools 0.13.0", "once_cell", "prettyplease", @@ -11872,6 +11955,28 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "zktrie" +version = "0.3.0" +dependencies = [ + "gobuild", + "zktrie_rust", +] + +[[package]] +name = "zktrie_rust" +version = "0.3.0" +dependencies = [ + "hex", + "lazy_static", + "log", + "num", + "num-derive", + "num-traits", + "strum 0.24.1", + "strum_macros 0.24.3", +] + [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index 4ac372aa3245..188921d86f7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,8 @@ members = [ "crates/rpc/rpc-testing-util/", "crates/rpc/rpc-types-compat/", "crates/rpc/rpc/", + "crates/scroll/primitives", + "crates/scroll/state-commitment", "crates/scroll/trie", "crates/stages/api/", "crates/stages/stages/", @@ -410,6 +412,11 @@ reth-trie-common = { path = "crates/trie/common" } reth-trie-db = { path = "crates/trie/db" } reth-trie-parallel = { path = "crates/trie/parallel" } +# scroll +scroll-state-commitment = { path = "crates/scroll/state-commitment" } +scroll-trie = { path = "crates/scroll/trie" } +scroll-primitives = { path = "crates/scroll/primitives" } + # revm revm = { version = "14.0.3", features = [ "std", diff --git a/crates/scroll/primitives/Cargo.toml b/crates/scroll/primitives/Cargo.toml new file mode 100644 index 000000000000..149d1b81bce9 --- /dev/null +++ b/crates/scroll/primitives/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "scroll-primitives" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +alloy-primitives.workspace = true +reth-trie.workspace = true +poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", branch = "master" } + +[lints] +workspace = true diff --git a/crates/scroll/primitives/src/lib.rs b/crates/scroll/primitives/src/lib.rs new file mode 100644 index 000000000000..6a8e24911e05 --- /dev/null +++ b/crates/scroll/primitives/src/lib.rs @@ -0,0 +1,42 @@ +//! Standalone crate for Scroll-specific Reth primitive types. + +use alloy_primitives::{B256, U256}; +use reth_trie::TrieAccount; + +/// Poseidon hashing primitives. +pub mod poseidon; + +/// A Scroll account as represented in the trie. +#[derive(Debug)] +pub struct ScrollTrieAccount { + /// nonce + pub nonce: u64, + /// code size + pub code_size: u64, + /// balance + pub balance: U256, + /// storage root + pub storage_root: B256, + /// keccak code hash + pub code_hash: B256, + /// poseidon code hash + pub poseidon_code_hash: B256, +} + +// TODO: Temporary method to convert from standard ethereum `TrieAccount` to `ScrollTrieAccount` +// TODO: Fix cast +impl From for ScrollTrieAccount { + fn from(value: TrieAccount) -> Self { + ScrollTrieAccount { + // TODO(frisitano): introduce code size and poseidon code hash following integration + // with Account changes + poseidon_code_hash: Default::default(), + code_size: Default::default(), + nonce: value.nonce, + balance: value.balance, + // TODO(frisitano): introduce storage root + storage_root: Default::default(), + code_hash: value.code_hash, + } + } +} diff --git a/crates/scroll/primitives/src/poseidon.rs b/crates/scroll/primitives/src/poseidon.rs new file mode 100644 index 000000000000..66a718b93f24 --- /dev/null +++ b/crates/scroll/primitives/src/poseidon.rs @@ -0,0 +1,74 @@ +pub use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; + +/// Type that is used to represent a field element in binary representation. +pub type FieldElementBytes = ::Repr; + +/// The number of bytes in the binary representation of a field element. +pub const FIELD_ELEMENT_REPR_BYTES: usize = core::mem::size_of::(); + +// Half the number of bytes in the binary representation of a field element. +const HALF_FIELD_ELEMENT_REPR_BYTES: usize = FIELD_ELEMENT_REPR_BYTES / 2; + +/// The domain multiplier per field element. +pub const DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT: u64 = 256; + +/// The domain for hashing two field elements. +pub const DOMAIN_TWO_FIELD_ELEMENTS: Fr = + Fr::from_raw([DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT * 2, 0, 0, 0]); + +/// Hash two field elements using poseidon. +pub fn hash(element_1: Fr, element_2: Fr) -> Fr { + hash_with_domain(&[element_1, element_2], DOMAIN_TWO_FIELD_ELEMENTS) +} + +/// Split and transform input be bytes into two field elements and hash using poseidon. +/// +/// # Panics +/// +/// This function will panic if more than 32 bytes are provided as input. +pub fn split_and_hash_be_bytes>(bytes: T) -> Fr { + debug_assert!( + bytes.as_ref().len() <= FIELD_ELEMENT_REPR_BYTES, + "bytes length should be less than or equal to field element bytes" + ); + let (bytes_lo, bytes_hi) = split_and_parse_field_elements(bytes.as_ref()); + hash(bytes_lo, bytes_hi) +} + +/// Parse input bytes into two field elements which represent the lower bytes and the upper bytes. +/// +/// # Panics +/// +/// This function will panic if more than 32 bytes are provided as input. +fn split_and_parse_field_elements(bytes: &[u8]) -> (Fr, Fr) { + debug_assert!( + bytes.len() <= FIELD_ELEMENT_REPR_BYTES, + "bytes length should be less than or equal to field element bytes" + ); + let mut bytes_lo = FieldElementBytes::default(); + let mut bytes_hi = FieldElementBytes::default(); + + if bytes.len() > (HALF_FIELD_ELEMENT_REPR_BYTES) { + bytes_lo[HALF_FIELD_ELEMENT_REPR_BYTES..] + .copy_from_slice(&bytes[..HALF_FIELD_ELEMENT_REPR_BYTES]); + bytes_hi[HALF_FIELD_ELEMENT_REPR_BYTES..bytes.len()] + .copy_from_slice(&bytes[HALF_FIELD_ELEMENT_REPR_BYTES..]); + } else { + bytes_lo[HALF_FIELD_ELEMENT_REPR_BYTES..(HALF_FIELD_ELEMENT_REPR_BYTES + bytes.len())] + .copy_from_slice(bytes) + } + + let bytes_lo = field_element_from_be_bytes(bytes_lo); + let bytes_hi = field_element_from_be_bytes(bytes_hi); + (bytes_lo, bytes_hi) +} + +/// Parses a field element from big endian bytes. +/// +/// # Panics +/// +/// This function will panic if the bytes are not a valid field element. +pub fn field_element_from_be_bytes(mut bytes: FieldElementBytes) -> Fr { + bytes.reverse(); + Fr::from_repr_vartime(bytes).expect("valid field element") +} diff --git a/crates/scroll/state-commitment/Cargo.toml b/crates/scroll/state-commitment/Cargo.toml new file mode 100644 index 000000000000..35312faeda26 --- /dev/null +++ b/crates/scroll/state-commitment/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "scroll-state-commitment" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +alloy-primitives.workspace = true +alloy-rlp.workspace = true +reth-execution-errors.workspace = true +reth-trie.workspace = true +reth-primitives.workspace = true +tracing.workspace = true +scroll-trie.workspace = true +scroll-primitives.workspace = true +poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", branch = "master" } +reth-trie-db.workspace = true +reth-db = { workspace = true, features = ["test-utils"] } + +# `metrics` feature +reth-metrics = { workspace = true, optional = true } +metrics = { workspace = true, optional = true } + +[dev-dependencies] +alloy-consensus.workspace = true +reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } +reth-trie = { workspace = true, features = ["test-utils"] } +reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } +reth-provider = { workspace = true, features = ["test-utils"] } +proptest.workspace = true +proptest-arbitrary-interop.workspace = true +zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie" } +zktrie = { path = "/Users/f/dev/scroll/zktrie", features = ["rs_zktrie"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] } + +[lints] +workspace = true + +[features] +metrics = ["reth-metrics", "dep:metrics"] + + diff --git a/crates/scroll/state-commitment/src/key.rs b/crates/scroll/state-commitment/src/key.rs new file mode 100644 index 000000000000..3442ff527da8 --- /dev/null +++ b/crates/scroll/state-commitment/src/key.rs @@ -0,0 +1,26 @@ +use alloy_primitives::B256; +use scroll_primitives::poseidon::{split_and_hash_be_bytes, PrimeField, FIELD_ELEMENT_REPR_BYTES}; + +// TODO(frisitano): Implement `KeyHasher` trait from upstream. Also consider introducing a +// `HashingScheme` trait that combines both `KeyHasher` and `ValueHasher` traits via GATs. + +/// An implementation of a key hasher that uses Poseidon. +#[derive(Debug)] +pub struct PoseidonKeyHasher; + +impl PoseidonKeyHasher { + /// Hashes the key using the Poseidon hash function. + /// + /// The bytes are expected to be provided in big endian format. + /// + /// Panics if the number of bytes provided is greater than the number of bytes in the + /// binary representation of a field element (32). + /// + /// Returns the hash digest in little endian representation with bits reversed. + pub fn hash_key>(bytes: T) -> B256 { + debug_assert!(bytes.as_ref().len() <= FIELD_ELEMENT_REPR_BYTES); + let mut bytes = split_and_hash_be_bytes(bytes.as_ref()).to_repr(); + bytes.iter_mut().for_each(|byte| *byte = byte.reverse_bits()); + bytes.into() + } +} diff --git a/crates/scroll/state-commitment/src/lib.rs b/crates/scroll/state-commitment/src/lib.rs new file mode 100644 index 000000000000..ca07610dfdc5 --- /dev/null +++ b/crates/scroll/state-commitment/src/lib.rs @@ -0,0 +1,11 @@ +//! The implementation of scrolls binary Merkle Patricia Trie used a cryptographic state commitment. + +mod root; +pub use root::{StateRoot, StorageRoot}; + +mod key; +mod value; + +// RE-EXPORTS +pub use key::PoseidonKeyHasher; +pub use value::PosiedonValueHasher; diff --git a/crates/scroll/state-commitment/src/root.rs b/crates/scroll/state-commitment/src/root.rs new file mode 100644 index 000000000000..1836109c48e7 --- /dev/null +++ b/crates/scroll/state-commitment/src/root.rs @@ -0,0 +1,829 @@ +use super::{PoseidonKeyHasher, PosiedonValueHasher}; +use alloy_primitives::{Address, BlockNumber, B256}; +use reth_db::transaction::DbTx; +use reth_execution_errors::{StateRootError, StorageRootError}; +use reth_primitives::constants::EMPTY_ROOT_HASH; +use reth_trie::{ + hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory, HashedStorageCursor}, + key::BitsCompatibility, + node_iter::{TrieElement, TrieNodeIter}, + prefix_set::{PrefixSet, TriePrefixSets}, + stats::TrieTracker, + trie_cursor::{InMemoryTrieCursorFactory, TrieCursorFactory}, + updates::{StorageTrieUpdates, TrieUpdates}, + walker::TrieWalker, + HashedPostState, IntermediateStateRootState, Nibbles, StateRootProgress, TrieAccount, + TrieInput, +}; +use scroll_primitives::ScrollTrieAccount; +use scroll_trie::HashBuilder; +use tracing::{debug, trace}; + +#[cfg(feature = "metrics")] +use crate::metrics::{StateRootMetrics, TrieRootMetrics}; + +// TODO(frisitano): Instead of introducing this new type we should + +/// `StateRoot` is used to compute the root node of a state trie. +#[derive(Debug)] +pub struct StateRoot { + /// The factory for trie cursors. + pub trie_cursor_factory: T, + /// The factory for hashed cursors. + pub hashed_cursor_factory: H, + /// A set of prefix sets that have changed. + pub prefix_sets: TriePrefixSets, + /// Previous intermediate state. + previous_state: Option, + /// The number of updates after which the intermediate progress should be returned. + threshold: u64, + #[cfg(feature = "metrics")] + /// State root metrics. + metrics: StateRootMetrics, +} + +impl StateRoot { + /// Creates [`StateRoot`] with `trie_cursor_factory` and `hashed_cursor_factory`. All other + /// parameters are set to reasonable defaults. + /// + /// The cursors created by given factories are then used to walk through the accounts and + /// calculate the state root value with. + pub fn new(trie_cursor_factory: T, hashed_cursor_factory: H) -> Self { + Self { + trie_cursor_factory, + hashed_cursor_factory, + prefix_sets: TriePrefixSets::default(), + previous_state: None, + threshold: 100_000, + #[cfg(feature = "metrics")] + metrics: StateRootMetrics::default(), + } + } + + /// Set the prefix sets. + pub fn with_prefix_sets(mut self, prefix_sets: TriePrefixSets) -> Self { + self.prefix_sets = prefix_sets; + self + } + + /// Set the threshold. + pub const fn with_threshold(mut self, threshold: u64) -> Self { + self.threshold = threshold; + self + } + + /// Set the threshold to maximum value so that intermediate progress is not returned. + pub const fn with_no_threshold(mut self) -> Self { + self.threshold = u64::MAX; + self + } + + /// Set the previously recorded intermediate state. + pub fn with_intermediate_state(mut self, state: Option) -> Self { + self.previous_state = state; + self + } + + /// Set the hashed cursor factory. + pub fn with_hashed_cursor_factory(self, hashed_cursor_factory: HF) -> StateRoot { + StateRoot { + trie_cursor_factory: self.trie_cursor_factory, + hashed_cursor_factory, + prefix_sets: self.prefix_sets, + threshold: self.threshold, + previous_state: self.previous_state, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } + + /// Set the trie cursor factory. + pub fn with_trie_cursor_factory(self, trie_cursor_factory: TF) -> StateRoot { + StateRoot { + trie_cursor_factory, + hashed_cursor_factory: self.hashed_cursor_factory, + prefix_sets: self.prefix_sets, + threshold: self.threshold, + previous_state: self.previous_state, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } +} + +impl StateRoot +where + T: TrieCursorFactory + Clone, + H: HashedCursorFactory + Clone, +{ + /// Walks the intermediate nodes of existing state trie (if any) and hashed entries. Feeds the + /// nodes into the hash builder. Collects the updates in the process. + /// + /// Ignores the threshold. + /// + /// # Returns + /// + /// The intermediate progress of state root computation and the trie updates. + pub fn root_with_updates(self) -> Result<(B256, TrieUpdates), StateRootError> { + match self.with_no_threshold().calculate(true)? { + StateRootProgress::Complete(root, _, updates) => Ok((root, updates)), + StateRootProgress::Progress(..) => unreachable!(), // unreachable threshold + } + } + + /// Walks the intermediate nodes of existing state trie (if any) and hashed entries. Feeds the + /// nodes into the hash builder. + /// + /// # Returns + /// + /// The state root hash. + pub fn root(self) -> Result { + match self.calculate(false)? { + StateRootProgress::Complete(root, _, _) => Ok(root), + StateRootProgress::Progress(..) => unreachable!(), // update retenion is disabled + } + } + + /// Walks the intermediate nodes of existing state trie (if any) and hashed entries. Feeds the + /// nodes into the hash builder. Collects the updates in the process. + /// + /// # Returns + /// + /// The intermediate progress of state root computation. + pub fn root_with_progress(self) -> Result { + self.calculate(true) + } + + fn calculate(self, retain_updates: bool) -> Result { + trace!(target: "trie::state_root", "calculating state root"); + let mut tracker = TrieTracker::default(); + let mut trie_updates = TrieUpdates::default(); + + let trie_cursor = self.trie_cursor_factory.account_trie_cursor()?; + + let hashed_account_cursor = self.hashed_cursor_factory.hashed_account_cursor()?; + let (mut hash_builder, mut account_node_iter) = match self.previous_state { + Some(state) => { + let hash_builder = state.hash_builder.with_updates(retain_updates).into(); + + let walker = TrieWalker::from_stack( + trie_cursor, + state.walker_stack, + self.prefix_sets.account_prefix_set, + ) + .with_deletions_retained(retain_updates); + let node_iter = TrieNodeIter::new(walker, hashed_account_cursor) + .with_last_hashed_key(state.last_account_key); + (hash_builder, node_iter) + } + None => { + let hash_builder = HashBuilder::default().with_updates(retain_updates); + let walker = TrieWalker::new(trie_cursor, self.prefix_sets.account_prefix_set) + .with_deletions_retained(retain_updates); + let node_iter = TrieNodeIter::new(walker, hashed_account_cursor); + (hash_builder, node_iter) + } + }; + + let mut hashed_entries_walked = 0; + let mut updated_storage_nodes = 0; + while let Some(node) = account_node_iter.try_next()? { + match node { + TrieElement::Branch(node) => { + tracker.inc_branch(); + hash_builder.add_branch(node.key, node.value, node.children_are_in_trie); + } + TrieElement::Leaf(hashed_address, account) => { + tracker.inc_leaf(); + hashed_entries_walked += 1; + + // We assume we can always calculate a storage root without + // OOMing. This opens us up to a potential DOS vector if + // a contract had too many storage entries and they were + // all buffered w/o us returning and committing our intermediate + // progress. + // TODO: We can consider introducing the TrieProgress::Progress/Complete + // abstraction inside StorageRoot, but let's give it a try as-is for now. + let storage_root_calculator = StorageRoot::new_hashed( + self.trie_cursor_factory.clone(), + self.hashed_cursor_factory.clone(), + hashed_address, + #[cfg(feature = "metrics")] + self.metrics.storage_trie.clone(), + ) + .with_prefix_set( + self.prefix_sets + .storage_prefix_sets + .get(&hashed_address) + .cloned() + .unwrap_or_default(), + ); + + let storage_root = if retain_updates { + let (root, storage_slots_walked, updates) = + storage_root_calculator.root_with_updates()?; + hashed_entries_walked += storage_slots_walked; + // We only walk over hashed address once, so it's safe to insert. + updated_storage_nodes += updates.len(); + trie_updates.insert_storage_updates(hashed_address, updates); + root + } else { + storage_root_calculator.root()? + }; + + let account: ScrollTrieAccount = + TrieAccount::from((account, storage_root)).into(); + let account_hash = PosiedonValueHasher::hash_account(account); + hash_builder.add_leaf( + Nibbles::unpack_and_truncate_bits(hashed_address), + account_hash.as_slice(), + ); + + // Decide if we need to return intermediate progress. + let total_updates_len = updated_storage_nodes + + account_node_iter.walker.removed_keys_len() + + hash_builder.updates_len(); + if retain_updates && total_updates_len as u64 >= self.threshold { + let (walker_stack, walker_deleted_keys) = account_node_iter.walker.split(); + trie_updates.removed_nodes.extend(walker_deleted_keys); + let (hash_builder, hash_builder_updates) = hash_builder.split(); + trie_updates.account_nodes.extend(hash_builder_updates); + + let state = IntermediateStateRootState { + hash_builder: hash_builder.into(), + walker_stack, + last_account_key: hashed_address, + }; + + return Ok(StateRootProgress::Progress( + Box::new(state), + hashed_entries_walked, + trie_updates, + )) + } + } + } + } + + let root = hash_builder.root(); + + trie_updates.finalize( + account_node_iter.walker, + hash_builder.into(), + self.prefix_sets.destroyed_accounts, + ); + + let stats = tracker.finish(); + + #[cfg(feature = "metrics")] + self.metrics.state_trie.record(stats); + + trace!( + target: "trie::state_root", + %root, + duration = ?stats.duration(), + branches_added = stats.branches_added(), + leaves_added = stats.leaves_added(), + "calculated state root" + ); + + Ok(StateRootProgress::Complete(root, hashed_entries_walked, trie_updates)) + } +} + +/// `StorageRoot` is used to compute the root node of an account storage trie. +#[derive(Debug)] +pub struct StorageRoot { + /// A reference to the database transaction. + pub trie_cursor_factory: T, + /// The factory for hashed cursors. + pub hashed_cursor_factory: H, + /// The hashed address of an account. + pub hashed_address: B256, + /// The set of storage slot prefixes that have changed. + pub prefix_set: PrefixSet, + /// Storage root metrics. + #[cfg(feature = "metrics")] + metrics: TrieRootMetrics, +} + +impl StorageRoot { + /// Creates a new storage root calculator given a raw address. + pub fn new( + trie_cursor_factory: T, + hashed_cursor_factory: H, + address: Address, + #[cfg(feature = "metrics")] metrics: TrieRootMetrics, + ) -> Self { + Self::new_hashed( + trie_cursor_factory, + hashed_cursor_factory, + PoseidonKeyHasher::hash_key(address), + #[cfg(feature = "metrics")] + metrics, + ) + } + + /// Creates a new storage root calculator given a hashed address. + pub fn new_hashed( + trie_cursor_factory: T, + hashed_cursor_factory: H, + hashed_address: B256, + #[cfg(feature = "metrics")] metrics: TrieRootMetrics, + ) -> Self { + Self { + trie_cursor_factory, + hashed_cursor_factory, + hashed_address, + prefix_set: PrefixSet::default(), + #[cfg(feature = "metrics")] + metrics, + } + } + + /// Set the changed prefixes. + pub fn with_prefix_set(mut self, prefix_set: PrefixSet) -> Self { + self.prefix_set = prefix_set; + self + } + + /// Set the hashed cursor factory. + pub fn with_hashed_cursor_factory(self, hashed_cursor_factory: HF) -> StorageRoot { + StorageRoot { + trie_cursor_factory: self.trie_cursor_factory, + hashed_cursor_factory, + hashed_address: self.hashed_address, + prefix_set: self.prefix_set, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } + + /// Set the trie cursor factory. + pub fn with_trie_cursor_factory(self, trie_cursor_factory: TF) -> StorageRoot { + StorageRoot { + trie_cursor_factory, + hashed_cursor_factory: self.hashed_cursor_factory, + hashed_address: self.hashed_address, + prefix_set: self.prefix_set, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } +} + +impl StorageRoot +where + T: TrieCursorFactory, + H: HashedCursorFactory, +{ + /// Walks the hashed storage table entries for a given address and calculates the storage root. + /// + /// # Returns + /// + /// The storage root and storage trie updates for a given address. + pub fn root_with_updates(self) -> Result<(B256, usize, StorageTrieUpdates), StorageRootError> { + self.calculate(true) + } + + /// Walks the hashed storage table entries for a given address and calculates the storage root. + /// + /// # Returns + /// + /// The storage root. + pub fn root(self) -> Result { + let (root, _, _) = self.calculate(false)?; + Ok(root) + } + + /// Walks the hashed storage table entries for a given address and calculates the storage root. + /// + /// # Returns + /// + /// The storage root, number of walked entries and trie updates + /// for a given address if requested. + pub fn calculate( + self, + retain_updates: bool, + ) -> Result<(B256, usize, StorageTrieUpdates), StorageRootError> { + trace!(target: "trie::storage_root", hashed_address = ?self.hashed_address, "calculating storage root"); + + let mut hashed_storage_cursor = + self.hashed_cursor_factory.hashed_storage_cursor(self.hashed_address)?; + + // short circuit on empty storage + if hashed_storage_cursor.is_storage_empty()? { + return Ok((EMPTY_ROOT_HASH, 0, StorageTrieUpdates::deleted())) + } + + let mut tracker = TrieTracker::default(); + let trie_cursor = self.trie_cursor_factory.storage_trie_cursor(self.hashed_address)?; + let walker = + TrieWalker::new(trie_cursor, self.prefix_set).with_deletions_retained(retain_updates); + + let mut hash_builder = HashBuilder::default().with_updates(retain_updates); + + let mut storage_node_iter = TrieNodeIter::new(walker, hashed_storage_cursor); + while let Some(node) = storage_node_iter.try_next()? { + match node { + TrieElement::Branch(node) => { + tracker.inc_branch(); + hash_builder.add_branch(node.key, node.value, node.children_are_in_trie); + } + TrieElement::Leaf(hashed_slot, value) => { + let hashed_value = PosiedonValueHasher::hash_storage(value); + tracker.inc_leaf(); + hash_builder.add_leaf( + Nibbles::unpack_and_truncate_bits(hashed_slot), + hashed_value.as_ref(), + ); + } + } + } + + let root = hash_builder.root(); + + let mut trie_updates = StorageTrieUpdates::default(); + trie_updates.finalize(storage_node_iter.walker, hash_builder.into()); + + let stats = tracker.finish(); + + #[cfg(feature = "metrics")] + self.metrics.record(stats); + + trace!( + target: "trie::storage_root", + %root, + hashed_address = %self.hashed_address, + duration = ?stats.duration(), + branches_added = stats.branches_added(), + leaves_added = stats.leaves_added(), + "calculated storage root" + ); + + let storage_slots_walked = stats.leaves_added() as usize; + Ok((root, storage_slots_walked, trie_updates)) + } +} + +use reth_trie_db::{ + DatabaseHashedCursorFactory, DatabaseStateRoot, DatabaseTrieCursorFactory, PrefixSetLoader, +}; +use std::ops::RangeInclusive; + +impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> + for StateRoot, DatabaseHashedCursorFactory<'a, TX>> +{ + fn from_tx(tx: &'a TX) -> Self { + Self::new(DatabaseTrieCursorFactory::new(tx), DatabaseHashedCursorFactory::new(tx)) + } + + fn incremental_root_calculator( + tx: &'a TX, + range: RangeInclusive, + ) -> Result { + let loaded_prefix_sets = PrefixSetLoader::new(tx).load(range)?; + Ok(Self::from_tx(tx).with_prefix_sets(loaded_prefix_sets)) + } + + fn incremental_root( + tx: &'a TX, + range: RangeInclusive, + ) -> Result { + debug!(target: "trie::loader", ?range, "incremental state root"); + Self::incremental_root_calculator(tx, range)?.root() + } + + fn incremental_root_with_updates( + tx: &'a TX, + range: RangeInclusive, + ) -> Result<(B256, TrieUpdates), StateRootError> { + debug!(target: "trie::loader", ?range, "incremental state root"); + Self::incremental_root_calculator(tx, range)?.root_with_updates() + } + + fn incremental_root_with_progress( + tx: &'a TX, + range: RangeInclusive, + ) -> Result { + debug!(target: "trie::loader", ?range, "incremental state root with progress"); + Self::incremental_root_calculator(tx, range)?.root_with_progress() + } + + fn overlay_root(tx: &'a TX, post_state: HashedPostState) -> Result { + let prefix_sets = post_state.construct_prefix_sets().freeze(); + let state_sorted = post_state.into_sorted(); + StateRoot::new( + DatabaseTrieCursorFactory::new(tx), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + ) + .with_prefix_sets(prefix_sets) + .root() + } + + fn overlay_root_with_updates( + tx: &'a TX, + post_state: HashedPostState, + ) -> Result<(B256, TrieUpdates), StateRootError> { + let prefix_sets = post_state.construct_prefix_sets().freeze(); + let state_sorted = post_state.into_sorted(); + StateRoot::new( + DatabaseTrieCursorFactory::new(tx), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + ) + .with_prefix_sets(prefix_sets) + .root_with_updates() + } + + fn overlay_root_from_nodes(tx: &'a TX, input: TrieInput) -> Result { + let state_sorted = input.state.into_sorted(); + let nodes_sorted = input.nodes.into_sorted(); + StateRoot::new( + InMemoryTrieCursorFactory::new(DatabaseTrieCursorFactory::new(tx), &nodes_sorted), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + ) + .with_prefix_sets(input.prefix_sets.freeze()) + .root() + } + + fn overlay_root_from_nodes_with_updates( + tx: &'a TX, + input: TrieInput, + ) -> Result<(B256, TrieUpdates), StateRootError> { + let state_sorted = input.state.into_sorted(); + let nodes_sorted = input.nodes.into_sorted(); + StateRoot::new( + InMemoryTrieCursorFactory::new(DatabaseTrieCursorFactory::new(tx), &nodes_sorted), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + ) + .with_prefix_sets(input.prefix_sets.freeze()) + .root_with_updates() + } +} + +#[cfg(test)] +mod test { + use super::StateRoot; + use alloy_consensus::constants::KECCAK_EMPTY; + use alloy_primitives::{ + aliases::U248, b256, hex_literal::hex, keccak256, Address, FixedBytes, Uint, B256, U256, + }; + use proptest::{prelude::ProptestConfig, proptest}; + use proptest_arbitrary_interop::arb; + use reth_db::{ + cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, + tables, + test_utils::TempDatabase, + transaction::{DbTx, DbTxMut}, + DatabaseEnv, + }; + use reth_primitives::{constants::EMPTY_ROOT_HASH, Account, StorageEntry}; + use reth_provider::{ + test_utils::create_test_provider_factory, DatabaseProviderRW, StorageTrieWriter, TrieWriter, + }; + use reth_trie::{ + prefix_set::PrefixSetMut, + test_utils::{state_root, state_root_prehashed, storage_root, storage_root_prehashed}, + BranchNodeCompact, StorageRoot, TrieMask, + }; + use reth_trie_db::{DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory}; + use std::{ + collections::{BTreeMap, HashMap}, + ops::Mul, + str::FromStr, + sync::Arc, + }; + + use alloy_rlp::Encodable; + use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; + use reth_trie::{ + prefix_set::TriePrefixSets, trie_cursor::InMemoryTrieCursorFactory, + updates::StorageTrieUpdates, HashedPostState, IntermediateStateRootState, Nibbles, + StateRootProgress, TrieAccount, + }; + use std::sync::Once; + use tracing_subscriber::{self, fmt::format::FmtSpan}; + use zktrie::HashField; + use zktrie_rust::{ + db::SimpleDb, + hash::AsHash, + types::{Hashable, TrieHashScheme}, + }; + + static INIT: Once = Once::new(); + + pub fn init_test_logger() { + INIT.call_once(|| { + tracing_subscriber::fmt() + .with_test_writer() // Capture logs for test output + .with_span_events(FmtSpan::CLOSE) // Optional: Add span events + .with_env_filter("trace") // Set log level as needed + .init(); + }); + } + + // TODO(frisitano): Clean up tests and add tests for storage trie. + + type State = BTreeMap)>; + + proptest! { + #![proptest_config(ProptestConfig { + cases: 1, ..ProptestConfig::default() + })] + + #[test] + fn fuzz_in_memory_account_nodes(mut init_state: BTreeMap)>, state_updates: [BTreeMap>; 4]) { + // init_test_logger(); + + let mut init_state: BTreeMap = init_state.into_iter().take(1).map(|(mut address, (nonce, mut balance, bytecode_hash))| { + // set the largest byte to 0 + >::as_mut(&mut address)[31] = 0; + // set the most significant 8 bits to 0 + unsafe { + balance.as_limbs_mut()[3] &= 0x00FFFFFFFFFFFFFF; + } + let account = Account { balance, nonce: nonce.into(), bytecode_hash }; + (address, account) + }).collect(); + let state_updates: Vec> = state_updates.into_iter().map(|update| { + let update = update.into_iter().take(1).map(|(mut address, mut update)| { + // set the largest byte to 0 + >::as_mut(&mut address)[31] = 0; + // set the most significant 8 bits to 0 + let account = if let Some(mut balance) = update { + unsafe { + balance.as_limbs_mut()[3] &= 0x00FFFFFFFFFFFFFF; + } + Some(Account { balance, ..Default::default() }) + } else { None }; + (address, account) + }).collect::>(); + update + }).collect(); + + + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + let mut hashed_account_cursor = provider.tx_ref().cursor_write::().unwrap(); + + // Insert init state into database + for (hashed_address, account) in init_state.clone().into_iter() { + hashed_account_cursor.upsert(reverse_bits(hashed_address), account).unwrap(); + } + + // Compute initial root and updates + let (_, mut trie_nodes) = StateRoot::from_tx(provider.tx_ref()) + .root_with_updates() + .unwrap(); + + let mut state = init_state; + for state_update in state_updates { + // Insert state updates into database + let mut hashed_state = HashedPostState::default(); + for (hashed_address, account) in state_update.into_iter().take(4) { + if let Some(account) = account { + hashed_account_cursor.upsert(reverse_bits(hashed_address), account).unwrap(); + hashed_state.accounts.insert(reverse_bits(hashed_address), Some(account)); + state.insert(hashed_address, account); + } else { + hashed_state.accounts.insert(reverse_bits(hashed_address), None); + state.remove(&hashed_address); + } + } + + // Compute root with in-memory trie nodes overlay + let (state_root, trie_updates) = StateRoot::from_tx(provider.tx_ref()) + .with_prefix_sets(hashed_state.construct_prefix_sets().freeze()) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( + DatabaseTrieCursorFactory::new(provider.tx_ref()), &trie_nodes.clone().into_sorted()) + ) + .root_with_updates() + .unwrap(); + + trie_nodes.extend(trie_updates); + + // Verify the result + let expected_root = state_root_zktrie( + state.iter().map(|(key, account)| (*key, (*account, std::iter::empty()))) + ); + assert_eq!(expected_root.0, state_root.0); + + } + } + + + } + + fn reverse_bits(b256: B256) -> B256 { + let mut b256 = b256.0; + for byte in b256.iter_mut() { + *byte = byte.reverse_bits(); + } + B256::from(b256) + } + + fn state_root_zktrie(accounts: I) -> B256 + where + I: IntoIterator, + S: IntoIterator, + { + let mut trie = zktrie(); + const COMPRESSION_FLAG: u32 = 8; + for (address, (account, storage)) in accounts.into_iter() { + let mut key = address.0; + key.reverse(); + let key = AsHash::from_bytes(&key).unwrap(); + let mut account_bytes = Vec::with_capacity(5); + + account_bytes.push(U256::from_limbs([account.nonce, 0, 0, 0]).to_be_bytes()); + account_bytes.push(account.balance.to_be_bytes()); + account_bytes.push([0u8; 32]); + account_bytes.push(account.bytecode_hash.unwrap_or(KECCAK_EMPTY).0); + account_bytes.push([0u8; 32]); + + // for bytes in account_bytes.iter() { + // println!("{:?}", bytes); + // } + + trie.try_update(&key, COMPRESSION_FLAG, account_bytes).unwrap(); + } + trie.prepare_root().unwrap(); + let mut root = trie.root().to_bytes(); + root.reverse(); + B256::from_slice(&root) + // 00 + } + + #[test] + fn test_basic_state_root_with_updates_succeeds() { + let address_1 = Address::with_last_byte(0); + let address_2 = Address::with_last_byte(3); + let address_3 = Address::with_last_byte(7); + let account_1 = Account { balance: Uint::from(1), ..Default::default() }; + let account_2 = Account { balance: Uint::from(2), ..Default::default() }; + let account_3 = Account { balance: Uint::from(3), ..Default::default() }; + + let factory = create_test_provider_factory(); + let tx = factory.provider_rw().unwrap(); + + insert_account(tx.tx_ref(), address_1, account_1, &Default::default()); + insert_account(tx.tx_ref(), address_2, account_2, &Default::default()); + insert_account(tx.tx_ref(), address_3, account_3, &Default::default()); + + tx.commit().unwrap(); + + let tx = factory.provider_rw().unwrap(); + let (root, updates) = StateRoot::from_tx(tx.tx_ref()).root_with_updates().unwrap(); + } + + fn test_state_root_with_state(state: State) { + let factory = create_test_provider_factory(); + let tx = factory.provider_rw().unwrap(); + + for (address, (account, storage)) in &state { + insert_account(tx.tx_ref(), *address, *account, storage) + } + tx.commit().unwrap(); + let expected = state_root(state); + + let tx = factory.provider_rw().unwrap(); + let got = StateRoot::from_tx(tx.tx_ref()).root().unwrap(); + assert_eq!(expected, got); + } + + fn insert_account( + tx: &impl DbTxMut, + address: Address, + account: Account, + storage: &BTreeMap, + ) { + let hashed_address = keccak256(address); + tx.put::(hashed_address, account).unwrap(); + insert_storage(tx, hashed_address, storage); + } + + fn insert_storage(tx: &impl DbTxMut, hashed_address: B256, storage: &BTreeMap) { + for (k, v) in storage { + tx.put::( + hashed_address, + StorageEntry { key: keccak256(k), value: *v }, + ) + .unwrap(); + } + } + + fn poseidon_hash_scheme(a: &[u8; 32], b: &[u8; 32], domain: &[u8; 32]) -> Option<[u8; 32]> { + let a = Fr::from_repr_vartime(*a)?; + let b = Fr::from_repr_vartime(*b)?; + let domain = Fr::from_repr_vartime(*domain)?; + Some(hash_with_domain(&[a, b], domain).to_repr()) + } + + fn zktrie() -> zktrie_rust::raw::ZkTrieImpl, SimpleDb, 248> { + zktrie::init_hash_scheme_simple(poseidon_hash_scheme); + zktrie_rust::raw::ZkTrieImpl::, SimpleDb, 248>::new_zktrie_impl( + SimpleDb::new(), + ) + .unwrap() + } +} diff --git a/crates/scroll/state-commitment/src/value.rs b/crates/scroll/state-commitment/src/value.rs new file mode 100644 index 000000000000..c24ded698757 --- /dev/null +++ b/crates/scroll/state-commitment/src/value.rs @@ -0,0 +1,57 @@ +use alloy_primitives::{B256, U256}; +use scroll_primitives::{ + poseidon::{ + field_element_from_be_bytes, hash_with_domain, split_and_hash_be_bytes, FieldElementBytes, + Fr, PrimeField, DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT, + }, + ScrollTrieAccount, +}; + +/// An implementation of a value hasher that uses Poseidon. +/// +/// This hash provides hashing of a [`ScrollTrieAccount`] and a storage entry ([`U256`]). +#[derive(Debug)] +pub struct PosiedonValueHasher; + +impl PosiedonValueHasher { + /// The number of field elements in the account hashing. + const ACCOUNT_HASHING_FIELD_ELEMENTS: u64 = 5; + + /// The domain for hashing the account. + const ACCOUNT_HASHING_DOMAIN: Fr = Fr::from_raw([ + Self::ACCOUNT_HASHING_FIELD_ELEMENTS * DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT, + 0, + 0, + 0, + ]); + + /// Hashes the account using Poseidon hash function. + pub(crate) fn hash_account(account: ScrollTrieAccount) -> B256 { + // combine nonce and code size and parse into field element + let nonce_code_size_bytes = field_element_from_be_bytes( + // TODO(frisitano): Replace with native handling of bytes instead of using U256. + U256::from_limbs([account.nonce, account.code_size, 0, 0]).to_be_bytes(), + ); + + // parse remaining field elements + let balance = field_element_from_be_bytes(account.balance.to_be_bytes()); + let keccak_code_hash = split_and_hash_be_bytes(account.code_hash); + let storage_root = field_element_from_be_bytes(account.storage_root.0); + let poseidon_code_hash = field_element_from_be_bytes(account.poseidon_code_hash.0); + + // hash field elements + let digest_1 = + hash_with_domain(&[nonce_code_size_bytes, balance], Self::ACCOUNT_HASHING_DOMAIN); + let digest_2 = + hash_with_domain(&[storage_root, keccak_code_hash], Self::ACCOUNT_HASHING_DOMAIN); + let digest = hash_with_domain(&[digest_1, digest_2], Self::ACCOUNT_HASHING_DOMAIN); + let digest = hash_with_domain(&[digest, poseidon_code_hash], Self::ACCOUNT_HASHING_DOMAIN); + + digest.to_repr().into() + } + + /// Hashes the storage entry using Poseidon hash function. + pub(crate) fn hash_storage(entry: U256) -> B256 { + split_and_hash_be_bytes::(entry.to_be_bytes()).to_repr().into() + } +} diff --git a/crates/scroll/trie/Cargo.toml b/crates/scroll/trie/Cargo.toml index 0c6a592f87b5..cd1debd3d96b 100644 --- a/crates/scroll/trie/Cargo.toml +++ b/crates/scroll/trie/Cargo.toml @@ -11,12 +11,14 @@ exclude.workspace = true [dependencies] alloy-trie = { workspace = true, features = ["serde"] } alloy-primitives.workspace = true +scroll-primitives.workspace = true tracing = { version = "0.1", default-features = false } -poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", branch = "master" } +reth-trie.workspace = true [dev-dependencies] tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] } hex-literal = "0.4" +proptest-arbitrary-interop.workspace = true [lints] workspace = true diff --git a/crates/scroll/trie/src/branch.rs b/crates/scroll/trie/src/branch.rs index f60ab8624f32..e6b92dba7fd3 100644 --- a/crates/scroll/trie/src/branch.rs +++ b/crates/scroll/trie/src/branch.rs @@ -1,18 +1,26 @@ -use alloy_primitives::{hex, keccak256, B256}; +use super::{ + BRANCH_NODE_LBRB_DOMAIN, BRANCH_NODE_LBRT_DOMAIN, BRANCH_NODE_LTRB_DOMAIN, + BRANCH_NODE_LTRT_DOMAIN, +}; +use alloy_primitives::{hex, B256}; use alloy_trie::TrieMask; use core::{fmt, ops::Range, slice::Iter}; +use scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; -// #[allow(unused_imports)] -// use alloc::vec::Vec; +#[allow(unused_imports)] +use alloc::vec::Vec; /// The range of valid child indexes. pub(crate) const CHILD_INDEX_RANGE: Range = 0..2; +/// A trie mask to extract the two child indexes from a branch node. +pub(crate) const CHILD_INDEX_MASK: TrieMask = TrieMask::new(0b11); + /// A reference to [BranchNode] and its state mask. /// NOTE: The stack may contain more items that specified in the state mask. #[derive(Clone)] pub(crate) struct BranchNodeRef<'a> { - /// Reference to the collection of RLP encoded nodes. + /// Reference to the collection of hash nodes. /// NOTE: The referenced stack might have more items than the number of children /// for this node. We should only ever access items starting from /// [BranchNodeRef::first_child_index]. @@ -47,7 +55,10 @@ impl<'a> BranchNodeRef<'a> { /// Means that the node is in inconsistent state. #[inline] pub(crate) fn first_child_index(&self) -> usize { - self.stack.len().checked_sub(self.state_mask.count_ones() as usize).unwrap() + self.stack + .len() + .checked_sub((self.state_mask & CHILD_INDEX_MASK).count_ones() as usize) + .unwrap() } #[inline] @@ -67,17 +78,31 @@ impl<'a> BranchNodeRef<'a> { pub(crate) fn hash(&self) -> B256 { let mut children_iter = self.children(); - let mut bytes: [u8; 64] = [0u8; 64]; - if let Some((_, Some(child))) = children_iter.next() { - bytes[..32].copy_from_slice(child.as_slice()); - } + let left_child = children_iter + .next() + .map(|(_, c)| *c.unwrap_or_default()) + .expect("branch node has two children"); + let left_child = + Fr::from_repr_vartime(left_child.0).expect("left child is a valid field element"); + let right_child = children_iter + .next() + .map(|(_, c)| *c.unwrap_or_default()) + .expect("branch node has two children"); + let right_child = + Fr::from_repr_vartime(right_child.0).expect("right child is a valid field element"); + + hash_with_domain(&[left_child, right_child], self.hashing_domain()).to_repr().into() + } - if let Some((_, Some(child))) = children_iter.next() { - bytes[32..].copy_from_slice(child.as_slice()); + fn hashing_domain(&self) -> Fr { + match *self.state_mask { + 0b1011 => Fr::from(BRANCH_NODE_LBRT_DOMAIN), + 0b1111 => Fr::from(BRANCH_NODE_LTRT_DOMAIN), + 0b0111 => Fr::from(BRANCH_NODE_LTRB_DOMAIN), + 0b0011 => Fr::from(BRANCH_NODE_LBRB_DOMAIN), + _ => unreachable!("invalid branch node state mask"), } - - keccak256(bytes.as_slice()) } } diff --git a/crates/scroll/trie/src/hash_builder.rs b/crates/scroll/trie/src/hash_builder.rs index 963bcadd4652..e79435e1c9f6 100644 --- a/crates/scroll/trie/src/hash_builder.rs +++ b/crates/scroll/trie/src/hash_builder.rs @@ -1,5 +1,9 @@ -use crate::{branch::BranchNodeRef, key::UnpackBits, sub_tree::SubTreeRef}; -use alloy_primitives::{keccak256, map::HashMap, B256}; +use crate::{ + branch::{BranchNodeRef, CHILD_INDEX_MASK}, + leaf::HashLeaf, + sub_tree::SubTreeRef, +}; +use alloy_primitives::{map::HashMap, B256}; use alloy_trie::{ hash_builder::{HashBuilderValue, HashBuilderValueRef}, nodes::LeafNodeRef, @@ -16,7 +20,8 @@ pub struct HashBuilder { pub value: HashBuilderValue, pub stack: Vec, - pub groups: Vec, + // TODO(frisitano): Introduce terminator / leaf masks + pub state_masks: Vec, pub tree_masks: Vec, pub hash_masks: Vec, @@ -78,7 +83,6 @@ impl HashBuilder { /// Adds a new leaf element and its value to the trie hash builder. pub fn add_leaf(&mut self, key: Nibbles, value: &[u8]) { - let key = key.unpack_bits(); assert!(key > self.key, "add_leaf key {:?} self.key {:?}", key, self.key); if !self.key.is_empty() { self.update(&key); @@ -88,7 +92,6 @@ impl HashBuilder { /// Adds a new branch element and its hash to the trie hash builder. pub fn add_branch(&mut self, key: Nibbles, value: B256, stored_in_database: bool) { - let key = key.unpack_bits(); assert!( key > self.key || (self.key.is_empty() && key.is_empty()), "add_branch key {:?} self.key {:?}", @@ -161,8 +164,8 @@ impl HashBuilder { loop { let _span = tracing::trace_span!(target: "trie::hash_builder", "loop", i, ?current, build_extensions).entered(); - let preceding_exists = !self.groups.is_empty(); - let preceding_len = self.groups.len().saturating_sub(1); + let preceding_exists = !self.state_masks.is_empty(); + let preceding_len = self.state_masks.len().saturating_sub(1); let common_prefix_len = succeeding.common_prefix_length(current.as_slice()); let len = cmp::max(preceding_len, common_prefix_len); @@ -179,16 +182,16 @@ impl HashBuilder { // Adjust the state masks for branch calculation let extra_digit = current[len]; - if self.groups.len() <= len { + if self.state_masks.len() <= len { let new_len = len + 1; - trace!(target: "trie::hash_builder", new_len, old_len = self.groups.len(), "scaling state masks to fit"); - self.groups.resize(new_len, TrieMask::default()); + trace!(target: "trie::hash_builder", new_len, old_len = self.state_masks.len(), "scaling state masks to fit"); + self.state_masks.resize(new_len, TrieMask::default()); } - self.groups[len] |= TrieMask::from_nibble(extra_digit); + self.state_masks[len] |= TrieMask::from_nibble(extra_digit); trace!( target: "trie::hash_builder", ?extra_digit, - groups = ?self.groups, + groups = ?self.state_masks, ); // Adjust the tree masks for exporting to the DB @@ -210,10 +213,12 @@ impl HashBuilder { if !build_extensions { match self.value.as_ref() { HashBuilderValueRef::Bytes(leaf_value) => { - let leaf_node = LeafNodeRef::new(&short_node_key, leaf_value); - // TODO: replace with appropriate account hashing - let leaf_hash = keccak256(leaf_value); - println!("leaf hash: {:?}", leaf_hash); + // TODO(frisitano): Replace with terminator masks + // Set the terminator mask for the leaf node + self.state_masks[len] |= TrieMask::new(0b100 << extra_digit); + let leaf_node = LeafNodeRef::new(¤t, leaf_value); + let leaf_hash = leaf_node.hash_leaf(); + println!("leaf hash: {:?}", leaf_hash.0); trace!( target: "trie::hash_builder", ?leaf_node, @@ -265,12 +270,11 @@ impl HashBuilder { if !succeeding.is_empty() || preceding_exists { // Pushes the corresponding branch node to the stack let children = self.push_branch_node(¤t, len); - println!("children: {:?}", children); // Need to store the branch node in an efficient format outside of the hash builder self.store_branch_node(¤t, len, children); } - self.groups.resize(len, TrieMask::default()); + self.state_masks.resize(len, TrieMask::default()); self.resize_masks(len); if preceding_len == 0 { @@ -281,9 +285,9 @@ impl HashBuilder { current.truncate(preceding_len); trace!(target: "trie::hash_builder", ?current, "truncated nibbles to {} bytes", preceding_len); - trace!(target: "trie::hash_builder", groups = ?self.groups, "popping empty state masks"); - while self.groups.last() == Some(&TrieMask::default()) { - self.groups.pop(); + trace!(target: "trie::hash_builder", groups = ?self.state_masks, "popping empty state masks"); + while self.state_masks.last() == Some(&TrieMask::default()) { + self.state_masks.pop(); } build_extensions = true; @@ -299,7 +303,7 @@ impl HashBuilder { /// Returns the hashes of the children of the branch node, only if `updated_branch_nodes` is /// enabled. fn push_branch_node(&mut self, _current: &Nibbles, len: usize) -> Vec { - let state_mask = self.groups[len]; + let state_mask = self.state_masks[len]; let hash_mask = self.hash_masks[len]; let branch_node = BranchNodeRef::new(&self.stack, state_mask); // Avoid calculating this value if it's not needed. @@ -310,13 +314,12 @@ impl HashBuilder { }; let branch_hash = branch_node.hash(); - println!("branch hash: {:?}", branch_hash); // TODO: enable proof retention // self.retain_proof_from_stack(¤t.slice(..len)); // Clears the stack from the branch node elements - let first_child_idx = self.stack.len() - state_mask.count_ones() as usize; + let first_child_idx = branch_node.first_child_index(); trace!( target: "trie::hash_builder", new_len = first_child_idx, @@ -325,10 +328,9 @@ impl HashBuilder { ); self.stack.resize_with(first_child_idx, Default::default); - // trace!(target: "trie::hash_builder", ?rlp, "pushing branch node with {state_mask:?} mask - // from stack"); + trace!(target: "trie::hash_builder", ?branch_hash, "pushing branch node with {state_mask:?} mask + from stack"); - // TODO compute branch node hash self.stack.push(branch_hash); children } @@ -354,18 +356,19 @@ impl HashBuilder { if self.updated_branch_nodes.is_some() { let common_prefix = current.slice(..len); let node = BranchNodeCompact::new( - self.groups[len], + self.state_masks[len] & CHILD_INDEX_MASK, self.tree_masks[len], self.hash_masks[len], children, (len == 0).then(|| self.current_root()), ); - trace!(target: "trie::hash_builder", ?node, "intermediate node"); + trace!(target: "trie::hash_builder", ?node, "storing updated intermediate node"); self.updated_branch_nodes.as_mut().unwrap().insert(common_prefix, node); } } } + // TODO(frisitano): Enable proof retention // fn retain_proof_from_stack(&mut self, prefix: &Nibbles) { // if let Some(proof_retainer) = self.proof_retainer.as_mut() { // proof_retainer.retain( @@ -400,16 +403,62 @@ impl HashBuilder { } } +// TODO(frisitano): Introduce generic for the HashBuilder. +impl From for HashBuilder { + fn from(hash_builder: reth_trie::HashBuilder) -> Self { + HashBuilder { + key: hash_builder.key, + value: hash_builder.value, + stack: hash_builder + .stack + .into_iter() + .map(|x| x.as_slice().try_into().expect("RlpNode contains 32 byte hashes")) + .collect(), + state_masks: hash_builder.groups, + tree_masks: hash_builder.tree_masks, + hash_masks: hash_builder.hash_masks, + stored_in_database: hash_builder.stored_in_database, + updated_branch_nodes: hash_builder.updated_branch_nodes, + proof_retainer: hash_builder.proof_retainer, + } + } +} + +impl From for reth_trie::HashBuilder { + fn from(value: HashBuilder) -> Self { + reth_trie::HashBuilder { + key: value.key, + value: value.value, + stack: value + .stack + .into_iter() + .map(|x| { + reth_trie::RlpNode::from_raw(&x.0).expect("32 byte hash can be cast to RlpNode") + }) + .collect(), + groups: value.state_masks, + tree_masks: value.tree_masks, + hash_masks: value.hash_masks, + stored_in_database: value.stored_in_database, + updated_branch_nodes: value.updated_branch_nodes, + proof_retainer: value.proof_retainer, + rlp_buf: Default::default(), + } + } +} + #[cfg(test)] mod test { use super::*; use alloc::collections::BTreeMap; use hex_literal::hex; + use reth_trie::key::BitsCompatibility; + use scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; #[test] fn test_convert_to_bit_representation() { - let nibbles = Nibbles::from_nibbles_unchecked(hex!("01020304")).unpack_bits(); - let expected = hex!("00000001000001000000010100010000"); + let nibbles = Nibbles::unpack_and_truncate_bits(vec![7, 8]); + let expected = [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0]; assert_eq!(nibbles.as_slice(), expected); } @@ -418,85 +467,110 @@ mod test { // 64 byte nibble let hex = hex!("0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f01020304"); assert_eq!(hex.len(), 64); - let nibbles = Nibbles::from_nibbles_unchecked(hex).unpack_bits(); + let nibbles = Nibbles::unpack_and_truncate_bits(hex); assert_eq!(nibbles.len(), 248); } #[test] fn test_basic_trie() { // Test a basic trie consisting of three key value pairs: - // - (0xF, 15) - // - (0x0, 0) - // - (0x1, 1) + // (0, 0, 0, 0, ... , 0) + // (0, 0, 0, 1, ... , 0) + // (0, 0, 1, 0, ... , 0) + // (1, 1, 1, 0, ... , 0) + // (1, 1, 1, 1, ... , 0) // The branch associated with key 0xF will be collapsed into a single leaf. - let mut hb = HashBuilder::default().with_updates(true); - let data = BTreeMap::from([ - // binary key: (0,0,0,0) - (hex!("00").to_vec(), Vec::from([0u8])), - // binary key: (0,0,0,1) - (hex!("01").to_vec(), Vec::from([1u8])), - // binary key: (1,1,1,1) - (hex!("0F").to_vec(), Vec::from([15u8])), + let leaf_1_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, ]); - data.iter().for_each(|(key, val)| { - let nibbles = Nibbles::from_nibbles_unchecked(key); - hb.add_leaf(nibbles, val.as_ref()); - }); - let root = hb.root(); - - const EMPTY_NODE: [u8; 32] = [0u8; 32]; - let expected = { - let leaf_0 = keccak256(data.get(hex!("00").as_slice()).unwrap()); - let leaf_1 = keccak256(data.get(hex!("01").as_slice()).unwrap()); - let leaf_f = keccak256(data.get(hex!("0F").as_slice()).unwrap()); - let node_000 = keccak256([leaf_0.as_slice(), leaf_1.as_slice()].concat()); - let node_00 = keccak256([node_000.as_slice(), &EMPTY_NODE].concat()); - let node_0 = keccak256([node_00.as_slice(), &EMPTY_NODE].concat()); - keccak256([node_0.as_slice(), leaf_f.as_slice()].concat()) - }; - - assert_eq!(expected, root); - } + let leaf_2_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_3_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_4_key = Nibbles::from_nibbles_unchecked([ + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_5_key = Nibbles::from_nibbles_unchecked([ + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_keys = [ + leaf_1_key.clone(), + leaf_2_key.clone(), + leaf_3_key.clone(), + leaf_4_key.clone(), + leaf_5_key.clone(), + ]; + + let leaf_values = leaf_keys + .into_iter() + .enumerate() + .map(|(i, key)| { + let mut leaf_value = [0u8; 32]; + leaf_value[0] = i as u8 + 1; + (key, leaf_value) + }) + .collect::>(); + + let leaf_hashes: BTreeMap<_, _> = leaf_values + .iter() + .map(|(key, value)| { + let key_fr = Fr::from_repr_vartime(key.encode_leaf_key()) + .expect("key is valid field element"); + let value = Fr::from_repr_vartime(*value).expect("value is a valid field element"); + let hash = hash_with_domain(&[key_fr, value], crate::LEAF_NODE_DOMAIN.into()); + (key.clone(), hash) + }) + .collect(); - #[test] - fn test_generates_branch_node() { let mut hb = HashBuilder::default().with_updates(true); - let data = BTreeMap::from([ - // binary key: (0,0,0,0) - (hex!("00").to_vec(), Vec::from([0u8])), - // binary key: (0,0,0,1) - (hex!("01").to_vec(), Vec::from([1u8])), - // binary key: (0,0,1,0) - (hex!("02").to_vec(), Vec::from([2u8])), - // binary key: (1,1,1,1) - (hex!("0F").to_vec(), Vec::from([15u8])), - ]); - data.iter().for_each(|(key, val)| { - let nibbles = Nibbles::from_nibbles_unchecked(key); - hb.add_leaf(nibbles, val.as_ref()); + + leaf_values.iter().for_each(|(key, val)| { + hb.add_leaf(key.clone(), val); }); + let root = hb.root(); - const EMPTY_NODE: [u8; 32] = [0u8; 32]; - let expected = { - let leaf_0 = keccak256(data.get(hex!("00").as_slice()).unwrap()); - let leaf_1 = keccak256(data.get(hex!("01").as_slice()).unwrap()); - let leaf_2 = keccak256(data.get(hex!("02").as_slice()).unwrap()); - let leaf_f = keccak256(data.get(hex!("0F").as_slice()).unwrap()); - let node_000 = keccak256([leaf_0.as_slice(), leaf_1.as_slice()].concat()); - let node_00 = keccak256([node_000.as_slice(), leaf_2.as_slice()].concat()); - let node_0 = keccak256([node_00.as_slice(), &EMPTY_NODE].concat()); - keccak256([node_0.as_slice(), leaf_f.as_slice()].concat()) + // node_000 -> hash(leaf_1, leaf_2) LTRT + // node_00 -> hash(node_000, leaf_3) LBRT + // node_0 -> hash(node_00, EMPTY) LBRT + // node_111 -> hash(leaf_4, leaf_5) LTRT + // node_11 -> hash(EMPTY, node_111) LTRB + // node_1 -> hash(EMPTY, node_11) LTRB + // root -> hash(node_0, node_1) LBRB + + let expected: B256 = { + let node_000 = hash_with_domain( + &[*leaf_hashes.get(&leaf_1_key).unwrap(), *leaf_hashes.get(&leaf_2_key).unwrap()], + crate::BRANCH_NODE_LTRT_DOMAIN.into(), + ); + let node_00 = hash_with_domain( + &[node_000, *leaf_hashes.get(&leaf_3_key).unwrap()], + crate::BRANCH_NODE_LBRT_DOMAIN.into(), + ); + let node_0 = + hash_with_domain(&[node_00, Fr::zero()], crate::BRANCH_NODE_LBRT_DOMAIN.into()); + let node_111 = hash_with_domain( + &[*leaf_hashes.get(&leaf_4_key).unwrap(), *leaf_hashes.get(&leaf_5_key).unwrap()], + crate::BRANCH_NODE_LTRT_DOMAIN.into(), + ); + let node_11 = + hash_with_domain(&[Fr::zero(), node_111], crate::BRANCH_NODE_LTRB_DOMAIN.into()); + let node_1 = + hash_with_domain(&[Fr::zero(), node_11], crate::BRANCH_NODE_LTRB_DOMAIN.into()); + + hash_with_domain(&[node_0, node_1], crate::BRANCH_NODE_LBRB_DOMAIN.into()) + .to_repr() + .into() }; - assert_eq!(root, expected); - - let (_, updates) = hb.split(); - for (key, update) in updates { - // TODO add additional assertions - println!("key: {:?}", key); - println!("update: {:?}", update); - } + assert_eq!(expected, root); } } diff --git a/crates/scroll/trie/src/key.rs b/crates/scroll/trie/src/key.rs deleted file mode 100644 index 11a51c6c8ad5..000000000000 --- a/crates/scroll/trie/src/key.rs +++ /dev/null @@ -1,32 +0,0 @@ -use alloy_trie::Nibbles; - -/// The maximum number of bits a key can contain. -const MAX_BITS: usize = 248; - -// The maximum number of nibbles a key can contain. -const MAX_NIBBLES: usize = MAX_BITS / 4; - -pub(crate) trait UnpackBits { - /// This takes the `Nibbles` representation and converts it to a bit representation in which - /// there is a byte for each bit in the nibble. - /// - /// We truncate the Nibbles such that we only have 248 bits. - fn unpack_bits(&self) -> Self; - - // TODO: introduce unpack_bits_truncated method -} - -impl UnpackBits for Nibbles { - fn unpack_bits(&self) -> Self { - let capacity = core::cmp::min(self.len() * 4, MAX_BITS); - let mut bits = Vec::with_capacity(capacity); - - for byte in self.as_slice().iter().take(MAX_NIBBLES) { - for i in (0..4).rev() { - let bit = (byte >> i) & 1; - bits.push(bit); - } - } - Nibbles::from_vec_unchecked(bits) - } -} diff --git a/crates/scroll/trie/src/leaf.rs b/crates/scroll/trie/src/leaf.rs new file mode 100644 index 000000000000..ce1692c95333 --- /dev/null +++ b/crates/scroll/trie/src/leaf.rs @@ -0,0 +1,23 @@ +use super::LEAF_NODE_DOMAIN; +use alloy_primitives::B256; +use reth_trie::{key::BitsCompatibility, LeafNodeRef}; +use scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; + +/// A trait used to hash the leaf node. +pub(crate) trait HashLeaf { + /// Hash the leaf node. + fn hash_leaf(&self) -> B256; +} + +impl HashLeaf for LeafNodeRef<'_> { + fn hash_leaf(&self) -> B256 { + let leaf_key = Fr::from_repr_vartime(self.key.encode_leaf_key()) + .expect("leaf key is a valid field element"); + let leaf_value = Fr::from_repr_vartime( + <[u8; 32]>::try_from(self.value).expect("leaf value is 32 bytes"), + ) + .expect("leaf value is a valid field element"); + println!("leaf value: {:?}", leaf_value.to_repr()); + hash_with_domain(&[leaf_key, leaf_value], Fr::from(LEAF_NODE_DOMAIN)).to_repr().into() + } +} diff --git a/crates/scroll/trie/src/lib.rs b/crates/scroll/trie/src/lib.rs index 90baac349199..71cb6fb2b6db 100644 --- a/crates/scroll/trie/src/lib.rs +++ b/crates/scroll/trie/src/lib.rs @@ -7,5 +7,20 @@ extern crate alloc; mod branch; mod hash_builder; pub use hash_builder::HashBuilder; -mod key; +mod leaf; mod sub_tree; + +/// The hashing domain for leaf nodes. +pub const LEAF_NODE_DOMAIN: u64 = 4; + +/// The hashing domain for a branch node with two terminal children. +pub const BRANCH_NODE_LTRT_DOMAIN: u64 = 6; + +/// The hashing domain for a branch node with a left terminal child and a right branch child. +pub const BRANCH_NODE_LTRB_DOMAIN: u64 = 7; + +/// The hashing domain for a branch node with a left branch child and a right terminal child. +pub const BRANCH_NODE_LBRT_DOMAIN: u64 = 8; + +/// The hashing domain for a branch node with two branch children. +pub const BRANCH_NODE_LBRB_DOMAIN: u64 = 9; diff --git a/crates/scroll/trie/src/sub_tree.rs b/crates/scroll/trie/src/sub_tree.rs index 38b5646db2d7..7eb722021a85 100644 --- a/crates/scroll/trie/src/sub_tree.rs +++ b/crates/scroll/trie/src/sub_tree.rs @@ -1,8 +1,11 @@ -use alloy_primitives::{hex, keccak256, B256}; +use super::{BRANCH_NODE_LBRT_DOMAIN, BRANCH_NODE_LTRB_DOMAIN}; +use alloy_primitives::{hex, B256}; use alloy_trie::Nibbles; use core::fmt; +use scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; -/// Reference to a subtree containing a single child. +/// [SubTreeRef] is a structure that allows for calculation of the root of a sparse binary Merkle +/// tree consisting of a single leaf node. pub(crate) struct SubTreeRef<'a> { /// The key to the child node. pub key: &'a Nibbles, @@ -18,17 +21,16 @@ impl<'a> SubTreeRef<'a> { } pub(crate) fn root(&self) -> B256 { - let mut tree_root = *self.child; - for &bit in self.key.as_slice().iter().rev() { - let mut bytes = [0u8; 64]; - if bit == 0 { - bytes[..32].copy_from_slice(tree_root.as_slice()); + let mut tree_root = + Fr::from_repr_vartime(self.child.0).expect("child is a valid field element"); + for bit in self.key.as_slice().iter().rev() { + tree_root = if *bit == 0 { + hash_with_domain(&[tree_root, Fr::zero()], Fr::from(BRANCH_NODE_LBRT_DOMAIN)) } else { - bytes[32..].copy_from_slice(tree_root.as_slice()); - } - tree_root = keccak256(&bytes); + hash_with_domain(&[Fr::zero(), tree_root], Fr::from(BRANCH_NODE_LTRB_DOMAIN)) + }; } - tree_root + tree_root.to_repr().into() } } diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index d0f0fa092a77..800b7406f791 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -33,6 +33,9 @@ rayon.workspace = true derive_more.workspace = true auto_impl.workspace = true itertools.workspace = true +smallvec = { version = "1.0", default-features = false, features = [ + "const_new", +] } # `metrics` feature reth-metrics = { workspace = true, optional = true } diff --git a/crates/trie/trie/src/key.rs b/crates/trie/trie/src/key.rs new file mode 100644 index 000000000000..334764aeaade --- /dev/null +++ b/crates/trie/trie/src/key.rs @@ -0,0 +1,92 @@ +use smallvec::SmallVec; + +use crate::Nibbles; + +/// The maximum number of bits a key can contain. +const MAX_BITS: usize = 248; + +/// The maximum number of bytes a key can contain. +const MAX_BYTES: usize = 31; + +// TODO(frisitano): Refactor this into a trait that is more generic and can be used by any +// implementation that requires converting between nibbles and bits. Better yet we should use a +// trait that allows for defining the key type via a GAT as opposed to using Nibbles. + +/// A trait for converting a `Nibbles` representation to a bit representation. +pub trait BitsCompatibility: Sized { + /// Unpacks the bits from the provided bytes such that there is a byte for each bit in the + /// input. The representation is big-endian with respect to the input. + /// + /// We truncate the Nibbles such that we only have [`MAX_BITS`] (248) bits. + fn unpack_and_truncate_bits>(data: T) -> Self; + + /// Pack the bits into a byte representation. + fn pack_bits(&self) -> SmallVec<[u8; 32]>; + + /// Encodes a leaf key represented as [`Nibbles`] into it's canonical little-endian + /// representation. + fn encode_leaf_key(&self) -> [u8; 32]; + + /// Increment the key to the next key. + fn increment_bit(&self) -> Option; +} + +impl BitsCompatibility for Nibbles { + fn unpack_and_truncate_bits>(data: T) -> Self { + let data = data.as_ref(); + let unpacked_len = core::cmp::min(data.len() * 8, MAX_BITS); + let mut bits = Vec::with_capacity(unpacked_len); + + for byte in data.iter().take(MAX_BYTES) { + for i in (0..8).rev() { + bits.push(byte >> i & 1); + } + } + + Nibbles::from_vec_unchecked(bits) + } + + fn pack_bits(&self) -> SmallVec<[u8; 32]> { + println!("bits {:?}", self); + let mut result = SmallVec::with_capacity((self.len() + 7) / 8); + + for bits in self.as_slice().chunks(8) { + let mut byte = 0; + for (bit_index, bit) in bits.iter().enumerate() { + byte |= *bit << (7 - bit_index); + } + result.push(byte); + } + + println!("result: {:?}", result); + + result + } + + fn encode_leaf_key(&self) -> [u8; 32] { + // This is strange we are now representing the leaf key using big endian?? + let mut result = [0u8; 32]; + for (byte_index, bytes) in self.as_slice().chunks(8).enumerate() { + for (bit_index, byte) in bytes.iter().enumerate() { + result[byte_index] |= byte << bit_index; + } + } + + result + } + + fn increment_bit(&self) -> Option { + let mut incremented = self.clone(); + + for nibble in incremented.as_mut_vec_unchecked().iter_mut().rev() { + if *nibble < 1 { + *nibble += 1; + return Some(incremented); + } else { + *nibble = 0; + } + } + + None + } +} diff --git a/crates/trie/trie/src/lib.rs b/crates/trie/trie/src/lib.rs index bb568ae8b8cf..0676865ad6c7 100644 --- a/crates/trie/trie/src/lib.rs +++ b/crates/trie/trie/src/lib.rs @@ -13,6 +13,9 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +/// A module for working with trie keys. +pub mod key; + /// The implementation of a container for storing intermediate changes to a trie. /// The container indicates when the trie has been modified. pub mod prefix_set; diff --git a/crates/trie/trie/src/node_iter.rs b/crates/trie/trie/src/node_iter.rs index feebe36e16e9..e3563a33a3e3 100644 --- a/crates/trie/trie/src/node_iter.rs +++ b/crates/trie/trie/src/node_iter.rs @@ -1,4 +1,7 @@ -use crate::{hashed_cursor::HashedCursor, trie_cursor::TrieCursor, walker::TrieWalker, Nibbles}; +use crate::{ + hashed_cursor::HashedCursor, key::BitsCompatibility, trie_cursor::TrieCursor, + walker::TrieWalker, Nibbles, +}; use alloy_primitives::B256; use reth_storage_errors::db::DatabaseError; @@ -106,7 +109,10 @@ where if let Some((hashed_key, value)) = self.current_hashed_entry.take() { // If the walker's key is less than the unpacked hashed key, // reset the checked status and continue - if self.walker.key().map_or(false, |key| key < &Nibbles::unpack(hashed_key)) { + if self.walker.key().map_or(false, |key| { + // TODO(frisitano): replace this with key abstraction. + key < &Nibbles::unpack_and_truncate_bits(hashed_key) + }) { self.current_walker_key_checked = false; continue } diff --git a/crates/trie/trie/src/proof.rs b/crates/trie/trie/src/proof.rs index e99d686aca7f..1f5385cb6281 100644 --- a/crates/trie/trie/src/proof.rs +++ b/crates/trie/trie/src/proof.rs @@ -1,5 +1,6 @@ use crate::{ hashed_cursor::{HashedCursorFactory, HashedStorageCursor}, + key::BitsCompatibility, node_iter::{TrieElement, TrieNodeIter}, prefix_set::{PrefixSetMut, TriePrefixSetsMut}, trie_cursor::TrieCursorFactory, @@ -96,11 +97,13 @@ where // Create the walker. let mut prefix_set = self.prefix_sets.account_prefix_set.clone(); - prefix_set.extend_keys(targets.keys().map(Nibbles::unpack)); + // TODO(frisitano): replace this with key abstraction. + prefix_set.extend_keys(targets.keys().map(|x| Nibbles::unpack_and_truncate_bits(x))); let walker = TrieWalker::new(trie_cursor, prefix_set.freeze()); // Create a hash builder to rebuild the root node since it is not available in the database. - let retainer = targets.keys().map(Nibbles::unpack).collect(); + // TODO(frisitano): replace this with key abstraction. + let retainer = targets.keys().map(|x| Nibbles::unpack_and_truncate_bits(x)).collect(); let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer); let mut storages = HashMap::default(); @@ -131,7 +134,11 @@ where let account = TrieAccount::from((account, storage_multiproof.root)); account.encode(&mut account_rlp as &mut dyn BufMut); - hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); + hash_builder.add_leaf( + // TODO(frisitano): replace this with key abstraction. + Nibbles::unpack_and_truncate_bits(hashed_address), + &account_rlp, + ); storages.insert(hashed_address, storage_multiproof); } } @@ -224,7 +231,11 @@ where return Ok(StorageMultiProof::empty()) } - let target_nibbles = targets.into_iter().map(Nibbles::unpack).collect::>(); + let target_nibbles = targets + .into_iter() + // TODO(frisitano): replace this with key abstraction. + .map(|x| Nibbles::unpack_and_truncate_bits(x)) + .collect::>(); self.prefix_set.extend_keys(target_nibbles.clone()); let trie_cursor = self.trie_cursor_factory.storage_trie_cursor(self.hashed_address)?; @@ -240,7 +251,8 @@ where } TrieElement::Leaf(hashed_slot, value) => { hash_builder.add_leaf( - Nibbles::unpack(hashed_slot), + // TODO(frisitano): replace this with key abstraction. + Nibbles::unpack_and_truncate_bits(hashed_slot), alloy_rlp::encode_fixed_size(&value).as_ref(), ); } diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index 2af48dfff798..0f737c760653 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -1,4 +1,5 @@ use crate::{ + key::BitsCompatibility, prefix_set::{PrefixSetMut, TriePrefixSetsMut}, Nibbles, }; @@ -113,7 +114,9 @@ impl HashedPostState { let mut account_prefix_set = PrefixSetMut::with_capacity(self.accounts.len()); let mut destroyed_accounts = HashSet::default(); for (hashed_address, account) in &self.accounts { - account_prefix_set.insert(Nibbles::unpack(hashed_address)); + account_prefix_set + // TODO(frisitano): replace this with key abstraction. + .insert(Nibbles::unpack_and_truncate_bits(hashed_address)); if account.is_none() { destroyed_accounts.insert(*hashed_address); @@ -123,7 +126,8 @@ impl HashedPostState { // Populate storage prefix sets. let mut storage_prefix_sets = HashMap::with_capacity(self.storages.len()); for (hashed_address, hashed_storage) in &self.storages { - account_prefix_set.insert(Nibbles::unpack(hashed_address)); + // TODO(frisitano): replace this with key abstraction. + account_prefix_set.insert(Nibbles::unpack_and_truncate_bits(hashed_address)); storage_prefix_sets.insert(*hashed_address, hashed_storage.construct_prefix_set()); } @@ -236,7 +240,8 @@ impl HashedStorage { } else { let mut prefix_set = PrefixSetMut::with_capacity(self.storage.len()); for hashed_slot in self.storage.keys() { - prefix_set.insert(Nibbles::unpack(hashed_slot)); + // TODO(frisitano): replace this with key abstraction. + prefix_set.insert(Nibbles::unpack_and_truncate_bits(hashed_slot)); } prefix_set } diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index b8aa133d6fa3..4ac4c5e2229f 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -1,5 +1,6 @@ use crate::{ hashed_cursor::{HashedCursorFactory, HashedStorageCursor}, + key::BitsCompatibility, node_iter::{TrieElement, TrieNodeIter}, prefix_set::{PrefixSet, TriePrefixSets}, progress::{IntermediateStateRootState, StateRootProgress}, @@ -228,7 +229,11 @@ where account_rlp.clear(); let account = TrieAccount::from((account, storage_root)); account.encode(&mut account_rlp as &mut dyn BufMut); - hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); + hash_builder.add_leaf( + // TODO(frisitano): replace this with key abstraction. + Nibbles::unpack_and_truncate_bits(hashed_address), + &account_rlp, + ); // Decide if we need to return intermediate progress. let total_updates_len = updated_storage_nodes + @@ -424,7 +429,8 @@ where TrieElement::Leaf(hashed_slot, value) => { tracker.inc_leaf(); hash_builder.add_leaf( - Nibbles::unpack(hashed_slot), + // TODO(frisitano): replace this with key abstraction. + Nibbles::unpack_and_truncate_bits(hashed_slot), alloy_rlp::encode_fixed_size(&value).as_ref(), ); } diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 6d1bcab63d8f..dadc251aed62 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -6,10 +6,12 @@ use std::collections::{HashMap, HashSet}; #[derive(PartialEq, Eq, Clone, Default, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TrieUpdates { + /// Collection of updated account trie nodes. #[cfg_attr(feature = "serde", serde(with = "serde_nibbles_map"))] - pub(crate) account_nodes: HashMap, + pub account_nodes: HashMap, + /// Collection of removed account trie nodes. #[cfg_attr(feature = "serde", serde(with = "serde_nibbles_set"))] - pub(crate) removed_nodes: HashSet, + pub removed_nodes: HashSet, pub(crate) storage_tries: HashMap, } @@ -226,6 +228,7 @@ impl StorageTrieUpdates { /// This also sorts the set before serializing. #[cfg(feature = "serde")] mod serde_nibbles_set { + use crate::key::BitsCompatibility; use std::collections::HashSet; use reth_trie_common::Nibbles; @@ -235,8 +238,10 @@ mod serde_nibbles_set { where S: Serializer, { - let mut storage_nodes = - map.iter().map(|elem| alloy_primitives::hex::encode(elem.pack())).collect::>(); + let mut storage_nodes = map + .iter() + .map(|elem| alloy_primitives::hex::encode(elem.pack_bits())) + .collect::>(); storage_nodes.sort_unstable(); storage_nodes.serialize(serializer) } @@ -248,7 +253,8 @@ mod serde_nibbles_set { Vec::::deserialize(deserializer)? .into_iter() .map(|node| { - Ok(Nibbles::unpack( + // TODO(frisitano): replace this with key abstraction. + Ok(Nibbles::unpack_and_truncate_bits( alloy_primitives::hex::decode(node) .map_err(|err| D::Error::custom(err.to_string()))?, )) @@ -273,6 +279,8 @@ mod serde_nibbles_map { Deserialize, Deserializer, Serialize, Serializer, }; + use crate::key::BitsCompatibility; + pub(super) fn serialize( map: &HashMap, serializer: S, @@ -286,7 +294,8 @@ mod serde_nibbles_map { storage_nodes.sort_unstable_by_key(|node| node.0); for (k, v) in storage_nodes { // pack, then hex encode the Nibbles - let packed = alloy_primitives::hex::encode(k.pack()); + // TODO(frisitano): replace this with key abstraction. + let packed = alloy_primitives::hex::encode(k.pack_bits()); map_serializer.serialize_entry(&packed, &v)?; } map_serializer.end() @@ -321,7 +330,8 @@ mod serde_nibbles_map { let decoded_key = hex::decode(&key).map_err(|err| Error::custom(err.to_string()))?; - let nibbles = Nibbles::unpack(&decoded_key); + // TODO(frisitano): replace this with key abstraction. + let nibbles = Nibbles::unpack_and_truncate_bits(&decoded_key); result.insert(nibbles, value); } diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index e75a96d0f1f4..2f2426b96207 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -1,4 +1,5 @@ use crate::{ + key::BitsCompatibility, prefix_set::PrefixSet, trie_cursor::{CursorSubNode, TrieCursor}, BranchNodeCompact, Nibbles, @@ -83,9 +84,10 @@ impl TrieWalker { self.key() .and_then(|key| { if self.can_skip_current_node { - key.increment().map(|inc| inc.pack()) + // TODO(frisitano): replace this with key abstraction. + key.increment_bit().map(|inc| inc.pack_bits()) } else { - Some(key.pack()) + Some(key.pack_bits()) } }) .map(|mut key| { diff --git a/crates/trie/trie/src/witness.rs b/crates/trie/trie/src/witness.rs index 971f10cfbae1..47593671759e 100644 --- a/crates/trie/trie/src/witness.rs +++ b/crates/trie/trie/src/witness.rs @@ -1,5 +1,6 @@ use crate::{ hashed_cursor::{HashedCursor, HashedCursorFactory}, + key::BitsCompatibility, prefix_set::TriePrefixSetsMut, proof::{Proof, StorageProof}, trie_cursor::TrieCursorFactory, @@ -152,7 +153,8 @@ where Self::next_root_from_proofs(storage_trie_nodes, |key: Nibbles| { // Right pad the target with 0s. - let mut padded_key = key.pack(); + // TODO(frisitano): remove this with key abstraction + let mut padded_key = key.pack_bits(); padded_key.resize(32, 0); let target_key = B256::from_slice(&padded_key); let storage_prefix_set = self @@ -179,7 +181,8 @@ where Self::next_root_from_proofs(account_trie_nodes, |key: Nibbles| { // Right pad the target with 0s. - let mut padded_key = key.pack(); + //TODO(frisitano): remove this with key abstraction + let mut padded_key = key.pack_bits(); padded_key.resize(32, 0); let targets = HashMap::from_iter([(B256::from_slice(&padded_key), HashSet::default())]); let proof = From 52426dbce689ea4595881023834c32166a17959f Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 20 Nov 2024 12:57:08 +0800 Subject: [PATCH 05/38] refactor: introduce StateCommimentProvider --- Cargo.lock | 1 + .../commands/debug_cmd/in_memory_merkle.rs | 8 +- bin/reth/src/commands/debug_cmd/merkle.rs | 11 +- crates/exex/exex/src/backfill/test_utils.rs | 6 +- crates/stages/stages/src/stages/execution.rs | 15 +- .../provider/src/providers/consistent.rs | 10 +- .../provider/src/providers/database/mod.rs | 4 +- .../src/providers/database/provider.rs | 42 +++--- .../src/providers/state/historical.rs | 133 ++++++++---------- .../provider/src/providers/state/latest.rs | 55 ++++---- crates/storage/storage-api/Cargo.toml | 1 + crates/storage/storage-api/src/state.rs | 14 +- 12 files changed, 142 insertions(+), 158 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 973ad4718720..f1fb2f89eeaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9183,6 +9183,7 @@ dependencies = [ "reth-stages-types", "reth-storage-errors", "reth-trie", + "reth-trie-db", ] [[package]] diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index ef186ca050e1..d5bb8a87b22e 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -22,9 +22,9 @@ use reth_network_api::NetworkInfo; use reth_node_api::{NodeTypesWithDB, NodeTypesWithEngine}; use reth_node_ethereum::EthExecutorProvider; use reth_provider::{ - writer::UnifiedStorageWriter, AccountExtReader, AsLatestStateProviderRef, ChainSpecProvider, - HashingWriter, HeaderProvider, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, - StateWriter, StorageReader, + writer::UnifiedStorageWriter, AccountExtReader, ChainSpecProvider, HashingWriter, + HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderFactory, + StageCheckpointReader, StateWriter, StorageReader, }; use reth_revm::database::StateProviderDatabase; use reth_stages::StageId; @@ -133,7 +133,7 @@ impl> Command { ) .await?; - let db = StateProviderDatabase::new(provider.latest()); + let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); let executor = EthExecutorProvider::ethereum(provider_factory.chain_spec()).executor(db); diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 4128b982ca24..9c77c70abc74 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -20,9 +20,9 @@ use reth_network_p2p::full_block::FullBlockClient; use reth_node_api::{NodeTypesWithDB, NodeTypesWithEngine}; use reth_node_ethereum::EthExecutorProvider; use reth_provider::{ - writer::UnifiedStorageWriter, AsLatestStateProviderRef, BlockNumReader, BlockWriter, - ChainSpecProvider, DatabaseProviderFactory, HeaderProvider, OriginalValuesKnown, ProviderError, - ProviderFactory, StateWriter, + writer::UnifiedStorageWriter, BlockNumReader, BlockWriter, ChainSpecProvider, + DatabaseProviderFactory, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, + ProviderError, ProviderFactory, StateWriter, }; use reth_revm::database::StateProviderDatabase; use reth_stages::{ @@ -152,8 +152,9 @@ impl> Command { provider_rw.insert_block(sealed_block.clone())?; td += sealed_block.difficulty; - let mut executor = - executor_provider.batch_executor(StateProviderDatabase::new(provider_rw.latest())); + let mut executor = executor_provider.batch_executor(StateProviderDatabase::new( + LatestStateProviderRef::new(&provider_rw), + )); executor.execute_and_verify_one((&sealed_block.clone().unseal(), td).into())?; let execution_outcome = executor.finalize(); diff --git a/crates/exex/exex/src/backfill/test_utils.rs b/crates/exex/exex/src/backfill/test_utils.rs index 222f9dc1d0d2..80af408c5c8f 100644 --- a/crates/exex/exex/src/backfill/test_utils.rs +++ b/crates/exex/exex/src/backfill/test_utils.rs @@ -13,7 +13,7 @@ use reth_primitives::{ Block, BlockBody, BlockWithSenders, Receipt, SealedBlockWithSenders, Transaction, }; use reth_provider::{ - providers::ProviderNodeTypes, AsLatestStateProviderRef, BlockWriter as _, ExecutionOutcome, + providers::ProviderNodeTypes, BlockWriter as _, ExecutionOutcome, LatestStateProviderRef, ProviderFactory, }; use reth_revm::database::StateProviderDatabase; @@ -63,7 +63,7 @@ where // Execute the block to produce a block execution output let mut block_execution_output = EthExecutorProvider::ethereum(chain_spec) - .executor(StateProviderDatabase::new(provider.latest())) + .executor(StateProviderDatabase::new(LatestStateProviderRef::new(&provider))) .execute(BlockExecutionInput { block, total_difficulty: U256::ZERO })?; block_execution_output.state.reverts.sort(); @@ -189,7 +189,7 @@ where let provider = provider_factory.provider()?; let executor = EthExecutorProvider::ethereum(chain_spec) - .batch_executor(StateProviderDatabase::new(provider.latest())); + .batch_executor(StateProviderDatabase::new(LatestStateProviderRef::new(&provider))); let mut execution_outcome = executor.execute_and_verify_batch(vec![ (&block1, U256::ZERO).into(), diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 7c6d41bd536f..c8bcc8ae389a 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -16,9 +16,9 @@ use reth_primitives_traits::{format_gas_throughput, NodePrimitives}; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, writer::UnifiedStorageWriter, - AsLatestStateProviderRef, BlockHashReader, BlockReader, DBProvider, HeaderProvider, - OriginalValuesKnown, ProviderError, StateChangeWriter, StateWriter, StaticFileProviderFactory, - StatsReader, TransactionVariant, + BlockHashReader, BlockReader, DBProvider, HeaderProvider, LatestStateProviderRef, + OriginalValuesKnown, ProviderError, StateChangeWriter, StateCommitmentProvider, StateWriter, + StaticFileProviderFactory, StatsReader, TransactionVariant, }; use reth_prune_types::PruneModes; use reth_revm::database::StateProviderDatabase; @@ -46,9 +46,8 @@ use tracing::*; /// - [`tables::BlockBodyIndices`] to get tx number /// - [`tables::Transactions`] to execute /// -/// For state access [`reth_provider::LatestStateProviderRef`] provides us latest state and history -/// state For latest most recent state [`reth_provider::LatestStateProviderRef`] would need (Used -/// for execution Stage): +/// For state access [`LatestStateProviderRef`] provides us latest state and history state +/// For latest most recent state [`LatestStateProviderRef`] would need (Used for execution Stage): /// - [`tables::PlainAccountState`] /// - [`tables::Bytecodes`] /// - [`tables::PlainStorageState`] @@ -182,7 +181,7 @@ where + StatsReader + StateChangeWriter + BlockHashReader - + AsLatestStateProviderRef, + + StateCommitmentProvider, for<'a> UnifiedStorageWriter<'a, Provider, StaticFileProviderRWRefMut<'a, Provider::Primitives>>: StateWriter, { @@ -227,7 +226,7 @@ where None }; - let db = StateProviderDatabase(provider.latest()); + let db = StateProviderDatabase(LatestStateProviderRef::new(provider)); let mut executor = self.executor_provider.batch_executor(db); executor.set_tip(max_block); executor.set_prune_modes(prune_modes); diff --git a/crates/storage/provider/src/providers/consistent.rs b/crates/storage/provider/src/providers/consistent.rs index a5980bcd7d48..7702504ced4a 100644 --- a/crates/storage/provider/src/providers/consistent.rs +++ b/crates/storage/provider/src/providers/consistent.rs @@ -1,10 +1,10 @@ use super::{DatabaseProviderRO, ProviderFactory, ProviderNodeTypes}; use crate::{ - providers::StaticFileProvider, AccountReader, AsLatestStateProviderRef, BlockHashReader, - BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, BlockSource, ChainSpecProvider, - ChangeSetReader, EvmEnvProvider, HeaderProvider, ProviderError, PruneCheckpointReader, - ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateReader, - StaticFileProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider, + providers::StaticFileProvider, AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, + BlockReader, BlockReaderIdExt, BlockSource, ChainSpecProvider, ChangeSetReader, EvmEnvProvider, + HeaderProvider, ProviderError, PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, + StageCheckpointReader, StateReader, StaticFileProviderFactory, TransactionVariant, + TransactionsProvider, WithdrawalsProvider, }; use alloy_consensus::Header; use alloy_eips::{ diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 14fc6f765f63..94c83bbb4422 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -160,9 +160,7 @@ impl ProviderFactory { #[track_caller] pub fn latest(&self) -> ProviderResult { trace!(target: "providers::db", "Returning latest state provider"); - Ok(Box::new(LatestStateProvider::<_, N::StateCommitment>::new( - self.database_provider_ro()?, - ))) + Ok(Box::new(LatestStateProvider::new(self.database_provider_ro()?))) } /// Storage provider for state at that given block diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 94b57c2113e4..ab5ed783e898 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -6,15 +6,15 @@ use crate::{ AccountExtReader, BlockSource, ChangeSetReader, ReceiptProvider, StageCheckpointWriter, }, writer::UnifiedStorageWriter, - AccountReader, AsLatestStateProviderRef, BlockExecutionWriter, BlockHashReader, BlockNumReader, - BlockReader, BlockWriter, BundleStateInit, ChainStateBlockReader, ChainStateBlockWriter, - DBProvider, EvmEnvProvider, HashingWriter, HeaderProvider, HeaderSyncGap, - HeaderSyncGapProvider, HistoricalStateProvider, HistoricalStateProviderRef, HistoryWriter, - LatestStateProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, - PruneCheckpointReader, PruneCheckpointWriter, RevertsInit, StageCheckpointReader, - StateChangeWriter, StateProviderBox, StateReader, StateWriter, StaticFileProviderFactory, - StatsReader, StorageReader, StorageTrieWriter, TransactionVariant, TransactionsProvider, - TransactionsProviderExt, TrieWriter, WithdrawalsProvider, + AccountReader, BlockExecutionWriter, BlockHashReader, BlockNumReader, BlockReader, BlockWriter, + BundleStateInit, ChainStateBlockReader, ChainStateBlockWriter, DBProvider, EvmEnvProvider, + HashingWriter, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HistoricalStateProvider, + HistoricalStateProviderRef, HistoryWriter, LatestStateProvider, LatestStateProviderRef, + OriginalValuesKnown, ProviderError, PruneCheckpointReader, PruneCheckpointWriter, RevertsInit, + StageCheckpointReader, StateChangeWriter, StateCommitmentProvider, StateProviderBox, + StateReader, StateWriter, StaticFileProviderFactory, StatsReader, StorageReader, + StorageTrieWriter, TransactionVariant, TransactionsProvider, TransactionsProviderExt, + TrieWriter, WithdrawalsProvider, }; use alloy_consensus::Header; use alloy_eips::{ @@ -149,6 +149,12 @@ impl DatabaseProvider { } impl DatabaseProvider { + /// State provider for latest state + pub fn latest<'a>(&'a self) -> Box { + trace!(target: "providers::db", "Returning latest state provider"); + Box::new(LatestStateProviderRef::new(self)) + } + /// Storage provider for state at that given block hash pub fn history_by_block_hash<'a>( &'a self, @@ -159,7 +165,7 @@ impl DatabaseProvider { if block_number == self.best_block_number().unwrap_or_default() && block_number == self.last_block_number().unwrap_or_default() { - return Ok(Box::new(LatestStateProviderRef::<'_, _, N::StateCommitment>::new(self))) + return Ok(Box::new(LatestStateProviderRef::new(self))) } // +1 as the changeset that we want is the one that was applied after this block. @@ -170,8 +176,7 @@ impl DatabaseProvider { let storage_history_prune_checkpoint = self.get_prune_checkpoint(PruneSegment::StorageHistory)?; - let mut state_provider = - HistoricalStateProviderRef::<'_, _, N::StateCommitment>::new(self, block_number); + let mut state_provider = HistoricalStateProviderRef::new(self, block_number); // If we pruned account or storage history, we can't return state on every historical block. // Instead, we should cap it at the latest prune checkpoint for corresponding prune segment. @@ -239,7 +244,7 @@ impl TryIntoHistoricalStateProvider for Databa if block_number == self.best_block_number().unwrap_or_default() && block_number == self.last_block_number().unwrap_or_default() { - return Ok(Box::new(LatestStateProvider::<_, N::StateCommitment>::new(self))) + return Ok(Box::new(LatestStateProvider::new(self))) } // +1 as the changeset that we want is the one that was applied after this block. @@ -250,8 +255,7 @@ impl TryIntoHistoricalStateProvider for Databa let storage_history_prune_checkpoint = self.get_prune_checkpoint(PruneSegment::StorageHistory)?; - let mut state_provider = - HistoricalStateProvider::<_, N::StateCommitment>::new(self, block_number); + let mut state_provider = HistoricalStateProvider::new(self, block_number); // If we pruned account or storage history, we can't return state on every historical block. // Instead, we should cap it at the latest prune checkpoint for corresponding prune segment. @@ -274,12 +278,8 @@ impl TryIntoHistoricalStateProvider for Databa } } -impl AsLatestStateProviderRef for DatabaseProvider { - /// State provider for latest state - fn latest<'a>(&'a self) -> Box { - trace!(target: "providers::db", "Returning latest state provider"); - Box::new(LatestStateProviderRef::<'_, _, N::StateCommitment>::new(self)) - } +impl StateCommitmentProvider for DatabaseProvider { + type StateCommitment = N::StateCommitment; } impl + 'static> diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 2cc912af9791..a70fe9ac203b 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -15,7 +15,9 @@ use reth_db_api::{ transaction::DbTx, }; use reth_primitives::{Account, Bytecode}; -use reth_storage_api::{BlockNumReader, DBProvider, StateProofProvider, StorageRootProvider}; +use reth_storage_api::{ + BlockNumReader, DBProvider, StateCommitmentProvider, StateProofProvider, StorageRootProvider, +}; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ proof::{Proof, StorageProof}, @@ -27,7 +29,7 @@ use reth_trie_db::{ DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot, DatabaseTrieWitness, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::fmt::Debug; /// State provider for a given block number which takes a tx reference. /// @@ -41,15 +43,13 @@ use std::{fmt::Debug, marker::PhantomData}; /// - [`tables::AccountChangeSets`] /// - [`tables::StorageChangeSets`] #[derive(Debug)] -pub struct HistoricalStateProviderRef<'b, Provider, SC> { +pub struct HistoricalStateProviderRef<'b, Provider> { /// Database provider provider: &'b Provider, /// Block number is main index for the history state of accounts and storages. block_number: BlockNumber, /// Lowest blocks at which different parts of the state are available. lowest_available_blocks: LowestAvailableBlocks, - /// Marker to associate the `StateCommitment` type with this provider. - _marker: PhantomData, } #[derive(Debug, Eq, PartialEq)] @@ -60,15 +60,12 @@ pub enum HistoryInfo { MaybeInPlainState, } -impl<'b, Provider: DBProvider + BlockNumReader, SC> HistoricalStateProviderRef<'b, Provider, SC> { +impl<'b, Provider: DBProvider + BlockNumReader + StateCommitmentProvider> + HistoricalStateProviderRef<'b, Provider> +{ /// Create new `StateProvider` for historical block number pub fn new(provider: &'b Provider, block_number: BlockNumber) -> Self { - Self { - provider, - block_number, - lowest_available_blocks: Default::default(), - _marker: PhantomData, - } + Self { provider, block_number, lowest_available_blocks: Default::default() } } /// Create new `StateProvider` for historical block number and lowest block numbers at which @@ -78,7 +75,7 @@ impl<'b, Provider: DBProvider + BlockNumReader, SC> HistoricalStateProviderRef<' block_number: BlockNumber, lowest_available_blocks: LowestAvailableBlocks, ) -> Self { - Self { provider, block_number, lowest_available_blocks, _marker: PhantomData } + Self { provider, block_number, lowest_available_blocks } } /// Lookup an account in the `AccountsHistory` table @@ -240,14 +237,14 @@ impl<'b, Provider: DBProvider + BlockNumReader, SC> HistoricalStateProviderRef<' } } -impl HistoricalStateProviderRef<'_, Provider, SC> { +impl HistoricalStateProviderRef<'_, Provider> { fn tx(&self) -> &Provider::Tx { self.provider.tx_ref() } } -impl AccountReader - for HistoricalStateProviderRef<'_, Provider, SC> +impl AccountReader + for HistoricalStateProviderRef<'_, Provider> { /// Get basic account information. fn basic_account(&self, address: Address) -> ProviderResult> { @@ -270,8 +267,8 @@ impl AccountReader } } -impl BlockHashReader - for HistoricalStateProviderRef<'_, Provider, SC> +impl BlockHashReader + for HistoricalStateProviderRef<'_, Provider> { /// Get block hash by number. fn block_hash(&self, number: u64) -> ProviderResult> { @@ -287,8 +284,8 @@ impl B } } -impl StateRootProvider - for HistoricalStateProviderRef<'_, Provider, SC> +impl StateRootProvider + for HistoricalStateProviderRef<'_, Provider> { fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult { let mut revert_state = self.revert_state()?; @@ -323,8 +320,8 @@ impl StateRootProvider } } -impl StorageRootProvider - for HistoricalStateProviderRef<'_, Provider, SC> +impl StorageRootProvider + for HistoricalStateProviderRef<'_, Provider> { fn storage_root( &self, @@ -350,8 +347,8 @@ impl StorageRootProvider } } -impl StateProofProvider - for HistoricalStateProviderRef<'_, Provider, SC> +impl StateProofProvider + for HistoricalStateProviderRef<'_, Provider> { /// Get account and storage proofs. fn proof( @@ -384,8 +381,8 @@ impl StateProofProvider } } -impl StateProvider - for HistoricalStateProviderRef<'_, Provider, SC> +impl + StateProvider for HistoricalStateProviderRef<'_, Provider> { /// Get storage. fn storage( @@ -426,26 +423,21 @@ impl S /// State provider for a given block number. /// For more detailed description, see [`HistoricalStateProviderRef`]. #[derive(Debug)] -pub struct HistoricalStateProvider { +pub struct HistoricalStateProvider { /// Database provider. provider: Provider, /// State at the block number is the main indexer of the state. block_number: BlockNumber, /// Lowest blocks at which different parts of the state are available. lowest_available_blocks: LowestAvailableBlocks, - /// Marker to associate the `StateCommitment` type with this provider. - _marker: PhantomData, } -impl HistoricalStateProvider { +impl + HistoricalStateProvider +{ /// Create new `StateProvider` for historical block number pub fn new(provider: Provider, block_number: BlockNumber) -> Self { - Self { - provider, - block_number, - lowest_available_blocks: Default::default(), - _marker: PhantomData, - } + Self { provider, block_number, lowest_available_blocks: Default::default() } } /// Set the lowest block number at which the account history is available. @@ -468,7 +460,7 @@ impl HistoricalStateProvider HistoricalStateProviderRef<'_, Provider, SC> { + const fn as_ref(&self) -> HistoricalStateProviderRef<'_, Provider> { HistoricalStateProviderRef::new_with_lowest_available_blocks( &self.provider, self.block_number, @@ -478,7 +470,7 @@ impl HistoricalStateProvider where [Provider: DBProvider + BlockNumReader + BlockHashReader, SC: Send + Sync]); +delegate_provider_impls!(HistoricalStateProvider where [Provider: DBProvider + BlockNumReader + BlockHashReader + StateCommitmentProvider]); /// Lowest blocks at which different parts of the state are available. /// They may be [Some] if pruning is enabled. @@ -522,12 +514,11 @@ mod tests { transaction::{DbTx, DbTxMut}, }; use reth_primitives::{Account, StorageEntry}; - use reth_storage_api::{BlockHashReader, BlockNumReader, DBProvider, DatabaseProviderFactory}; + use reth_storage_api::{ + BlockHashReader, BlockNumReader, DBProvider, DatabaseProviderFactory, + StateCommitmentProvider, + }; use reth_storage_errors::provider::ProviderError; - use reth_trie_db::MerklePatriciaTrie; - - type TestHistoricalStateProviderRef<'a, TX> = - HistoricalStateProviderRef<'a, TX, MerklePatriciaTrie>; const ADDRESS: Address = address!("0000000000000000000000000000000000000001"); const HIGHER_ADDRESS: Address = address!("0000000000000000000000000000000000000005"); @@ -536,10 +527,9 @@ mod tests { const fn assert_state_provider() {} #[allow(dead_code)] const fn assert_historical_state_provider< - T: DBProvider + BlockNumReader + BlockHashReader, - SC: Send + Sync, + T: DBProvider + BlockNumReader + BlockHashReader + StateCommitmentProvider, >() { - assert_state_provider::>(); + assert_state_provider::>(); } #[test] @@ -608,46 +598,43 @@ mod tests { let db = factory.provider().unwrap(); // run - assert_eq!(TestHistoricalStateProviderRef::new(&db, 1).basic_account(ADDRESS), Ok(None)); + assert_eq!(HistoricalStateProviderRef::new(&db, 1).basic_account(ADDRESS), Ok(None)); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 2).basic_account(ADDRESS), + HistoricalStateProviderRef::new(&db, 2).basic_account(ADDRESS), Ok(Some(acc_at3)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 3).basic_account(ADDRESS), + HistoricalStateProviderRef::new(&db, 3).basic_account(ADDRESS), Ok(Some(acc_at3)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 4).basic_account(ADDRESS), + HistoricalStateProviderRef::new(&db, 4).basic_account(ADDRESS), Ok(Some(acc_at7)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 7).basic_account(ADDRESS), + HistoricalStateProviderRef::new(&db, 7).basic_account(ADDRESS), Ok(Some(acc_at7)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 9).basic_account(ADDRESS), + HistoricalStateProviderRef::new(&db, 9).basic_account(ADDRESS), Ok(Some(acc_at10)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 10).basic_account(ADDRESS), + HistoricalStateProviderRef::new(&db, 10).basic_account(ADDRESS), Ok(Some(acc_at10)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 11).basic_account(ADDRESS), + HistoricalStateProviderRef::new(&db, 11).basic_account(ADDRESS), Ok(Some(acc_at15)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 16).basic_account(ADDRESS), + HistoricalStateProviderRef::new(&db, 16).basic_account(ADDRESS), Ok(Some(acc_plain)) ); + assert_eq!(HistoricalStateProviderRef::new(&db, 1).basic_account(HIGHER_ADDRESS), Ok(None)); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 1).basic_account(HIGHER_ADDRESS), - Ok(None) - ); - assert_eq!( - TestHistoricalStateProviderRef::new(&db, 1000).basic_account(HIGHER_ADDRESS), + HistoricalStateProviderRef::new(&db, 1000).basic_account(HIGHER_ADDRESS), Ok(Some(higher_acc_plain)) ); } @@ -705,41 +692,41 @@ mod tests { let db = factory.provider().unwrap(); // run - assert_eq!(TestHistoricalStateProviderRef::new(&db, 0).storage(ADDRESS, STORAGE), Ok(None)); + assert_eq!(HistoricalStateProviderRef::new(&db, 0).storage(ADDRESS, STORAGE), Ok(None)); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 3).storage(ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 3).storage(ADDRESS, STORAGE), Ok(Some(U256::ZERO)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 4).storage(ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 4).storage(ADDRESS, STORAGE), Ok(Some(entry_at7.value)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 7).storage(ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 7).storage(ADDRESS, STORAGE), Ok(Some(entry_at7.value)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 9).storage(ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 9).storage(ADDRESS, STORAGE), Ok(Some(entry_at10.value)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 10).storage(ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 10).storage(ADDRESS, STORAGE), Ok(Some(entry_at10.value)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 11).storage(ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 11).storage(ADDRESS, STORAGE), Ok(Some(entry_at15.value)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 16).storage(ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 16).storage(ADDRESS, STORAGE), Ok(Some(entry_plain.value)) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 1).storage(HIGHER_ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 1).storage(HIGHER_ADDRESS, STORAGE), Ok(None) ); assert_eq!( - TestHistoricalStateProviderRef::new(&db, 1000).storage(HIGHER_ADDRESS, STORAGE), + HistoricalStateProviderRef::new(&db, 1000).storage(HIGHER_ADDRESS, STORAGE), Ok(Some(higher_entry_plain.value)) ); } @@ -751,7 +738,7 @@ mod tests { // provider block_number < lowest available block number, // i.e. state at provider block is pruned - let provider = TestHistoricalStateProviderRef::new_with_lowest_available_blocks( + let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( &db, 2, LowestAvailableBlocks { @@ -770,7 +757,7 @@ mod tests { // provider block_number == lowest available block number, // i.e. state at provider block is available - let provider = TestHistoricalStateProviderRef::new_with_lowest_available_blocks( + let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( &db, 2, LowestAvailableBlocks { @@ -786,7 +773,7 @@ mod tests { // provider block_number == lowest available block number, // i.e. state at provider block is available - let provider = TestHistoricalStateProviderRef::new_with_lowest_available_blocks( + let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( &db, 2, LowestAvailableBlocks { diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index bec5aa5e3ac6..ab70d759b992 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -1,5 +1,3 @@ -use std::marker::PhantomData; - use crate::{ providers::state::macros::delegate_provider_impls, AccountReader, BlockHashReader, StateProvider, StateRootProvider, @@ -11,7 +9,9 @@ use alloy_primitives::{ use reth_db::tables; use reth_db_api::{cursor::DbDupCursorRO, transaction::DbTx}; use reth_primitives::{Account, Bytecode}; -use reth_storage_api::{DBProvider, StateProofProvider, StorageRootProvider}; +use reth_storage_api::{ + DBProvider, StateCommitmentProvider, StateProofProvider, StorageRootProvider, +}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{ proof::{Proof, StorageProof}, @@ -26,15 +26,14 @@ use reth_trie_db::{ /// State provider over latest state that takes tx reference. /// -/// Wraps a [`DBProvider`] to get access to database and [`reth_trie_db::StateCommitment`] -/// (`PhantomData`) to get access to state commitment operations. +/// Wraps a [`DBProvider`] to get access to database. #[derive(Debug)] -pub struct LatestStateProviderRef<'b, Provider, SC>(&'b Provider, PhantomData); +pub struct LatestStateProviderRef<'b, Provider>(&'b Provider); -impl<'b, Provider: DBProvider, SC> LatestStateProviderRef<'b, Provider, SC> { +impl<'b, Provider: DBProvider> LatestStateProviderRef<'b, Provider> { /// Create new state provider pub const fn new(provider: &'b Provider) -> Self { - Self(provider, PhantomData) + Self(provider) } fn tx(&self) -> &Provider::Tx { @@ -42,18 +41,14 @@ impl<'b, Provider: DBProvider, SC> LatestStateProviderRef<'b, Provider, SC> { } } -impl AccountReader - for LatestStateProviderRef<'_, Provider, SC> -{ +impl AccountReader for LatestStateProviderRef<'_, Provider> { /// Get basic account information. fn basic_account(&self, address: Address) -> ProviderResult> { self.tx().get::(address).map_err(Into::into) } } -impl BlockHashReader - for LatestStateProviderRef<'_, Provider, SC> -{ +impl BlockHashReader for LatestStateProviderRef<'_, Provider> { /// Get block hash by number. fn block_hash(&self, number: u64) -> ProviderResult> { self.0.block_hash(number) @@ -68,8 +63,8 @@ impl BlockHashReader } } -impl StateRootProvider - for LatestStateProviderRef<'_, Provider, SC> +impl StateRootProvider + for LatestStateProviderRef<'_, Provider> { fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult { StateRoot::overlay_root(self.tx(), hashed_state) @@ -98,8 +93,8 @@ impl StateRootProvider } } -impl StorageRootProvider - for LatestStateProviderRef<'_, Provider, SC> +impl StorageRootProvider + for LatestStateProviderRef<'_, Provider> { fn storage_root( &self, @@ -121,8 +116,8 @@ impl StorageRootProvider } } -impl StateProofProvider - for LatestStateProviderRef<'_, Provider, SC> +impl StateProofProvider + for LatestStateProviderRef<'_, Provider> { fn proof( &self, @@ -151,8 +146,8 @@ impl StateProofProvider } } -impl StateProvider - for LatestStateProviderRef<'_, Provider, SC> +impl StateProvider + for LatestStateProviderRef<'_, Provider> { /// Get storage. fn storage( @@ -177,23 +172,23 @@ impl StateProvider /// State provider for the latest state. #[derive(Debug)] -pub struct LatestStateProvider(Provider, PhantomData); +pub struct LatestStateProvider(Provider); -impl LatestStateProvider { +impl LatestStateProvider { /// Create new state provider pub const fn new(db: Provider) -> Self { - Self(db, PhantomData) + Self(db) } /// Returns a new provider that takes the `TX` as reference #[inline(always)] - const fn as_ref(&self) -> LatestStateProviderRef<'_, Provider, SC> { + const fn as_ref(&self) -> LatestStateProviderRef<'_, Provider> { LatestStateProviderRef::new(&self.0) } } // Delegates all provider impls to [LatestStateProviderRef] -delegate_provider_impls!(LatestStateProvider where [Provider: DBProvider + BlockHashReader, SC: Send + Sync]); +delegate_provider_impls!(LatestStateProvider where [Provider: DBProvider + BlockHashReader + StateCommitmentProvider]); #[cfg(test)] mod tests { @@ -201,7 +196,9 @@ mod tests { const fn assert_state_provider() {} #[allow(dead_code)] - const fn assert_latest_state_provider() { - assert_state_provider::>(); + const fn assert_latest_state_provider< + T: DBProvider + BlockHashReader + StateCommitmentProvider, + >() { + assert_state_provider::>(); } } diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index 2b13f6332f87..88e35c18f8c6 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -22,6 +22,7 @@ reth-prune-types.workspace = true reth-stages-types.workspace = true reth-storage-errors.workspace = true reth-trie.workspace = true +reth-trie-db.workspace = true reth-db.workspace = true # ethereum diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 6c9e82cfba3b..49db24a9bb93 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -9,6 +9,7 @@ use auto_impl::auto_impl; use reth_execution_types::ExecutionOutcome; use reth_primitives::Bytecode; use reth_storage_errors::provider::{ProviderError, ProviderResult}; +use reth_trie_db::StateCommitment; /// Type alias of boxed [`StateProvider`]. pub type StateProviderBox = Box; @@ -82,6 +83,12 @@ pub trait StateProvider: } } +/// Trait implemented for database providers that can provide the [`StateCommitment`] type. +pub trait StateCommitmentProvider { + /// The [`StateCommitment`] type that can be used to perform state commitment operations. + type StateCommitment: StateCommitment; +} + /// Trait implemented for database providers that can be converted into a historical state provider. pub trait TryIntoHistoricalStateProvider { /// Returns a historical [`StateProvider`] indexed by the given historic block number. @@ -91,13 +98,6 @@ pub trait TryIntoHistoricalStateProvider { ) -> ProviderResult; } -/// Trait implemented for database providers that can be converted into a latest state provider -/// reference. -pub trait AsLatestStateProviderRef { - /// Returns a [`StateProvider`] for the latest state. - fn latest<'a>(&'a self) -> Box; -} - /// Light wrapper that returns `StateProvider` implementations that correspond to the given /// `BlockNumber`, the latest state, or the pending state. /// From 10af3b1a82d8a9a5874c5970d761342d676afcde Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 25 Nov 2024 13:17:23 +0800 Subject: [PATCH 06/38] feat: introduce HashedPostStateProvider --- Cargo.lock | 5 +-- .../src/commands/debug_cmd/build_block.rs | 5 +-- .../commands/debug_cmd/in_memory_merkle.rs | 10 +++--- crates/blockchain-tree/src/blockchain_tree.rs | 11 +++++-- crates/blockchain-tree/src/chain.rs | 11 +++---- crates/chain-state/Cargo.toml | 4 +-- crates/chain-state/src/in_memory.rs | 10 ++++-- crates/chain-state/src/memory_overlay.rs | 11 +++++-- .../engine/invalid-block-hooks/src/witness.rs | 4 +-- crates/engine/tree/src/tree/mod.rs | 16 +++++++--- crates/engine/util/src/reorg.rs | 3 +- crates/ethereum/payload/Cargo.toml | 1 - crates/ethereum/payload/src/lib.rs | 3 +- .../execution-types/src/execution_outcome.rs | 6 ++-- crates/optimism/payload/Cargo.toml | 1 - crates/optimism/payload/src/builder.rs | 27 ++++++++-------- crates/revm/src/test_utils.rs | 14 +++++--- crates/rpc/rpc-eth-api/Cargo.toml | 1 - .../rpc-eth-api/src/helpers/pending_block.rs | 3 +- crates/rpc/rpc-eth-types/src/cache/db.rs | 11 ++++++- crates/rpc/rpc/Cargo.toml | 1 - crates/rpc/rpc/src/validation.rs | 3 +- .../src/providers/blockchain_provider.rs | 19 +++++++++-- .../src/providers/bundle_state_provider.rs | 22 +++++++++---- .../provider/src/providers/database/mod.rs | 21 +++++++++--- .../src/providers/state/historical.rs | 26 +++++++++++++-- .../provider/src/providers/state/latest.rs | 26 +++++++++++++-- .../provider/src/providers/state/macros.rs | 3 ++ .../storage/provider/src/test_utils/mock.rs | 9 +++++- .../storage/provider/src/test_utils/noop.rs | 8 ++++- crates/storage/provider/src/writer/mod.rs | 10 ++---- crates/storage/storage-api/Cargo.toml | 1 + crates/storage/storage-api/src/state.rs | 10 ++++++ crates/trie/db/src/prefix_set.rs | 32 +++++++++++++------ crates/trie/db/src/state.rs | 8 +++-- crates/trie/trie/benches/hash_post_state.rs | 4 +-- crates/trie/trie/src/state.rs | 14 ++++---- 37 files changed, 258 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e8fc217e4b0..f70aff6e1454 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7457,7 +7457,6 @@ dependencies = [ "reth-provider", "reth-revm", "reth-transaction-pool", - "reth-trie", "revm", "tracing", ] @@ -8367,7 +8366,6 @@ dependencies = [ "reth-revm", "reth-rpc-types-compat", "reth-transaction-pool", - "reth-trie", "revm", "sha2 0.10.8", "thiserror 1.0.69", @@ -8771,7 +8769,6 @@ dependencies = [ "reth-tasks", "reth-testing-utils", "reth-transaction-pool", - "reth-trie", "revm", "revm-inspectors", "revm-primitives", @@ -8951,7 +8948,6 @@ dependencies = [ "reth-rpc-types-compat", "reth-tasks", "reth-transaction-pool", - "reth-trie", "revm", "revm-inspectors", "revm-primitives", @@ -9200,6 +9196,7 @@ dependencies = [ "reth-storage-errors", "reth-trie", "reth-trie-db", + "revm", ] [[package]] diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index adb2c83b1b2f..4a950f56cf79 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -261,7 +261,8 @@ impl> Command { let block_with_senders = SealedBlockWithSenders::new(block.clone(), senders).unwrap(); - let db = StateProviderDatabase::new(blockchain_db.latest()?); + let state_provider = blockchain_db.latest()?; + let db = StateProviderDatabase::new(&state_provider); let executor = EthExecutorProvider::ethereum(provider_factory.chain_spec()).executor(db); @@ -271,7 +272,7 @@ impl> Command { ExecutionOutcome::from((block_execution_output, block.number)); debug!(target: "reth::cli", ?execution_outcome, "Executed block"); - let hashed_post_state = execution_outcome.hash_state_slow(); + let hashed_post_state = state_provider.hashed_post_state(execution_outcome.state()); let (state_root, trie_updates) = StateRoot::overlay_root_with_updates( provider_factory.provider()?.tx_ref(), hashed_post_state.clone(), diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index ce5f318632e5..c7a05ace3ebf 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -22,8 +22,9 @@ use reth_network_api::NetworkInfo; use reth_node_ethereum::EthExecutorProvider; use reth_provider::{ providers::ProviderNodeTypes, writer::UnifiedStorageWriter, AccountExtReader, - ChainSpecProvider, HashingWriter, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, - ProviderFactory, StageCheckpointReader, StateWriter, StorageReader, + ChainSpecProvider, HashedPostStateProvider, HashingWriter, HeaderProvider, + LatestStateProviderRef, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, + StateWriter, StorageReader, }; use reth_revm::database::StateProviderDatabase; use reth_stages::StageId; @@ -132,7 +133,8 @@ impl> Command { ) .await?; - let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); + let state_provider = LatestStateProviderRef::new(&provider); + let db = StateProviderDatabase::new(&state_provider); let executor = EthExecutorProvider::ethereum(provider_factory.chain_spec()).executor(db); @@ -154,7 +156,7 @@ impl> Command { // Unpacked `BundleState::state_root_slow` function let (in_memory_state_root, in_memory_updates) = StateRoot::overlay_root_with_updates( provider.tx_ref(), - execution_outcome.hash_state_slow(), + state_provider.hashed_post_state(execution_outcome.state()), )?; if in_memory_state_root == block.state_root { diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 1a8a390e99dc..661eaa67ee8c 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -24,7 +24,7 @@ use reth_provider::{ providers::ProviderNodeTypes, BlockExecutionWriter, BlockNumReader, BlockWriter, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, ChainSpecProvider, ChainSplit, ChainSplitTarget, DBProvider, DisplayBlocksChain, - HeaderProvider, ProviderError, StaticFileProviderFactory, + HashedPostStateProvider, HeaderProvider, ProviderError, StaticFileProviderFactory, }; use reth_stages_api::{MetricEvent, MetricEventsSender}; use reth_storage_errors::provider::{ProviderResult, RootMismatch}; @@ -1214,7 +1214,7 @@ where recorder: &mut MakeCanonicalDurationsRecorder, ) -> Result<(), CanonicalError> { let (blocks, state, chain_trie_updates) = chain.into_inner(); - let hashed_state = state.hash_state_slow(); + let hashed_state = self.externals.provider_factory.hashed_post_state(state.state()); let prefix_sets = hashed_state.construct_prefix_sets().freeze(); let hashed_state_sorted = hashed_state.into_sorted(); @@ -1869,7 +1869,12 @@ mod tests { ); let provider = tree.externals.provider_factory.provider().unwrap(); - let prefix_sets = exec5.hash_state_slow().construct_prefix_sets().freeze(); + let prefix_sets = tree + .externals + .provider_factory + .hashed_post_state(exec5.state()) + .construct_prefix_sets() + .freeze(); let state_root = StateRoot::from_tx(provider.tx_ref()).with_prefix_sets(prefix_sets).root().unwrap(); assert_eq!(state_root, block5.state_root); diff --git a/crates/blockchain-tree/src/chain.rs b/crates/blockchain-tree/src/chain.rs index 6ac39c316702..a73d87f3a445 100644 --- a/crates/blockchain-tree/src/chain.rs +++ b/crates/blockchain-tree/src/chain.rs @@ -18,11 +18,11 @@ use reth_execution_types::{Chain, ExecutionOutcome}; use reth_primitives::{GotExpected, SealedBlockWithSenders, SealedHeader}; use reth_provider::{ providers::{BundleStateProvider, ConsistentDbView, ProviderNodeTypes}, - DBProvider, FullExecutionDataProvider, ProviderError, StateRootProvider, - TryIntoHistoricalStateProvider, + DBProvider, FullExecutionDataProvider, HashedPostStateProvider, ProviderError, + StateRootProvider, TryIntoHistoricalStateProvider, }; use reth_revm::database::StateProviderDatabase; -use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInput}; +use reth_trie::{updates::TrieUpdates, TrieInput}; use reth_trie_parallel::root::ParallelStateRoot; use std::{ collections::BTreeMap, @@ -228,14 +228,13 @@ impl AppendableChain { execution_outcome.extend(initial_execution_outcome.clone()); ParallelStateRoot::new( consistent_view, - TrieInput::from_state(execution_outcome.hash_state_slow()), + TrieInput::from_state(provider.hashed_post_state(execution_outcome.state())), ) .incremental_root_with_updates() .map(|(root, updates)| (root, Some(updates))) .map_err(ProviderError::from)? } else { - let hashed_state = - HashedPostState::from_bundle_state(&initial_execution_outcome.state().state); + let hashed_state = provider.hashed_post_state(initial_execution_outcome.state()); let state_root = provider.state_root(hashed_state)?; (state_root, None) }; diff --git a/crates/chain-state/Cargo.toml b/crates/chain-state/Cargo.toml index ff62b76e5dfb..7d6d503a7060 100644 --- a/crates/chain-state/Cargo.toml +++ b/crates/chain-state/Cargo.toml @@ -25,6 +25,7 @@ reth-trie.workspace = true alloy-eips.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true +revm.workspace = true # async tokio = { workspace = true, default-features = false, features = ["sync", "macros"] } @@ -44,7 +45,6 @@ pin-project.workspace = true alloy-signer = { workspace = true, optional = true } alloy-signer-local = { workspace = true, optional = true } rand = { workspace = true, optional = true } -revm = { workspace = true, optional = true } [dev-dependencies] reth-testing-utils.workspace = true @@ -59,9 +59,7 @@ test-utils = [ "alloy-signer", "alloy-signer-local", "rand", - "revm", "reth-chainspec/test-utils", "reth-primitives/test-utils", "reth-trie/test-utils", - "revm?/test-utils", ] diff --git a/crates/chain-state/src/in_memory.rs b/crates/chain-state/src/in_memory.rs index e07eaeaa5d9e..9320ff64107b 100644 --- a/crates/chain-state/src/in_memory.rs +++ b/crates/chain-state/src/in_memory.rs @@ -919,8 +919,8 @@ mod tests { use reth_errors::ProviderResult; use reth_primitives::{Account, Bytecode, EthPrimitives, Receipt}; use reth_storage_api::{ - AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateRootProvider, - StorageRootProvider, + AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, + StateRootProvider, StorageRootProvider, }; use reth_trie::{AccountProof, HashedStorage, MultiProof, StorageProof, TrieInput}; @@ -1015,6 +1015,12 @@ mod tests { } } + impl HashedPostStateProvider for MockStateProvider { + fn hashed_post_state(&self, _bundle_state: &revm::db::BundleState) -> HashedPostState { + HashedPostState::default() + } + } + impl StorageRootProvider for MockStateProvider { fn storage_root( &self, diff --git a/crates/chain-state/src/memory_overlay.rs b/crates/chain-state/src/memory_overlay.rs index 88cd411d38b2..92ae409991da 100644 --- a/crates/chain-state/src/memory_overlay.rs +++ b/crates/chain-state/src/memory_overlay.rs @@ -7,12 +7,13 @@ use alloy_primitives::{ use reth_errors::ProviderResult; use reth_primitives::{Account, Bytecode, NodePrimitives}; use reth_storage_api::{ - AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateRootProvider, - StorageRootProvider, + AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, + StateRootProvider, StorageRootProvider, }; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput, }; +use revm::db::BundleState; use std::sync::OnceLock; /// A state provider that stores references to in-memory blocks along with their state as well as a @@ -202,6 +203,12 @@ macro_rules! impl_state_provider { } } + impl $($tokens)* HashedPostStateProvider for $type { + fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState { + self.historical.hashed_post_state(bundle_state) + } + } + impl $($tokens)* StateProvider for $type { fn storage( &self, diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 4e92411ea128..0daf3a64911e 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -20,7 +20,7 @@ use reth_revm::{ }; use reth_rpc_api::DebugApiClient; use reth_tracing::tracing::warn; -use reth_trie::{updates::TrieUpdates, HashedPostState, HashedStorage}; +use reth_trie::{updates::TrieUpdates, HashedStorage}; use serde::Serialize; /// Generates a witness for the given block and saves it to a file. @@ -130,7 +130,7 @@ where // // Note: We grab *all* accounts in the cache here, as the `BundleState` prunes // referenced accounts + storage slots. - let mut hashed_state = HashedPostState::from_bundle_state(&bundle_state.state); + let mut hashed_state = db.database.hashed_post_state(&bundle_state); for (address, account) in db.cache.accounts { let hashed_address = keccak256(address); hashed_state diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 39843377684c..4bfa0e20ee76 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -39,8 +39,8 @@ use reth_payload_validator::ExecutionPayloadValidator; use reth_primitives::{Block, GotExpected, SealedBlock, SealedBlockWithSenders, SealedHeader}; use reth_provider::{ providers::ConsistentDbView, BlockReader, DatabaseProviderFactory, ExecutionOutcome, - ProviderError, StateProviderBox, StateProviderFactory, StateReader, StateRootProvider, - TransactionVariant, + HashedPostStateProvider, ProviderError, StateProviderBox, StateProviderFactory, StateReader, + StateRootProvider, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_stages_api::ControlFlow; @@ -536,7 +536,13 @@ impl std::fmt::Debug impl EngineApiTreeHandler where - P: DatabaseProviderFactory + BlockReader + StateProviderFactory + StateReader + Clone + 'static, + P: DatabaseProviderFactory + + BlockReader + + StateProviderFactory + + StateReader + + HashedPostStateProvider + + Clone + + 'static,

::Provider: BlockReader, E: BlockExecutorProvider, T: EngineTypes, @@ -1541,7 +1547,7 @@ where .provider .get_state(block.number)? .ok_or_else(|| ProviderError::StateForNumberNotFound(block.number))?; - let hashed_state = execution_output.hash_state_slow(); + let hashed_state = self.provider.hashed_post_state(execution_output.state()); Ok(Some(ExecutedBlock { block: Arc::new(block), @@ -2215,7 +2221,7 @@ where return Err(err.into()) } - let hashed_state = HashedPostState::from_bundle_state(&output.state.state); + let hashed_state = self.provider.hashed_post_state(&output.state); trace!(target: "engine::tree", block=?sealed_block.num_hash(), "Calculating block state root"); let root_time = Instant::now(); diff --git a/crates/engine/util/src/reorg.rs b/crates/engine/util/src/reorg.rs index ec69bbd00241..96bf4f252970 100644 --- a/crates/engine/util/src/reorg.rs +++ b/crates/engine/util/src/reorg.rs @@ -26,7 +26,6 @@ use reth_revm::{ DatabaseCommit, }; use reth_rpc_types_compat::engine::payload::block_to_payload; -use reth_trie::HashedPostState; use revm_primitives::{ calc_excess_blob_gas, BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, }; @@ -383,7 +382,7 @@ where reorg_target.number, Default::default(), ); - let hashed_state = HashedPostState::from_bundle_state(&outcome.state().state); + let hashed_state = state_provider.hashed_post_state(outcome.state()); let (blob_gas_used, excess_blob_gas) = if chain_spec.is_cancun_active_at_timestamp(reorg_target.timestamp) { diff --git a/crates/ethereum/payload/Cargo.toml b/crates/ethereum/payload/Cargo.toml index 4e0880d1d153..b01f4c5bc74f 100644 --- a/crates/ethereum/payload/Cargo.toml +++ b/crates/ethereum/payload/Cargo.toml @@ -25,7 +25,6 @@ reth-basic-payload-builder.workspace = true reth-evm.workspace = true reth-evm-ethereum.workspace = true reth-errors.workspace = true -reth-trie.workspace = true reth-chain-state.workspace = true reth-chainspec.workspace = true diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 4ec1e212c8d5..41b3317bc134 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -35,7 +35,6 @@ use reth_transaction_pool::{ noop::NoopTransactionPool, BestTransactions, BestTransactionsAttributes, TransactionPool, ValidPoolTransaction, }; -use reth_trie::HashedPostState; use revm::{ db::{states::bundle_state::BundleRetention, State}, primitives::{ @@ -375,7 +374,7 @@ where let logs_bloom = execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); // calculate the state root - let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); + let hashed_state = db.database.db.hashed_post_state(execution_outcome.state()); let (state_root, trie_output) = { db.database.inner().state_root_with_updates(hashed_state.clone()).inspect_err(|err| { warn!(target: "payload_builder", diff --git a/crates/evm/execution-types/src/execution_outcome.rs b/crates/evm/execution-types/src/execution_outcome.rs index 412269ace9cd..82cc1d20c247 100644 --- a/crates/evm/execution-types/src/execution_outcome.rs +++ b/crates/evm/execution-types/src/execution_outcome.rs @@ -4,7 +4,7 @@ use alloy_eips::eip7685::Requests; use alloy_primitives::{Address, BlockNumber, Bloom, Log, B256, U256}; use reth_primitives::{logs_bloom, Account, Bytecode, Receipts, StorageEntry}; use reth_primitives_traits::{receipt::ReceiptExt, Receipt}; -use reth_trie::HashedPostState; +use reth_trie::{HashedPostState, KeyHasher}; use revm::{ db::{states::BundleState, BundleAccount}, primitives::AccountInfo, @@ -166,8 +166,8 @@ impl ExecutionOutcome { /// Returns [`HashedPostState`] for this execution outcome. /// See [`HashedPostState::from_bundle_state`] for more info. - pub fn hash_state_slow(&self) -> HashedPostState { - HashedPostState::from_bundle_state(&self.bundle.state) + pub fn hash_state_slow(&self) -> HashedPostState { + HashedPostState::from_bundle_state::(&self.bundle.state) } /// Transform block number to the index of block. diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index 7f47da7e2360..1c4f855b6aa2 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -26,7 +26,6 @@ reth-payload-builder-primitives.workspace = true reth-payload-util.workspace = true reth-payload-primitives = { workspace = true, features = ["op"] } reth-basic-payload-builder.workspace = true -reth-trie.workspace = true reth-chain-state.workspace = true # op-reth diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 3644d8f71a54..0d90681e6ff3 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -19,12 +19,14 @@ use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; use reth_payload_util::PayloadTransactions; use reth_primitives::{proofs, Block, BlockBody, Receipt, SealedHeader, TransactionSigned, TxType}; -use reth_provider::{ProviderError, StateProofProvider, StateProviderFactory, StateRootProvider}; +use reth_provider::{ + HashedPostStateProvider, ProviderError, StateProofProvider, StateProviderFactory, + StateRootProvider, +}; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::{ noop::NoopTransactionPool, BestTransactionsAttributes, TransactionPool, }; -use reth_trie::HashedPostState; use revm::{ db::{states::bundle_state::BundleRetention, State}, primitives::{ @@ -336,7 +338,7 @@ where where EvmConfig: ConfigureEvm

, DB: Database + AsRef

, - P: StateRootProvider, + P: StateRootProvider + HashedPostStateProvider, { let ExecutedPayload { info, @@ -367,17 +369,16 @@ where execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); // // calculate the state root - let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); + let state_provider = state.database.as_ref(); + let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); let (state_root, trie_output) = { - state.database.as_ref().state_root_with_updates(hashed_state.clone()).inspect_err( - |err| { - warn!(target: "payload_builder", - parent_header=%ctx.parent().hash(), - %err, - "failed to calculate state root for payload" - ); - }, - )? + state_provider.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { + warn!(target: "payload_builder", + parent_header=%ctx.parent().hash(), + %err, + "failed to calculate state root for payload" + ); + })? }; // create the block header diff --git a/crates/revm/src/test_utils.rs b/crates/revm/src/test_utils.rs index 813997c72d11..23705dda0e1d 100644 --- a/crates/revm/src/test_utils.rs +++ b/crates/revm/src/test_utils.rs @@ -6,13 +6,13 @@ use alloy_primitives::{ }; use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ - AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateRootProvider, - StorageRootProvider, + AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, + StateRootProvider, StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ - updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, StorageProof, - TrieInput, + updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, KeccakKeyHasher, + MultiProof, StorageProof, TrieInput, }; /// Mock state for testing @@ -141,6 +141,12 @@ impl StateProofProvider for StateProviderTest { } } +impl HashedPostStateProvider for StateProviderTest { + fn hashed_post_state(&self, bundle_state: &revm::db::BundleState) -> HashedPostState { + HashedPostState::from_bundle_state::(bundle_state.state()) + } +} + impl StateProvider for StateProviderTest { fn storage( &self, diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index e4b1b28074f0..ba13943923f8 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -29,7 +29,6 @@ reth-execution-types.workspace = true reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-network-api.workspace = true -reth-trie.workspace = true reth-node-api.workspace = true # ethereum diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index 548f9101023d..b697c6870c5a 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -33,7 +33,6 @@ use reth_revm::{ }; use reth_rpc_eth_types::{EthApiError, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; -use reth_trie::HashedPostState; use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State}; use std::time::{Duration, Instant}; use tokio::sync::Mutex; @@ -389,7 +388,7 @@ pub trait LoadPendingBlock: block_number, Vec::new(), ); - let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); + let hashed_state = db.database.hashed_post_state(execution_outcome.state()); let receipts_root = self.receipts_root(&block_env, &execution_outcome, block_number); diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index 50fd4b04625f..de61f308fd6c 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -8,7 +8,7 @@ use alloy_primitives::{ }; use reth_errors::ProviderResult; use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef}; -use reth_storage_api::StateProvider; +use reth_storage_api::{HashedPostStateProvider, StateProvider}; use reth_trie::HashedStorage; use revm::Database; @@ -130,6 +130,15 @@ impl reth_storage_api::BlockHashReader for StateProviderTraitObjWrapper<'_> { } } +impl HashedPostStateProvider for StateProviderTraitObjWrapper<'_> { + fn hashed_post_state( + &self, + bundle_state: &revm::db::BundleState, + ) -> reth_trie::HashedPostState { + self.0.hashed_post_state(bundle_state) + } +} + impl StateProvider for StateProviderTraitObjWrapper<'_> { fn storage( &self, diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 5418cd1eb3a7..d4aef0536030 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -33,7 +33,6 @@ reth-evm.workspace = true reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-network-types.workspace = true -reth-trie.workspace = true reth-consensus.workspace = true reth-payload-validator.workspace = true diff --git a/crates/rpc/rpc/src/validation.rs b/crates/rpc/rpc/src/validation.rs index b997dec1e015..bd8ee5486643 100644 --- a/crates/rpc/rpc/src/validation.rs +++ b/crates/rpc/rpc/src/validation.rs @@ -24,7 +24,6 @@ use reth_revm::{cached::CachedReads, database::StateProviderDatabase}; use reth_rpc_api::BlockSubmissionValidationApiServer; use reth_rpc_eth_types::EthApiError; use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; -use reth_trie::HashedPostState; use revm_primitives::{Address, B256, U256}; use serde::{Deserialize, Serialize}; use std::{collections::HashSet, sync::Arc}; @@ -178,7 +177,7 @@ where self.ensure_payment(&block, &output, &message)?; let state_root = - state_provider.state_root(HashedPostState::from_bundle_state(&output.state.state))?; + state_provider.state_root(state_provider.hashed_post_state(&output.state))?; if state_root != block.state_root { return Err(ConsensusError::BodyStateRootDiff( diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index 083e7fb596b6..1df7db0e4599 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -4,8 +4,8 @@ use crate::{ AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, BlockSource, CanonChainTracker, CanonStateNotifications, CanonStateSubscriptions, ChainSpecProvider, ChainStateBlockReader, ChangeSetReader, DatabaseProvider, - DatabaseProviderFactory, EvmEnvProvider, FullProvider, HeaderProvider, ProviderError, - ProviderFactory, PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, + DatabaseProviderFactory, EvmEnvProvider, FullProvider, HashedPostStateProvider, HeaderProvider, + ProviderError, ProviderFactory, PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateProviderBox, StateProviderFactory, StateReader, StaticFileProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; @@ -34,7 +34,12 @@ use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{DBProvider, StorageChangeSetReader}; use reth_storage_errors::provider::ProviderResult; -use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg}; +use reth_trie::HashedPostState; +use reth_trie_db::StateCommitment; +use revm::{ + db::BundleState, + primitives::{BlockEnv, CfgEnvWithHandlerCfg}, +}; use std::{ ops::{Add, RangeBounds, RangeInclusive, Sub}, sync::Arc, @@ -631,6 +636,14 @@ impl StateProviderFactory for BlockchainProvider2 { } } +impl HashedPostStateProvider for BlockchainProvider2 { + fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState { + HashedPostState::from_bundle_state::<::KeyHasher>( + bundle_state.state(), + ) + } +} + impl CanonChainTracker for BlockchainProvider2 where Self: BlockReader, diff --git a/crates/storage/provider/src/providers/bundle_state_provider.rs b/crates/storage/provider/src/providers/bundle_state_provider.rs index be6549033cde..a7a102bc5c41 100644 --- a/crates/storage/provider/src/providers/bundle_state_provider.rs +++ b/crates/storage/provider/src/providers/bundle_state_provider.rs @@ -6,7 +6,7 @@ use alloy_primitives::{ Address, BlockNumber, Bytes, B256, }; use reth_primitives::{Account, Bytecode}; -use reth_storage_api::{StateProofProvider, StorageRootProvider}; +use reth_storage_api::{HashedPostStateProvider, StateProofProvider, StorageRootProvider}; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput, @@ -86,7 +86,7 @@ impl StateRootProvider { fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult { let bundle_state = self.block_execution_data_provider.execution_outcome().state(); - let mut state = HashedPostState::from_bundle_state(&bundle_state.state); + let mut state = self.hashed_post_state(bundle_state); state.extend(hashed_state); self.state_provider.state_root(state) } @@ -100,7 +100,7 @@ impl StateRootProvider hashed_state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { let bundle_state = self.block_execution_data_provider.execution_outcome().state(); - let mut state = HashedPostState::from_bundle_state(&bundle_state.state); + let mut state = self.hashed_post_state(bundle_state); state.extend(hashed_state); self.state_provider.state_root_with_updates(state) } @@ -110,7 +110,7 @@ impl StateRootProvider mut input: TrieInput, ) -> ProviderResult<(B256, TrieUpdates)> { let bundle_state = self.block_execution_data_provider.execution_outcome().state(); - input.prepend(HashedPostState::from_bundle_state(&bundle_state.state)); + input.prepend(self.hashed_post_state(bundle_state)); self.state_provider.state_root_from_nodes_with_updates(input) } } @@ -150,7 +150,7 @@ impl StateProofProvider slots: &[B256], ) -> ProviderResult { let bundle_state = self.block_execution_data_provider.execution_outcome().state(); - input.prepend(HashedPostState::from_bundle_state(&bundle_state.state)); + input.prepend(self.hashed_post_state(bundle_state)); self.state_provider.proof(input, address, slots) } @@ -160,7 +160,7 @@ impl StateProofProvider targets: HashMap>, ) -> ProviderResult { let bundle_state = self.block_execution_data_provider.execution_outcome().state(); - input.prepend(HashedPostState::from_bundle_state(&bundle_state.state)); + input.prepend(self.hashed_post_state(bundle_state)); self.state_provider.multiproof(input, targets) } @@ -170,11 +170,19 @@ impl StateProofProvider target: HashedPostState, ) -> ProviderResult> { let bundle_state = self.block_execution_data_provider.execution_outcome().state(); - input.prepend(HashedPostState::from_bundle_state(&bundle_state.state)); + input.prepend(self.hashed_post_state(bundle_state)); self.state_provider.witness(input, target) } } +impl HashedPostStateProvider + for BundleStateProvider +{ + fn hashed_post_state(&self, bundle_state: &revm::db::BundleState) -> HashedPostState { + self.state_provider.hashed_post_state(bundle_state) + } +} + impl StateProvider for BundleStateProvider { fn storage( &self, diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index a64bb2578dd6..094a3369ee1b 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -3,9 +3,9 @@ use crate::{ to_range, traits::{BlockSource, ReceiptProvider}, BlockHashReader, BlockNumReader, BlockReader, ChainSpecProvider, DatabaseProviderFactory, - EvmEnvProvider, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, ProviderError, - PruneCheckpointReader, StageCheckpointReader, StateProviderBox, StaticFileProviderFactory, - TransactionVariant, TransactionsProvider, WithdrawalsProvider, + EvmEnvProvider, HashedPostStateProvider, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, + ProviderError, PruneCheckpointReader, StageCheckpointReader, StateProviderBox, + StaticFileProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; use alloy_consensus::Header; use alloy_eips::{ @@ -28,7 +28,12 @@ use reth_prune_types::{PruneCheckpoint, PruneModes, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::TryIntoHistoricalStateProvider; use reth_storage_errors::provider::ProviderResult; -use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg}; +use reth_trie::HashedPostState; +use reth_trie_db::StateCommitment; +use revm::{ + db::BundleState, + primitives::{BlockEnv, CfgEnvWithHandlerCfg}, +}; use std::{ ops::{RangeBounds, RangeInclusive}, path::Path, @@ -625,6 +630,14 @@ impl PruneCheckpointReader for ProviderFactory { } } +impl HashedPostStateProvider for ProviderFactory { + fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState { + HashedPostState::from_bundle_state::<::KeyHasher>( + bundle_state.state(), + ) + } +} + impl Clone for ProviderFactory { fn clone(&self) -> Self { Self { diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index a70fe9ac203b..e4594d3ddda9 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -1,6 +1,6 @@ use crate::{ providers::state::macros::delegate_provider_impls, AccountReader, BlockHashReader, - ProviderError, StateProvider, StateRootProvider, + HashedPostStateProvider, ProviderError, StateProvider, StateRootProvider, }; use alloy_eips::merge::EPOCH_SLOTS; use alloy_primitives::{ @@ -27,7 +27,7 @@ use reth_trie::{ }; use reth_trie_db::{ DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot, - DatabaseStorageProof, DatabaseStorageRoot, DatabaseTrieWitness, + DatabaseStorageProof, DatabaseStorageRoot, DatabaseTrieWitness, StateCommitment, }; use std::fmt::Debug; @@ -381,6 +381,16 @@ impl StateProof } } +impl HashedPostStateProvider + for HistoricalStateProviderRef<'_, Provider> +{ + fn hashed_post_state(&self, bundle_state: &revm::db::BundleState) -> HashedPostState { + HashedPostState::from_bundle_state::< + ::KeyHasher, + >(bundle_state.state()) + } +} + impl StateProvider for HistoricalStateProviderRef<'_, Provider> { @@ -420,6 +430,12 @@ impl StateCommitmentProvider + for HistoricalStateProviderRef<'_, Provider> +{ + type StateCommitment = Provider::StateCommitment; +} + /// State provider for a given block number. /// For more detailed description, see [`HistoricalStateProviderRef`]. #[derive(Debug)] @@ -469,6 +485,12 @@ impl } } +impl StateCommitmentProvider + for HistoricalStateProvider +{ + type StateCommitment = Provider::StateCommitment; +} + // Delegates all provider impls to [HistoricalStateProviderRef] delegate_provider_impls!(HistoricalStateProvider where [Provider: DBProvider + BlockNumReader + BlockHashReader + StateCommitmentProvider]); diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index ab70d759b992..825817357ca9 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -1,6 +1,6 @@ use crate::{ providers::state::macros::delegate_provider_impls, AccountReader, BlockHashReader, - StateProvider, StateRootProvider, + HashedPostStateProvider, StateProvider, StateRootProvider, }; use alloy_primitives::{ map::{HashMap, HashSet}, @@ -21,7 +21,7 @@ use reth_trie::{ }; use reth_trie_db::{ DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot, - DatabaseTrieWitness, + DatabaseTrieWitness, StateCommitment, }; /// State provider over latest state that takes tx reference. @@ -146,6 +146,16 @@ impl StateProofProvider } } +impl HashedPostStateProvider + for LatestStateProviderRef<'_, Provider> +{ + fn hashed_post_state(&self, bundle_state: &revm::db::BundleState) -> HashedPostState { + HashedPostState::from_bundle_state::< + ::KeyHasher, + >(bundle_state.state()) + } +} + impl StateProvider for LatestStateProviderRef<'_, Provider> { @@ -170,11 +180,17 @@ impl StateProv } } +impl StateCommitmentProvider + for LatestStateProviderRef<'_, Provider> +{ + type StateCommitment = Provider::StateCommitment; +} + /// State provider for the latest state. #[derive(Debug)] pub struct LatestStateProvider(Provider); -impl LatestStateProvider { +impl LatestStateProvider { /// Create new state provider pub const fn new(db: Provider) -> Self { Self(db) @@ -187,6 +203,10 @@ impl LatestStateProvider { } } +impl StateCommitmentProvider for LatestStateProvider { + type StateCommitment = Provider::StateCommitment; +} + // Delegates all provider impls to [LatestStateProviderRef] delegate_provider_impls!(LatestStateProvider where [Provider: DBProvider + BlockHashReader + StateCommitmentProvider]); diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index b90924354c43..860407923573 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -56,6 +56,9 @@ macro_rules! delegate_provider_impls { fn multiproof(&self, input: reth_trie::TrieInput, targets: alloy_primitives::map::HashMap>) -> reth_storage_errors::provider::ProviderResult; fn witness(&self, input: reth_trie::TrieInput, target: reth_trie::HashedPostState) -> reth_storage_errors::provider::ProviderResult>; } + HashedPostStateProvider $(where [$($generics)*])? { + fn hashed_post_state(&self, bundle_state: &revm::db::BundleState) -> reth_trie::HashedPostState; + } ); } } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 43bb1e809422..1c1e933dc89b 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -29,7 +29,8 @@ use reth_primitives::{ }; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{ - DatabaseProviderFactory, StageCheckpointReader, StateProofProvider, StorageRootProvider, + DatabaseProviderFactory, HashedPostStateProvider, StageCheckpointReader, StateProofProvider, + StorageRootProvider, }; use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult}; use reth_trie::{ @@ -668,6 +669,12 @@ impl StateProofProvider for MockEthProvider { } } +impl HashedPostStateProvider for MockEthProvider { + fn hashed_post_state(&self, _state: &revm::db::BundleState) -> HashedPostState { + HashedPostState::default() + } +} + impl StateProvider for MockEthProvider { fn storage( &self, diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index d12539a2c27f..949adc86bcaf 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -27,7 +27,7 @@ use reth_primitives::{ }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; -use reth_storage_api::{StateProofProvider, StorageRootProvider}; +use reth_storage_api::{HashedPostStateProvider, StateProofProvider, StorageRootProvider}; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput, @@ -397,6 +397,12 @@ impl StateProofProvider for NoopProvider { } } +impl HashedPostStateProvider for NoopProvider { + fn hashed_post_state(&self, _bundle_state: &revm::db::BundleState) -> HashedPostState { + HashedPostState::default() + } +} + impl StateProvider for NoopProvider { fn storage( &self, diff --git a/crates/storage/provider/src/writer/mod.rs b/crates/storage/provider/src/writer/mod.rs index 17dea5a6d51f..43c192f55326 100644 --- a/crates/storage/provider/src/writer/mod.rs +++ b/crates/storage/provider/src/writer/mod.rs @@ -557,7 +557,7 @@ mod tests { transaction::{DbTx, DbTxMut}, }; use reth_primitives::{Account, Receipt, Receipts, StorageEntry}; - use reth_storage_api::DatabaseProviderFactory; + use reth_storage_api::{DatabaseProviderFactory, HashedPostStateProvider}; use reth_trie::{ test_utils::{state_root, storage_root_prehashed}, HashedPostState, HashedStorage, StateRoot, StorageRoot, @@ -1447,13 +1447,7 @@ mod tests { assert_eq!( StateRoot::overlay_root( tx, - ExecutionOutcome::::new( - state.bundle_state.clone(), - Receipts::default(), - 0, - Vec::new() - ) - .hash_state_slow(), + provider_factory.hashed_post_state(&state.bundle_state) ) .unwrap(), state_root(expected.clone().into_iter().map(|(address, (account, storage))| ( diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index ba2ccf1b1573..7ebff976d135 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -25,6 +25,7 @@ reth-storage-errors.workspace = true reth-trie.workspace = true reth-trie-db.workspace = true reth-db.workspace = true +revm.workspace = true # ethereum alloy-eips.workspace = true diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 49db24a9bb93..98af854b2498 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -9,7 +9,9 @@ use auto_impl::auto_impl; use reth_execution_types::ExecutionOutcome; use reth_primitives::Bytecode; use reth_storage_errors::provider::{ProviderError, ProviderResult}; +use reth_trie::HashedPostState; use reth_trie_db::StateCommitment; +use revm::db::states::BundleState; /// Type alias of boxed [`StateProvider`]. pub type StateProviderBox = Box; @@ -22,6 +24,7 @@ pub trait StateProvider: + StateRootProvider + StorageRootProvider + StateProofProvider + + HashedPostStateProvider + Send + Sync { @@ -89,6 +92,13 @@ pub trait StateCommitmentProvider { type StateCommitment: StateCommitment; } +/// Trait that provides the hashed state from various sources. +#[auto_impl(&, Arc, Box)] +pub trait HashedPostStateProvider { + /// Returns the `HashedPostState` of the provided [`BundleState`]. + fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState; +} + /// Trait implemented for database providers that can be converted into a historical state provider. pub trait TryIntoHistoricalStateProvider { /// Returns a historical [`StateProvider`] indexed by the given historic block number. diff --git a/crates/trie/db/src/prefix_set.rs b/crates/trie/db/src/prefix_set.rs index cd50503bc703..b8cb380ff0ee 100644 --- a/crates/trie/db/src/prefix_set.rs +++ b/crates/trie/db/src/prefix_set.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{keccak256, BlockNumber, B256}; +use alloy_primitives::{BlockNumber, B256}; use derive_more::Deref; use reth_db::tables; use reth_db_api::{ @@ -8,25 +8,37 @@ use reth_db_api::{ DatabaseError, }; use reth_primitives::StorageEntry; -use reth_trie::prefix_set::{PrefixSetMut, TriePrefixSets}; +use reth_trie::{ + prefix_set::{PrefixSetMut, TriePrefixSets}, + KeyHasher, +}; use reth_trie_common::Nibbles; use std::{ collections::{HashMap, HashSet}, + marker::PhantomData, ops::RangeInclusive, }; /// A wrapper around a database transaction that loads prefix sets within a given block range. -#[derive(Deref, Debug)] -pub struct PrefixSetLoader<'a, TX>(&'a TX); +#[derive(Debug)] +pub struct PrefixSetLoader<'a, TX, KH>(&'a TX, PhantomData); -impl<'a, TX> PrefixSetLoader<'a, TX> { +impl<'a, TX, KH> PrefixSetLoader<'a, TX, KH> { /// Create a new loader. pub const fn new(tx: &'a TX) -> Self { - Self(tx) + Self(tx, PhantomData) + } +} + +impl Deref for PrefixSetLoader<'_, TX, KH> { + type Target = TX; + + fn deref(&self) -> &Self::Target { + self.0 } } -impl PrefixSetLoader<'_, TX> { +impl PrefixSetLoader<'_, TX, KH> { /// Load all account and storage changes for the given block range. pub fn load(self, range: RangeInclusive) -> Result { // Initialize prefix sets. @@ -39,7 +51,7 @@ impl PrefixSetLoader<'_, TX> { let mut account_hashed_state_cursor = self.cursor_read::()?; for account_entry in account_changeset_cursor.walk_range(range.clone())? { let (_, AccountBeforeTx { address, .. }) = account_entry?; - let hashed_address = keccak256(address); + let hashed_address = KH::hash_key(address); account_prefix_set.insert(Nibbles::unpack(hashed_address)); if account_hashed_state_cursor.seek_exact(hashed_address)?.is_none() { @@ -53,12 +65,12 @@ impl PrefixSetLoader<'_, TX> { let storage_range = BlockNumberAddress::range(range); for storage_entry in storage_cursor.walk_range(storage_range)? { let (BlockNumberAddress((_, address)), StorageEntry { key, .. }) = storage_entry?; - let hashed_address = keccak256(address); + let hashed_address = KH::hash_key(address); account_prefix_set.insert(Nibbles::unpack(hashed_address)); storage_prefix_sets .entry(hashed_address) .or_default() - .insert(Nibbles::unpack(keccak256(key))); + .insert(Nibbles::unpack(KH::hash_key(key))); } Ok(TriePrefixSets { diff --git a/crates/trie/db/src/state.rs b/crates/trie/db/src/state.rs index 6e2cea5051d0..5f77b0b916c4 100644 --- a/crates/trie/db/src/state.rs +++ b/crates/trie/db/src/state.rs @@ -10,7 +10,8 @@ use reth_execution_errors::StateRootError; use reth_storage_errors::db::DatabaseError; use reth_trie::{ hashed_cursor::HashedPostStateCursorFactory, trie_cursor::InMemoryTrieCursorFactory, - updates::TrieUpdates, HashedPostState, HashedStorage, StateRoot, StateRootProgress, TrieInput, + updates::TrieUpdates, HashedPostState, HashedStorage, KeccakKeyHasher, StateRoot, + StateRootProgress, TrieInput, }; use std::{collections::HashMap, ops::RangeInclusive}; use tracing::debug; @@ -136,7 +137,7 @@ impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> tx: &'a TX, range: RangeInclusive, ) -> Result { - let loaded_prefix_sets = PrefixSetLoader::new(tx).load(range)?; + let loaded_prefix_sets = PrefixSetLoader::<_, KeccakKeyHasher>::new(tx).load(range)?; Ok(Self::from_tx(tx).with_prefix_sets(loaded_prefix_sets)) } @@ -265,6 +266,7 @@ mod tests { use alloy_primitives::{hex, map::HashMap, Address, U256}; use reth_db::test_utils::create_test_rw_db; use reth_db_api::database::Database; + use reth_trie::KeccakKeyHasher; use revm::{db::BundleState, primitives::AccountInfo}; #[test] @@ -285,7 +287,7 @@ mod tests { .build(); assert_eq!(bundle_state.reverts.len(), 1); - let post_state = HashedPostState::from_bundle_state(&bundle_state.state); + let post_state = HashedPostState::from_bundle_state::(&bundle_state.state); assert_eq!(post_state.accounts.len(), 2); assert_eq!(post_state.storages.len(), 2); diff --git a/crates/trie/trie/benches/hash_post_state.rs b/crates/trie/trie/benches/hash_post_state.rs index 6e913ef78a3c..7111a785f469 100644 --- a/crates/trie/trie/benches/hash_post_state.rs +++ b/crates/trie/trie/benches/hash_post_state.rs @@ -2,7 +2,7 @@ use alloy_primitives::{keccak256, map::HashMap, Address, B256, U256}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner}; -use reth_trie::{HashedPostState, HashedStorage}; +use reth_trie::{HashedPostState, HashedStorage, KeccakKeyHasher}; use revm::db::{states::BundleBuilder, BundleAccount}; pub fn hash_post_state(c: &mut Criterion) { @@ -19,7 +19,7 @@ pub fn hash_post_state(c: &mut Criterion) { // parallel group.bench_function(BenchmarkId::new("parallel hashing", size), |b| { - b.iter(|| HashedPostState::from_bundle_state(&state)) + b.iter(|| HashedPostState::from_bundle_state::(&state)) }); } } diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index eca126744e96..66a6af5a3a70 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -6,6 +6,7 @@ use alloy_primitives::{keccak256, Address, B256, U256}; use itertools::Itertools; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use reth_primitives::Account; +use reth_trie_common::KeyHasher; use revm::db::{states::CacheAccount, AccountStatus, BundleAccount}; use std::{ borrow::Cow, @@ -25,13 +26,13 @@ impl HashedPostState { /// Initialize [`HashedPostState`] from bundle state. /// Hashes all changed accounts and storage entries that are currently stored in the bundle /// state. - pub fn from_bundle_state<'a>( + pub fn from_bundle_state<'a, KH: KeyHasher>( state: impl IntoParallelIterator, ) -> Self { let hashed = state .into_par_iter() .map(|(address, account)| { - let hashed_address = keccak256(address); + let hashed_address = KH::hash_key(address); let hashed_account = account.info.clone().map(Into::into); let hashed_storage = HashedStorage::from_plain_storage( account.status, @@ -52,13 +53,13 @@ impl HashedPostState { /// Initialize [`HashedPostState`] from cached state. /// Hashes all changed accounts and storage entries that are currently stored in cache. - pub fn from_cache_state<'a>( + pub fn from_cache_state<'a, KH: KeyHasher>( state: impl IntoParallelIterator, ) -> Self { let hashed = state .into_par_iter() .map(|(address, account)| { - let hashed_address = keccak256(address); + let hashed_address = KH::hash_key(address); let hashed_account = account.account.as_ref().map(|a| a.info.clone().into()); let hashed_storage = HashedStorage::from_plain_storage( account.status, @@ -349,6 +350,7 @@ impl HashedStorageSorted { mod tests { use super::*; use alloy_primitives::Bytes; + use reth_trie_common::KeccakKeyHasher; use revm::{ db::{ states::{plain_account::PlainStorage, StorageSlot}, @@ -462,7 +464,7 @@ mod tests { let state = vec![(&address, &account)]; // Convert the bundle state into a hashed post state. - let hashed_state = HashedPostState::from_bundle_state(state); + let hashed_state = HashedPostState::from_bundle_state::(state); // Validate the hashed post state. assert_eq!(hashed_state.accounts.len(), 1); @@ -501,7 +503,7 @@ mod tests { let state = vec![(&address, &account)]; // Convert the cache state into a hashed post state. - let hashed_state = HashedPostState::from_cache_state(state); + let hashed_state = HashedPostState::from_cache_state::(state); // Validate the hashed post state. assert_eq!(hashed_state.accounts.len(), 1); From 500f8c25f1a5e56eaa90dd822076c0bf469bb5c5 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 25 Nov 2024 14:38:25 +0800 Subject: [PATCH 07/38] feat: HashedPostState from reverts --- crates/engine/tree/src/tree/mod.rs | 5 +++-- .../provider/src/providers/blockchain_provider.rs | 6 +++++- .../provider/src/providers/consistent_view.rs | 10 ++++++---- .../storage/provider/src/providers/database/mod.rs | 6 +++++- .../provider/src/providers/state/historical.rs | 4 +++- crates/storage/provider/src/test_utils/mock.rs | 8 ++++++-- crates/trie/db/src/state.rs | 14 +++++++------- crates/trie/parallel/src/proof.rs | 8 +++++++- crates/trie/parallel/src/root.rs | 8 +++++++- 9 files changed, 49 insertions(+), 20 deletions(-) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 4bfa0e20ee76..8263f2edc438 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -39,8 +39,8 @@ use reth_payload_validator::ExecutionPayloadValidator; use reth_primitives::{Block, GotExpected, SealedBlock, SealedBlockWithSenders, SealedHeader}; use reth_provider::{ providers::ConsistentDbView, BlockReader, DatabaseProviderFactory, ExecutionOutcome, - HashedPostStateProvider, ProviderError, StateProviderBox, StateProviderFactory, StateReader, - StateRootProvider, TransactionVariant, + HashedPostStateProvider, ProviderError, StateCommitmentProvider, StateProviderBox, + StateProviderFactory, StateReader, StateRootProvider, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_stages_api::ControlFlow; @@ -540,6 +540,7 @@ where + BlockReader + StateProviderFactory + StateReader + + StateCommitmentProvider + HashedPostStateProvider + Clone + 'static, diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index 14a25dba6472..6ac09376292b 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -32,7 +32,7 @@ use reth_primitives::{ }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; -use reth_storage_api::{DBProvider, StorageChangeSetReader}; +use reth_storage_api::{DBProvider, StateCommitmentProvider, StorageChangeSetReader}; use reth_storage_errors::provider::ProviderResult; use reth_trie::HashedPostState; use reth_trie_db::StateCommitment; @@ -167,6 +167,10 @@ impl DatabaseProviderFactory for BlockchainProvider2 { } } +impl StateCommitmentProvider for BlockchainProvider2 { + type StateCommitment = N::StateCommitment; +} + impl StaticFileProviderFactory for BlockchainProvider2 { type Primitives = N::Primitives; diff --git a/crates/storage/provider/src/providers/consistent_view.rs b/crates/storage/provider/src/providers/consistent_view.rs index 4640f4603354..479537f120cc 100644 --- a/crates/storage/provider/src/providers/consistent_view.rs +++ b/crates/storage/provider/src/providers/consistent_view.rs @@ -2,11 +2,11 @@ use crate::{BlockNumReader, DatabaseProviderFactory, HeaderProvider}; use alloy_primitives::B256; use reth_errors::ProviderError; use reth_primitives::GotExpected; -use reth_storage_api::{BlockReader, DBProvider}; +use reth_storage_api::{BlockReader, DBProvider, StateCommitmentProvider}; use reth_storage_errors::provider::ProviderResult; use reth_trie::HashedPostState; -use reth_trie_db::DatabaseHashedPostState; +use reth_trie_db::{DatabaseHashedPostState, StateCommitment}; pub use reth_storage_errors::provider::ConsistentViewError; @@ -33,7 +33,7 @@ pub struct ConsistentDbView { impl ConsistentDbView where - Factory: DatabaseProviderFactory, + Factory: DatabaseProviderFactory + StateCommitmentProvider, { /// Creates new consistent database view. pub const fn new(factory: Factory, tip: Option) -> Self { @@ -59,7 +59,9 @@ where { Ok(HashedPostState::default()) } else { - Ok(HashedPostState::from_reverts(provider.tx_ref(), block_number + 1)?) + Ok(HashedPostState::from_reverts::< + ::KeyHasher, + >(provider.tx_ref(), block_number + 1)?) } } diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 13fbb77582e6..d620c92edab0 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -26,7 +26,7 @@ use reth_primitives::{ }; use reth_prune_types::{PruneCheckpoint, PruneModes, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; -use reth_storage_api::TryIntoHistoricalStateProvider; +use reth_storage_api::{StateCommitmentProvider, TryIntoHistoricalStateProvider}; use reth_storage_errors::provider::ProviderResult; use reth_trie::HashedPostState; use reth_trie_db::StateCommitment; @@ -221,6 +221,10 @@ impl DatabaseProviderFactory for ProviderFactory { } } +impl StateCommitmentProvider for ProviderFactory { + type StateCommitment = N::StateCommitment; +} + impl StaticFileProviderFactory for ProviderFactory { type Primitives = N::Primitives; diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index e4594d3ddda9..ddc52c721e91 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -135,7 +135,9 @@ impl<'b, Provider: DBProvider + BlockNumReader + StateCommitmentProvider> ); } - Ok(HashedPostState::from_reverts(self.tx(), self.block_number)?) + Ok(HashedPostState::from_reverts::< + ::KeyHasher, + >(self.tx(), self.block_number)?) } /// Retrieve revert hashed storage for this history provider and target address. diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index d2536b0e63a1..0df3a8988e82 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -28,8 +28,8 @@ use reth_primitives::{ }; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{ - DatabaseProviderFactory, HashedPostStateProvider, StageCheckpointReader, StateProofProvider, - StorageRootProvider, + DatabaseProviderFactory, HashedPostStateProvider, StageCheckpointReader, + StateCommitmentProvider, StateProofProvider, StorageRootProvider, }; use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult}; use reth_trie::{ @@ -164,6 +164,10 @@ impl NodeTypes for MockNode { type Storage = EthStorage; } +impl StateCommitmentProvider for MockEthProvider { + type StateCommitment = ::StateCommitment; +} + impl DatabaseProviderFactory for MockEthProvider { type DB = DatabaseMock; type Provider = DatabaseProvider; diff --git a/crates/trie/db/src/state.rs b/crates/trie/db/src/state.rs index 5f77b0b916c4..5aaf3ebe5b0f 100644 --- a/crates/trie/db/src/state.rs +++ b/crates/trie/db/src/state.rs @@ -1,5 +1,5 @@ use crate::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory, PrefixSetLoader}; -use alloy_primitives::{keccak256, Address, BlockNumber, B256, U256}; +use alloy_primitives::{Address, BlockNumber, B256, U256}; use reth_db::tables; use reth_db_api::{ cursor::DbCursorRO, @@ -10,7 +10,7 @@ use reth_execution_errors::StateRootError; use reth_storage_errors::db::DatabaseError; use reth_trie::{ hashed_cursor::HashedPostStateCursorFactory, trie_cursor::InMemoryTrieCursorFactory, - updates::TrieUpdates, HashedPostState, HashedStorage, KeccakKeyHasher, StateRoot, + updates::TrieUpdates, HashedPostState, HashedStorage, KeccakKeyHasher, KeyHasher, StateRoot, StateRootProgress, TrieInput, }; use std::{collections::HashMap, ops::RangeInclusive}; @@ -123,7 +123,7 @@ pub trait DatabaseStateRoot<'a, TX>: Sized { pub trait DatabaseHashedPostState: Sized { /// Initializes [`HashedPostState`] from reverts. Iterates over state reverts from the specified /// block up to the current tip and aggregates them into hashed state in reverse. - fn from_reverts(tx: &TX, from: BlockNumber) -> Result; + fn from_reverts(tx: &TX, from: BlockNumber) -> Result; } impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> @@ -217,7 +217,7 @@ impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> } impl DatabaseHashedPostState for HashedPostState { - fn from_reverts(tx: &TX, from: BlockNumber) -> Result { + fn from_reverts(tx: &TX, from: BlockNumber) -> Result { // Iterate over account changesets and record value before first occurring account change. let mut accounts = HashMap::new(); let mut account_changesets_cursor = tx.cursor_read::()?; @@ -238,19 +238,19 @@ impl DatabaseHashedPostState for HashedPostState { } let hashed_accounts = - accounts.into_iter().map(|(address, info)| (keccak256(address), info)).collect(); + accounts.into_iter().map(|(address, info)| (KH::hash_key(address), info)).collect(); let hashed_storages = storages .into_iter() .map(|(address, storage)| { ( - keccak256(address), + KH::hash_key(address), HashedStorage::from_iter( // The `wiped` flag indicates only whether previous storage entries // should be looked up in db or not. For reverts it's a noop since all // wiped changes had been written as storage reverts. false, - storage.into_iter().map(|(slot, value)| (keccak256(slot), value)), + storage.into_iter().map(|(slot, value)| (KH::hash_key(slot), value)), ), ) }) diff --git a/crates/trie/parallel/src/proof.rs b/crates/trie/parallel/src/proof.rs index dcb1a0231dd1..d0a31738ef9d 100644 --- a/crates/trie/parallel/src/proof.rs +++ b/crates/trie/parallel/src/proof.rs @@ -9,6 +9,7 @@ use reth_db::DatabaseError; use reth_execution_errors::StorageRootError; use reth_provider::{ providers::ConsistentDbView, BlockReader, DBProvider, DatabaseProviderFactory, ProviderError, + StateCommitmentProvider, }; use reth_trie::{ hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory}, @@ -53,7 +54,12 @@ impl ParallelProof { impl ParallelProof where - Factory: DatabaseProviderFactory + Clone + Send + Sync + 'static, + Factory: DatabaseProviderFactory + + StateCommitmentProvider + + Clone + + Send + + Sync + + 'static, { /// Generate a state multiproof according to specified targets. pub fn multiproof( diff --git a/crates/trie/parallel/src/root.rs b/crates/trie/parallel/src/root.rs index 7a316d8b15fb..8b75a1225c6d 100644 --- a/crates/trie/parallel/src/root.rs +++ b/crates/trie/parallel/src/root.rs @@ -7,6 +7,7 @@ use itertools::Itertools; use reth_execution_errors::StorageRootError; use reth_provider::{ providers::ConsistentDbView, BlockReader, DBProvider, DatabaseProviderFactory, ProviderError, + StateCommitmentProvider, }; use reth_trie::{ hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory}, @@ -57,7 +58,12 @@ impl ParallelStateRoot { impl ParallelStateRoot where - Factory: DatabaseProviderFactory + Clone + Send + Sync + 'static, + Factory: DatabaseProviderFactory + + StateCommitmentProvider + + Clone + + Send + + Sync + + 'static, { /// Calculate incremental state root in parallel. pub fn incremental_root(self) -> Result { From 8b24bc32efdaa6602edccd1971832fbdbe736a60 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 25 Nov 2024 15:31:07 +0800 Subject: [PATCH 08/38] feat: introduce HashedStorageProvider --- crates/chain-state/src/in_memory.rs | 11 +++++++-- crates/chain-state/src/memory_overlay.rs | 12 +++++++--- crates/revm/src/test_utils.rs | 10 ++++++-- crates/rpc/rpc-eth-types/src/cache/db.rs | 8 ++++++- .../src/providers/bundle_state_provider.rs | 19 +++++++++------ .../src/providers/state/historical.rs | 13 ++++++++++- .../provider/src/providers/state/latest.rs | 13 ++++++++++- .../provider/src/providers/state/macros.rs | 3 +++ .../storage/provider/src/test_utils/mock.rs | 8 ++++++- .../storage/provider/src/test_utils/noop.rs | 10 +++++++- crates/storage/storage-api/src/state.rs | 5 ++-- crates/storage/storage-api/src/storage.rs | 9 ++++++++ crates/trie/trie/src/state.rs | 23 ++++++++++++++----- 13 files changed, 117 insertions(+), 27 deletions(-) diff --git a/crates/chain-state/src/in_memory.rs b/crates/chain-state/src/in_memory.rs index d6e885ad2bc4..c88b86896553 100644 --- a/crates/chain-state/src/in_memory.rs +++ b/crates/chain-state/src/in_memory.rs @@ -919,10 +919,11 @@ mod tests { use reth_errors::ProviderResult; use reth_primitives::{Account, Bytecode, EthPrimitives, Receipt}; use reth_storage_api::{ - AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, - StateRootProvider, StorageRootProvider, + AccountReader, BlockHashReader, HashedPostStateProvider, HashedStorageProvider, + StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, }; use reth_trie::{AccountProof, HashedStorage, MultiProof, StorageProof, TrieInput}; + use revm::db::BundleAccount; fn create_mock_state( test_block_builder: &mut TestBlockBuilder, @@ -1021,6 +1022,12 @@ mod tests { } } + impl HashedStorageProvider for MockStateProvider { + fn hashed_storage(&self, _account: &BundleAccount) -> HashedStorage { + HashedStorage::default() + } + } + impl StorageRootProvider for MockStateProvider { fn storage_root( &self, diff --git a/crates/chain-state/src/memory_overlay.rs b/crates/chain-state/src/memory_overlay.rs index 92ae409991da..4c160b037ee8 100644 --- a/crates/chain-state/src/memory_overlay.rs +++ b/crates/chain-state/src/memory_overlay.rs @@ -7,13 +7,13 @@ use alloy_primitives::{ use reth_errors::ProviderResult; use reth_primitives::{Account, Bytecode, NodePrimitives}; use reth_storage_api::{ - AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, - StateRootProvider, StorageRootProvider, + AccountReader, BlockHashReader, HashedPostStateProvider, HashedStorageProvider, + StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, }; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput, }; -use revm::db::BundleState; +use revm::db::{BundleAccount, BundleState}; use std::sync::OnceLock; /// A state provider that stores references to in-memory blocks along with their state as well as a @@ -209,6 +209,12 @@ macro_rules! impl_state_provider { } } + impl $($tokens)* HashedStorageProvider for $type { + fn hashed_storage(&self, account: &BundleAccount) -> HashedStorage { + self.historical.hashed_storage(account) + } + } + impl $($tokens)* StateProvider for $type { fn storage( &self, diff --git a/crates/revm/src/test_utils.rs b/crates/revm/src/test_utils.rs index 23705dda0e1d..5057ff828220 100644 --- a/crates/revm/src/test_utils.rs +++ b/crates/revm/src/test_utils.rs @@ -6,8 +6,8 @@ use alloy_primitives::{ }; use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ - AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, - StateRootProvider, StorageRootProvider, + AccountReader, BlockHashReader, HashedPostStateProvider, HashedStorageProvider, + StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -147,6 +147,12 @@ impl HashedPostStateProvider for StateProviderTest { } } +impl HashedStorageProvider for StateProviderTest { + fn hashed_storage(&self, account: &revm::db::BundleAccount) -> HashedStorage { + HashedStorage::from_bundle_account::(account) + } +} + impl StateProvider for StateProviderTest { fn storage( &self, diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index de61f308fd6c..ce99a0af8a60 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -8,7 +8,7 @@ use alloy_primitives::{ }; use reth_errors::ProviderResult; use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef}; -use reth_storage_api::{HashedPostStateProvider, StateProvider}; +use reth_storage_api::{HashedPostStateProvider, HashedStorageProvider, StateProvider}; use reth_trie::HashedStorage; use revm::Database; @@ -139,6 +139,12 @@ impl HashedPostStateProvider for StateProviderTraitObjWrapper<'_> { } } +impl HashedStorageProvider for StateProviderTraitObjWrapper<'_> { + fn hashed_storage(&self, account: &revm::db::BundleAccount) -> HashedStorage { + self.0.hashed_storage(account) + } +} + impl StateProvider for StateProviderTraitObjWrapper<'_> { fn storage( &self, diff --git a/crates/storage/provider/src/providers/bundle_state_provider.rs b/crates/storage/provider/src/providers/bundle_state_provider.rs index a7a102bc5c41..8db8f3753378 100644 --- a/crates/storage/provider/src/providers/bundle_state_provider.rs +++ b/crates/storage/provider/src/providers/bundle_state_provider.rs @@ -6,7 +6,9 @@ use alloy_primitives::{ Address, BlockNumber, Bytes, B256, }; use reth_primitives::{Account, Bytecode}; -use reth_storage_api::{HashedPostStateProvider, StateProofProvider, StorageRootProvider}; +use reth_storage_api::{ + HashedPostStateProvider, HashedStorageProvider, StateProofProvider, StorageRootProvider, +}; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput, @@ -37,12 +39,7 @@ impl BundleStateProvider let bundle_state = self.block_execution_data_provider.execution_outcome().state(); bundle_state .account(&address) - .map(|account| { - HashedStorage::from_plain_storage( - account.status, - account.storage.iter().map(|(slot, value)| (slot, &value.present_value)), - ) - }) + .map(|account| self.state_provider.hashed_storage(account)) .unwrap_or_default() } } @@ -183,6 +180,14 @@ impl HashedPostStateProvider } } +impl HashedStorageProvider + for BundleStateProvider +{ + fn hashed_storage(&self, account: &revm::db::BundleAccount) -> HashedStorage { + self.state_provider.hashed_storage(account) + } +} + impl StateProvider for BundleStateProvider { fn storage( &self, diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index ddc52c721e91..a06246176f01 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -16,7 +16,8 @@ use reth_db_api::{ }; use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ - BlockNumReader, DBProvider, StateCommitmentProvider, StateProofProvider, StorageRootProvider, + BlockNumReader, DBProvider, HashedStorageProvider, StateCommitmentProvider, StateProofProvider, + StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -393,6 +394,16 @@ impl HashedPostStateProvider } } +impl HashedStorageProvider + for HistoricalStateProviderRef<'_, Provider> +{ + fn hashed_storage(&self, account: &revm::db::BundleAccount) -> HashedStorage { + HashedStorage::from_bundle_account::< + ::KeyHasher, + >(account) + } +} + impl StateProvider for HistoricalStateProviderRef<'_, Provider> { diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 825817357ca9..e920ac5a7c73 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -10,7 +10,8 @@ use reth_db::tables; use reth_db_api::{cursor::DbDupCursorRO, transaction::DbTx}; use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ - DBProvider, StateCommitmentProvider, StateProofProvider, StorageRootProvider, + DBProvider, HashedStorageProvider, StateCommitmentProvider, StateProofProvider, + StorageRootProvider, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{ @@ -156,6 +157,16 @@ impl HashedPostStateProvider } } +impl HashedStorageProvider + for LatestStateProviderRef<'_, Provider> +{ + fn hashed_storage<'a>(&self, account: &revm::db::BundleAccount) -> HashedStorage { + HashedStorage::from_bundle_account::< + ::KeyHasher, + >(account) + } +} + impl StateProvider for LatestStateProviderRef<'_, Provider> { diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index 860407923573..d83dc3b292da 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -59,6 +59,9 @@ macro_rules! delegate_provider_impls { HashedPostStateProvider $(where [$($generics)*])? { fn hashed_post_state(&self, bundle_state: &revm::db::BundleState) -> reth_trie::HashedPostState; } + HashedStorageProvider $(where [$($generics)*])? { + fn hashed_storage(&self, bundle_state: &revm::db::BundleAccount) -> reth_trie::HashedStorage; + } ); } } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 0df3a8988e82..f4412387e2cf 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -28,7 +28,7 @@ use reth_primitives::{ }; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{ - DatabaseProviderFactory, HashedPostStateProvider, StageCheckpointReader, + DatabaseProviderFactory, HashedPostStateProvider, HashedStorageProvider, StageCheckpointReader, StateCommitmentProvider, StateProofProvider, StorageRootProvider, }; use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult}; @@ -677,6 +677,12 @@ impl HashedPostStateProvider for MockEthProvider { } } +impl HashedStorageProvider for MockEthProvider { + fn hashed_storage(&self, _account: &revm::db::BundleAccount) -> HashedStorage { + HashedStorage::default() + } +} + impl StateProvider for MockEthProvider { fn storage( &self, diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index f97d403cb1f7..2269b1cb8fae 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -27,7 +27,9 @@ use reth_primitives::{ }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; -use reth_storage_api::{HashedPostStateProvider, StateProofProvider, StorageRootProvider}; +use reth_storage_api::{ + HashedPostStateProvider, HashedStorageProvider, StateProofProvider, StorageRootProvider, +}; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput, @@ -405,6 +407,12 @@ impl HashedPostStateProvider for NoopProvider { } } +impl HashedStorageProvider for NoopProvider { + fn hashed_storage(&self, _account: &revm::db::BundleAccount) -> HashedStorage { + HashedStorage::default() + } +} + impl StateProvider for NoopProvider { fn storage( &self, diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 9b0134607c34..2f52a42d4519 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -1,6 +1,6 @@ use super::{ - AccountReader, BlockHashReader, BlockIdReader, StateProofProvider, StateRootProvider, - StorageRootProvider, + AccountReader, BlockHashReader, BlockIdReader, HashedStorageProvider, StateProofProvider, + StateRootProvider, StorageRootProvider, }; use alloy_consensus::constants::KECCAK_EMPTY; use alloy_eips::{BlockId, BlockNumberOrTag}; @@ -24,6 +24,7 @@ pub trait StateProvider: + StorageRootProvider + StateProofProvider + HashedPostStateProvider + + HashedStorageProvider + Send + Sync { diff --git a/crates/storage/storage-api/src/storage.rs b/crates/storage/storage-api/src/storage.rs index e1443347e4bb..3e3fd92eaefe 100644 --- a/crates/storage/storage-api/src/storage.rs +++ b/crates/storage/storage-api/src/storage.rs @@ -2,6 +2,8 @@ use alloy_primitives::{Address, BlockNumber, B256}; use reth_db_api::models::BlockNumberAddress; use reth_primitives::StorageEntry; use reth_storage_errors::provider::ProviderResult; +use reth_trie::HashedStorage; +use revm::db::BundleAccount; use std::{ collections::{BTreeMap, BTreeSet}, ops::RangeInclusive, @@ -41,3 +43,10 @@ pub trait StorageChangeSetReader: Send + Sync { block_number: BlockNumber, ) -> ProviderResult>; } + +/// Provider of [`HashedStorage`] +#[auto_impl::auto_impl(&, Arc, Box)] +pub trait HashedStorageProvider: Send + Sync { + /// Construct [`HashedStorage`] from the provided [`BundleAccount`]. + fn hashed_storage(&self, account: &BundleAccount) -> HashedStorage; +} diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index 66a6af5a3a70..6307358163f1 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -2,7 +2,7 @@ use crate::{ prefix_set::{PrefixSetMut, TriePrefixSetsMut}, Nibbles, }; -use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_primitives::{Address, B256, U256}; use itertools::Itertools; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use reth_primitives::Account; @@ -34,7 +34,7 @@ impl HashedPostState { .map(|(address, account)| { let hashed_address = KH::hash_key(address); let hashed_account = account.info.clone().map(Into::into); - let hashed_storage = HashedStorage::from_plain_storage( + let hashed_storage = HashedStorage::from_plain_storage::( account.status, account.storage.iter().map(|(slot, value)| (slot, &value.present_value)), ); @@ -61,7 +61,7 @@ impl HashedPostState { .map(|(address, account)| { let hashed_address = KH::hash_key(address); let hashed_account = account.account.as_ref().map(|a| a.info.clone().into()); - let hashed_storage = HashedStorage::from_plain_storage( + let hashed_storage = HashedStorage::from_plain_storage::( account.status, account.account.as_ref().map(|a| a.storage.iter()).into_iter().flatten(), ); @@ -219,14 +219,25 @@ impl HashedStorage { Self { wiped, storage: HashMap::from_iter(iter) } } + /// Create a new hashed storage from the provided [`BundleAccount`] + /// + /// This function will use the present value of the storage slots in the account to create the + /// hashed storage. + pub fn from_bundle_account(account: &BundleAccount) -> Self { + Self::from_plain_storage::( + account.status, + account.storage.iter().map(|(slot, value)| (slot, &value.present_value)), + ) + } + /// Create new hashed storage from account status and plain storage. - pub fn from_plain_storage<'a>( + pub fn from_plain_storage<'a, KH: KeyHasher>( status: AccountStatus, storage: impl IntoIterator, ) -> Self { Self::from_iter( status.was_destroyed(), - storage.into_iter().map(|(key, value)| (keccak256(B256::from(*key)), *value)), + storage.into_iter().map(|(key, value)| (KH::hash_key(B256::from(*key)), *value)), ) } @@ -349,7 +360,7 @@ impl HashedStorageSorted { #[cfg(test)] mod tests { use super::*; - use alloy_primitives::Bytes; + use alloy_primitives::{keccak256, Bytes}; use reth_trie_common::KeccakKeyHasher; use revm::{ db::{ From 0e79635cc265ab6788cdf4af9dab557970cedfda Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 25 Nov 2024 15:34:24 +0800 Subject: [PATCH 09/38] lint: revm/test-utils feature propogation --- crates/chain-state/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/chain-state/Cargo.toml b/crates/chain-state/Cargo.toml index 7d6d503a7060..7f05d8475cc0 100644 --- a/crates/chain-state/Cargo.toml +++ b/crates/chain-state/Cargo.toml @@ -52,7 +52,6 @@ alloy-signer.workspace = true alloy-signer-local.workspace = true alloy-consensus.workspace = true rand.workspace = true -revm.workspace = true [features] test-utils = [ @@ -62,4 +61,5 @@ test-utils = [ "reth-chainspec/test-utils", "reth-primitives/test-utils", "reth-trie/test-utils", + "revm/test-utils", ] From 593587e691763a71cf678e3501e0092caaaef7ed Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 25 Nov 2024 15:40:50 +0800 Subject: [PATCH 10/38] fix: add Send + Sync bound on introduced storage state api methods --- crates/storage/storage-api/src/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 9b0134607c34..dc53319f4c5f 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -86,14 +86,14 @@ pub trait StateProvider: } /// Trait implemented for database providers that can provide the [`StateCommitment`] type. -pub trait StateCommitmentProvider { +pub trait StateCommitmentProvider: Send + Sync { /// The [`StateCommitment`] type that can be used to perform state commitment operations. type StateCommitment: StateCommitment; } /// Trait that provides the hashed state from various sources. #[auto_impl(&, Arc, Box)] -pub trait HashedPostStateProvider { +pub trait HashedPostStateProvider: Send + Sync { /// Returns the `HashedPostState` of the provided [`BundleState`]. fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState; } From dbeb344bc0c4ea4395c4342e3d905d944553fc45 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 25 Nov 2024 17:19:37 +0800 Subject: [PATCH 11/38] feat: introduce KeyHasherProvider --- crates/chain-state/src/in_memory.rs | 14 ++++++- crates/chain-state/src/memory_overlay.rs | 13 ++++-- .../engine/invalid-block-hooks/src/witness.rs | 11 +++-- crates/revm/src/test_utils.rs | 10 ++++- crates/rpc/rpc-eth-types/src/cache/db.rs | 10 ++++- crates/rpc/rpc-eth-types/src/simulate.rs | 8 ++-- .../stages/src/stages/hashing_account.rs | 25 +++++++++--- .../stages/src/stages/hashing_storage.rs | 25 +++++++++--- .../src/providers/bundle_state_provider.rs | 11 ++++- .../src/providers/database/provider.rs | 40 ++++++++++++------- .../src/providers/state/historical.rs | 17 ++++++-- .../provider/src/providers/state/latest.rs | 13 ++++-- .../provider/src/providers/state/macros.rs | 3 ++ .../storage/provider/src/test_utils/mock.rs | 10 ++++- .../storage/provider/src/test_utils/noop.rs | 9 ++++- crates/storage/storage-api/src/state.rs | 8 ++++ 16 files changed, 172 insertions(+), 55 deletions(-) diff --git a/crates/chain-state/src/in_memory.rs b/crates/chain-state/src/in_memory.rs index c88b86896553..95c327980b04 100644 --- a/crates/chain-state/src/in_memory.rs +++ b/crates/chain-state/src/in_memory.rs @@ -920,9 +920,13 @@ mod tests { use reth_primitives::{Account, Bytecode, EthPrimitives, Receipt}; use reth_storage_api::{ AccountReader, BlockHashReader, HashedPostStateProvider, HashedStorageProvider, - StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, + KeyHasherProvider, StateProofProvider, StateProvider, StateRootProvider, + StorageRootProvider, + }; + use reth_trie::{ + AccountProof, HashedStorage, KeccakKeyHasher, KeyHasher, MultiProof, StorageProof, + TrieInput, }; - use reth_trie::{AccountProof, HashedStorage, MultiProof, StorageProof, TrieInput}; use revm::db::BundleAccount; fn create_mock_state( @@ -1028,6 +1032,12 @@ mod tests { } } + impl KeyHasherProvider for MockStateProvider { + fn hash_key(&self, bytes: &[u8]) -> B256 { + KeccakKeyHasher::hash_key(bytes) + } + } + impl StorageRootProvider for MockStateProvider { fn storage_root( &self, diff --git a/crates/chain-state/src/memory_overlay.rs b/crates/chain-state/src/memory_overlay.rs index 4c160b037ee8..05e761f99980 100644 --- a/crates/chain-state/src/memory_overlay.rs +++ b/crates/chain-state/src/memory_overlay.rs @@ -1,6 +1,5 @@ use super::ExecutedBlock; use alloy_primitives::{ - keccak256, map::{HashMap, HashSet}, Address, BlockNumber, Bytes, StorageKey, StorageValue, B256, }; @@ -8,7 +7,7 @@ use reth_errors::ProviderResult; use reth_primitives::{Account, Bytecode, NodePrimitives}; use reth_storage_api::{ AccountReader, BlockHashReader, HashedPostStateProvider, HashedStorageProvider, - StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, + KeyHasherProvider, StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, }; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput, @@ -150,7 +149,7 @@ macro_rules! impl_state_provider { fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult { let state = &self.trie_state().state; let mut hashed_storage = - state.storages.get(&keccak256(address)).cloned().unwrap_or_default(); + state.storages.get(&self.hash_key(address.as_ref())).cloned().unwrap_or_default(); hashed_storage.extend(&storage); self.historical.storage_root(address, hashed_storage) } @@ -164,7 +163,7 @@ macro_rules! impl_state_provider { ) -> ProviderResult { let state = &self.trie_state().state; let mut hashed_storage = - state.storages.get(&keccak256(address)).cloned().unwrap_or_default(); + state.storages.get(&self.hash_key(address.as_ref())).cloned().unwrap_or_default(); hashed_storage.extend(&storage); self.historical.storage_proof(address, slot, hashed_storage) } @@ -215,6 +214,12 @@ macro_rules! impl_state_provider { } } + impl $($tokens)* KeyHasherProvider for $type { + fn hash_key(&self, bytes: &[u8]) -> B256 { + self.historical.hash_key(bytes) + } + } + impl $($tokens)* StateProvider for $type { fn storage( &self, diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 0daf3a64911e..acb3351afa6f 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, fmt::Debug, fs::File, io::Write, path::PathBuf}; use alloy_consensus::Header; -use alloy_primitives::{keccak256, B256, U256}; +use alloy_primitives::{B256, U256}; use alloy_rpc_types_debug::ExecutionWitness; use eyre::OptionExt; use pretty_assertions::Comparison; @@ -68,10 +68,9 @@ where // TODO(alexey): unify with `DebugApi::debug_execution_witness` // Setup database. + let provider = self.provider.state_by_block_hash(parent_header.hash())?; let mut db = StateBuilder::new() - .with_database(StateProviderDatabase::new( - self.provider.state_by_block_hash(parent_header.hash())?, - )) + .with_database(StateProviderDatabase::new(&provider)) .with_bundle_update() .build(); @@ -132,7 +131,7 @@ where // referenced accounts + storage slots. let mut hashed_state = db.database.hashed_post_state(&bundle_state); for (address, account) in db.cache.accounts { - let hashed_address = keccak256(address); + let hashed_address = provider.hash_key(address.as_ref()); hashed_state .accounts .insert(hashed_address, account.account.as_ref().map(|a| a.info.clone().into())); @@ -147,7 +146,7 @@ where for (slot, value) in account.storage { let slot = B256::from(slot); - let hashed_slot = keccak256(slot); + let hashed_slot = provider.hash_key(slot.as_ref()); storage.storage.insert(hashed_slot, value); state_preimages.insert(hashed_slot, alloy_rlp::encode(slot).into()); diff --git a/crates/revm/src/test_utils.rs b/crates/revm/src/test_utils.rs index 5057ff828220..6beb61c2cd59 100644 --- a/crates/revm/src/test_utils.rs +++ b/crates/revm/src/test_utils.rs @@ -7,11 +7,11 @@ use alloy_primitives::{ use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ AccountReader, BlockHashReader, HashedPostStateProvider, HashedStorageProvider, - StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, + KeyHasherProvider, StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ - updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, KeccakKeyHasher, + updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, KeccakKeyHasher, KeyHasher, MultiProof, StorageProof, TrieInput, }; @@ -153,6 +153,12 @@ impl HashedStorageProvider for StateProviderTest { } } +impl KeyHasherProvider for StateProviderTest { + fn hash_key(&self, bytes: &[u8]) -> B256 { + KeccakKeyHasher::hash_key(bytes) + } +} + impl StateProvider for StateProviderTest { fn storage( &self, diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index ce99a0af8a60..34da3b562c61 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -8,7 +8,9 @@ use alloy_primitives::{ }; use reth_errors::ProviderResult; use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef}; -use reth_storage_api::{HashedPostStateProvider, HashedStorageProvider, StateProvider}; +use reth_storage_api::{ + HashedPostStateProvider, HashedStorageProvider, KeyHasherProvider, StateProvider, +}; use reth_trie::HashedStorage; use revm::Database; @@ -145,6 +147,12 @@ impl HashedStorageProvider for StateProviderTraitObjWrapper<'_> { } } +impl KeyHasherProvider for StateProviderTraitObjWrapper<'_> { + fn hash_key(&self, bytes: &[u8]) -> B256 { + self.0.hash_key(bytes) + } +} + impl StateProvider for StateProviderTraitObjWrapper<'_> { fn storage( &self, diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 5a0daa1b42f0..58a695b7533e 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -15,10 +15,10 @@ use reth_primitives::{ use reth_revm::database::StateProviderDatabase; use reth_rpc_server_types::result::rpc_err; use reth_rpc_types_compat::{block::from_block, TransactionCompat}; -use reth_storage_api::StateRootProvider; +use reth_storage_api::{KeyHasherProvider, StateRootProvider}; use reth_trie::{HashedPostState, HashedStorage}; use revm::{db::CacheDB, Database}; -use revm_primitives::{keccak256, Address, BlockEnv, Bytes, ExecutionResult, TxKind, B256, U256}; +use revm_primitives::{Address, BlockEnv, Bytes, ExecutionResult, TxKind, B256, U256}; use crate::{ cache::db::StateProviderTraitObjWrapper, @@ -231,7 +231,7 @@ pub fn build_block>( let mut hashed_state = HashedPostState::default(); for (address, account) in &db.accounts { - let hashed_address = keccak256(address); + let hashed_address = db.db.hash_key(address.as_ref()); hashed_state.accounts.insert(hashed_address, Some(account.info.clone().into())); let storage = hashed_state @@ -241,7 +241,7 @@ pub fn build_block>( for (slot, value) in &account.storage { let slot = B256::from(*slot); - let hashed_slot = keccak256(slot); + let hashed_slot = db.db.hash_key(slot.as_ref()); storage.storage.insert(hashed_slot, *value); } } diff --git a/crates/stages/stages/src/stages/hashing_account.rs b/crates/stages/stages/src/stages/hashing_account.rs index e6b1e548455f..581b98b3a683 100644 --- a/crates/stages/stages/src/stages/hashing_account.rs +++ b/crates/stages/stages/src/stages/hashing_account.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{keccak256, B256}; +use alloy_primitives::B256; use itertools::Itertools; use reth_config::config::{EtlConfig, HashingConfig}; use reth_db::{tables, RawKey, RawTable, RawValue}; @@ -8,12 +8,16 @@ use reth_db_api::{ }; use reth_etl::Collector; use reth_primitives::Account; -use reth_provider::{AccountExtReader, DBProvider, HashingWriter, StatsReader}; +use reth_provider::{ + AccountExtReader, DBProvider, HashingWriter, StateCommitmentProvider, StatsReader, +}; use reth_stages_api::{ AccountHashingCheckpoint, EntitiesCheckpoint, ExecInput, ExecOutput, Stage, StageCheckpoint, StageError, StageId, UnwindInput, UnwindOutput, }; use reth_storage_errors::provider::ProviderResult; +use reth_trie::KeyHasher; +use reth_trie_db::StateCommitment; use std::{ fmt::Debug, ops::{Range, RangeInclusive}, @@ -131,7 +135,11 @@ impl Default for AccountHashingStage { impl Stage for AccountHashingStage where - Provider: DBProvider + HashingWriter + AccountExtReader + StatsReader, + Provider: DBProvider + + HashingWriter + + AccountExtReader + + StatsReader + + StateCommitmentProvider, { /// Return the id of the stage fn id(&self) -> StageId { @@ -172,7 +180,14 @@ where rayon::spawn(move || { for (address, account) in chunk { let address = address.key().unwrap(); - let _ = tx.send((RawKey::new(keccak256(address)), account)); + let _ = tx.send(( + RawKey::new( + <::KeyHasher as KeyHasher>::hash_key( + address, + ), + ), + account, + )); } }); @@ -299,7 +314,7 @@ mod tests { stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, UnwindStageTestRunner, }; - use alloy_primitives::U256; + use alloy_primitives::{keccak256, U256}; use assert_matches::assert_matches; use reth_primitives::Account; use reth_provider::providers::StaticFileWriter; diff --git a/crates/stages/stages/src/stages/hashing_storage.rs b/crates/stages/stages/src/stages/hashing_storage.rs index dcabbe83ee64..3208113140ee 100644 --- a/crates/stages/stages/src/stages/hashing_storage.rs +++ b/crates/stages/stages/src/stages/hashing_storage.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{bytes::BufMut, keccak256, B256}; +use alloy_primitives::{bytes::BufMut, B256}; use itertools::Itertools; use reth_config::config::{EtlConfig, HashingConfig}; use reth_db::tables; @@ -10,12 +10,16 @@ use reth_db_api::{ }; use reth_etl::Collector; use reth_primitives::StorageEntry; -use reth_provider::{DBProvider, HashingWriter, StatsReader, StorageReader}; +use reth_provider::{ + DBProvider, HashingWriter, StateCommitmentProvider, StatsReader, StorageReader, +}; use reth_stages_api::{ EntitiesCheckpoint, ExecInput, ExecOutput, Stage, StageCheckpoint, StageError, StageId, StorageHashingCheckpoint, UnwindInput, UnwindOutput, }; use reth_storage_errors::provider::ProviderResult; +use reth_trie::KeyHasher; +use reth_trie_db::StateCommitment; use std::{ fmt::Debug, sync::mpsc::{self, Receiver}, @@ -64,7 +68,11 @@ impl Default for StorageHashingStage { impl Stage for StorageHashingStage where - Provider: DBProvider + StorageReader + HashingWriter + StatsReader, + Provider: DBProvider + + StorageReader + + HashingWriter + + StatsReader + + StateCommitmentProvider, { /// Return the id of the stage fn id(&self) -> StageId { @@ -103,8 +111,13 @@ where rayon::spawn(move || { for (address, slot) in chunk { let mut addr_key = Vec::with_capacity(64); - addr_key.put_slice(keccak256(address).as_slice()); - addr_key.put_slice(keccak256(slot.key).as_slice()); + addr_key.put_slice( + <::KeyHasher as KeyHasher>::hash_key( + address, + ) + .as_slice(), + ); + addr_key.put_slice(<::KeyHasher as KeyHasher>::hash_key(slot.key).as_slice()); let _ = tx.send((addr_key, CompactU256::from(slot.value))); } }); @@ -212,7 +225,7 @@ mod tests { stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestStageDB, UnwindStageTestRunner, }; - use alloy_primitives::{Address, U256}; + use alloy_primitives::{keccak256, Address, U256}; use assert_matches::assert_matches; use rand::Rng; use reth_db_api::{ diff --git a/crates/storage/provider/src/providers/bundle_state_provider.rs b/crates/storage/provider/src/providers/bundle_state_provider.rs index 8db8f3753378..1b1bc12e8cd7 100644 --- a/crates/storage/provider/src/providers/bundle_state_provider.rs +++ b/crates/storage/provider/src/providers/bundle_state_provider.rs @@ -7,7 +7,8 @@ use alloy_primitives::{ }; use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ - HashedPostStateProvider, HashedStorageProvider, StateProofProvider, StorageRootProvider, + HashedPostStateProvider, HashedStorageProvider, KeyHasherProvider, StateProofProvider, + StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -188,6 +189,14 @@ impl HashedStorageProvider } } +impl KeyHasherProvider + for BundleStateProvider +{ + fn hash_key(&self, bytes: &[u8]) -> B256 { + self.state_provider.hash_key(bytes) + } +} + impl StateProvider for BundleStateProvider { fn storage( &self, diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index cdf07361389b..9d84a2dd5ddd 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -14,12 +14,12 @@ use crate::{ BlockReader, BlockWriter, BundleStateInit, ChainStateBlockReader, ChainStateBlockWriter, DBProvider, EvmEnvProvider, HashingWriter, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HistoricalStateProvider, HistoricalStateProviderRef, HistoryWriter, - LatestStateProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, - PruneCheckpointReader, PruneCheckpointWriter, RevertsInit, StageCheckpointReader, - StateChangeWriter, StateCommitmentProvider, StateProviderBox, StateReader, StateWriter, - StaticFileProviderFactory, StatsReader, StorageLocation, StorageReader, StorageTrieWriter, - TransactionVariant, TransactionsProvider, TransactionsProviderExt, TrieWriter, - WithdrawalsProvider, + KeyHasherProvider, LatestStateProvider, LatestStateProviderRef, OriginalValuesKnown, + ProviderError, PruneCheckpointReader, PruneCheckpointWriter, RevertsInit, + StageCheckpointReader, StateChangeWriter, StateCommitmentProvider, StateProviderBox, + StateReader, StateWriter, StaticFileProviderFactory, StatsReader, StorageLocation, + StorageReader, StorageTrieWriter, TransactionVariant, TransactionsProvider, + TransactionsProviderExt, TrieWriter, WithdrawalsProvider, }; use alloy_consensus::Header; use alloy_eips::{ @@ -61,9 +61,9 @@ use reth_storage_errors::provider::{ProviderResult, RootMismatch}; use reth_trie::{ prefix_set::{PrefixSet, PrefixSetMut, TriePrefixSets}, updates::{StorageTrieUpdates, TrieUpdates}, - HashedPostStateSorted, Nibbles, StateRoot, StoredNibbles, + HashedPostStateSorted, KeyHasher, Nibbles, StateRoot, StoredNibbles, }; -use reth_trie_db::{DatabaseStateRoot, DatabaseStorageTrieCursor}; +use reth_trie_db::{DatabaseStateRoot, DatabaseStorageTrieCursor, StateCommitment}; use revm::{ db::states::{PlainStateReverts, PlainStorageChangeset, PlainStorageRevert, StateChangeset}, primitives::{BlockEnv, CfgEnvWithHandlerCfg}, @@ -379,6 +379,12 @@ impl StateCommitmentProvider for DatabaseProvi type StateCommitment = N::StateCommitment; } +impl KeyHasherProvider for DatabaseProvider { + fn hash_key(&self, bytes: &[u8]) -> B256 { + <::KeyHasher as KeyHasher>::hash_key(bytes) + } +} + impl DatabaseProvider { // TODO: uncomment below, once `reth debug_cmd` has been feature gated with dev. // #[cfg(any(test, feature = "test-utils"))] @@ -2348,7 +2354,7 @@ impl HashingWriter for DatabaseProvi // changes are applied in the correct order. let hashed_accounts = changesets .into_iter() - .map(|(_, e)| (keccak256(e.address), e.info)) + .map(|(_, e)| (self.hash_key(e.address.as_ref()), e.info)) .collect::>() .into_iter() .rev() @@ -2384,8 +2390,10 @@ impl HashingWriter for DatabaseProvi changesets: impl IntoIterator)>, ) -> ProviderResult>> { let mut hashed_accounts_cursor = self.tx.cursor_write::()?; - let hashed_accounts = - changesets.into_iter().map(|(ad, ac)| (keccak256(ad), ac)).collect::>(); + let hashed_accounts = changesets + .into_iter() + .map(|(ad, ac)| (self.hash_key(ad.as_ref()), ac)) + .collect::>(); for (hashed_address, account) in &hashed_accounts { if let Some(account) = account { hashed_accounts_cursor.upsert(*hashed_address, *account)?; @@ -2404,7 +2412,11 @@ impl HashingWriter for DatabaseProvi let mut hashed_storages = changesets .into_iter() .map(|(BlockNumberAddress((_, address)), storage_entry)| { - (keccak256(address), keccak256(storage_entry.key), storage_entry.value) + ( + self.hash_key(address.as_ref()), + self.hash_key(storage_entry.key.as_ref()), + storage_entry.value, + ) }) .collect::>(); hashed_storages.sort_by_key(|(ha, hk, _)| (*ha, *hk)); @@ -2451,10 +2463,10 @@ impl HashingWriter for DatabaseProvi let hashed_storages = storages.into_iter().fold(BTreeMap::new(), |mut map, (address, storage)| { let storage = storage.into_iter().fold(BTreeMap::new(), |mut map, entry| { - map.insert(keccak256(entry.key), entry.value); + map.insert(self.hash_key(entry.key.as_ref()), entry.value); map }); - map.insert(keccak256(address), storage); + map.insert(self.hash_key(address.as_ref()), storage); map }); diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index a06246176f01..659ec330d1ad 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -16,15 +16,16 @@ use reth_db_api::{ }; use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ - BlockNumReader, DBProvider, HashedStorageProvider, StateCommitmentProvider, StateProofProvider, - StorageRootProvider, + BlockNumReader, DBProvider, HashedStorageProvider, KeyHasherProvider, StateCommitmentProvider, + StateProofProvider, StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ proof::{Proof, StorageProof}, updates::TrieUpdates, witness::TrieWitness, - AccountProof, HashedPostState, HashedStorage, MultiProof, StateRoot, StorageRoot, TrieInput, + AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StateRoot, StorageRoot, + TrieInput, }; use reth_trie_db::{ DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot, @@ -394,7 +395,7 @@ impl HashedPostStateProvider } } -impl HashedStorageProvider +impl HashedStorageProvider for HistoricalStateProviderRef<'_, Provider> { fn hashed_storage(&self, account: &revm::db::BundleAccount) -> HashedStorage { @@ -404,6 +405,14 @@ impl HashedStorageProvider } } +impl KeyHasherProvider + for HistoricalStateProviderRef<'_, Provider> +{ + fn hash_key(&self, bytes: &[u8]) -> B256 { + <::KeyHasher as KeyHasher>::hash_key(bytes) + } +} + impl StateProvider for HistoricalStateProviderRef<'_, Provider> { diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index e920ac5a7c73..4bc0318a6123 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -10,15 +10,16 @@ use reth_db::tables; use reth_db_api::{cursor::DbDupCursorRO, transaction::DbTx}; use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ - DBProvider, HashedStorageProvider, StateCommitmentProvider, StateProofProvider, - StorageRootProvider, + DBProvider, HashedStorageProvider, KeyHasherProvider, StateCommitmentProvider, + StateProofProvider, StorageRootProvider, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{ proof::{Proof, StorageProof}, updates::TrieUpdates, witness::TrieWitness, - AccountProof, HashedPostState, HashedStorage, MultiProof, StateRoot, StorageRoot, TrieInput, + AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StateRoot, StorageRoot, + TrieInput, }; use reth_trie_db::{ DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot, @@ -167,6 +168,12 @@ impl HashedStorageProvider } } +impl KeyHasherProvider for LatestStateProviderRef<'_, Provider> { + fn hash_key(&self, bytes: &[u8]) -> B256 { + <::KeyHasher as KeyHasher>::hash_key(bytes) + } +} + impl StateProvider for LatestStateProviderRef<'_, Provider> { diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index d83dc3b292da..82ff634b1d9d 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -62,6 +62,9 @@ macro_rules! delegate_provider_impls { HashedStorageProvider $(where [$($generics)*])? { fn hashed_storage(&self, bundle_state: &revm::db::BundleAccount) -> reth_trie::HashedStorage; } + KeyHasherProvider $(where [$($generics)*])? { + fn hash_key(&self, bytes: &[u8]) -> alloy_primitives::B256; + } ); } } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index f4412387e2cf..a52da1338699 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -28,8 +28,8 @@ use reth_primitives::{ }; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{ - DatabaseProviderFactory, HashedPostStateProvider, HashedStorageProvider, StageCheckpointReader, - StateCommitmentProvider, StateProofProvider, StorageRootProvider, + DatabaseProviderFactory, HashedPostStateProvider, HashedStorageProvider, KeyHasherProvider, + StageCheckpointReader, StateCommitmentProvider, StateProofProvider, StorageRootProvider, }; use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult}; use reth_trie::{ @@ -683,6 +683,12 @@ impl HashedStorageProvider for MockEthProvider { } } +impl KeyHasherProvider for MockEthProvider { + fn hash_key(&self, _bytes: &[u8]) -> B256 { + B256::default() + } +} + impl StateProvider for MockEthProvider { fn storage( &self, diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 2269b1cb8fae..4ad60b864146 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -28,7 +28,8 @@ use reth_primitives::{ use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{ - HashedPostStateProvider, HashedStorageProvider, StateProofProvider, StorageRootProvider, + HashedPostStateProvider, HashedStorageProvider, KeyHasherProvider, StateProofProvider, + StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -413,6 +414,12 @@ impl HashedStorageProvider for NoopProvider { } } +impl KeyHasherProvider for NoopProvider { + fn hash_key(&self, _bytes: &[u8]) -> B256 { + B256::default() + } +} + impl StateProvider for NoopProvider { fn storage( &self, diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 59b47221588e..859e73eb61e9 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -25,6 +25,7 @@ pub trait StateProvider: + StateProofProvider + HashedPostStateProvider + HashedStorageProvider + + KeyHasherProvider + Send + Sync { @@ -99,6 +100,13 @@ pub trait HashedPostStateProvider: Send + Sync { fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState; } +/// Trait that provides a method to hash bytes to produce a [`B256`] hash. +#[auto_impl(&, Arc, Box)] +pub trait KeyHasherProvider: Send + Sync { + /// Hashes the provided bytes into a 256-bit hash. + fn hash_key(&self, bytes: &[u8]) -> B256; +} + /// Trait implemented for database providers that can be converted into a historical state provider. pub trait TryIntoHistoricalStateProvider { /// Returns a historical [`StateProvider`] indexed by the given historic block number. From 60b4f7a95a42099ffd5675a345cf71a858a4cbbf Mon Sep 17 00:00:00 2001 From: frisitano Date: Tue, 26 Nov 2024 16:54:12 +0800 Subject: [PATCH 12/38] feat: introduce StateRootProviderExt and integrate it (and StateRootProvider) with StateCommitment --- Cargo.lock | 3 - bin/reth/Cargo.toml | 2 - .../src/commands/debug_cmd/build_block.rs | 8 +- .../commands/debug_cmd/in_memory_merkle.rs | 18 ++--- crates/blockchain-tree/Cargo.toml | 3 +- crates/blockchain-tree/src/blockchain_tree.rs | 20 ++--- crates/blockchain-tree/src/chain.rs | 2 +- crates/chain-state/src/in_memory.rs | 4 +- crates/chain-state/src/memory_overlay.rs | 4 +- crates/cli/commands/Cargo.toml | 4 +- .../cli/commands/src/recover/storage_tries.rs | 9 ++- .../engine/invalid-block-hooks/src/witness.rs | 2 +- crates/engine/tree/src/tree/mod.rs | 2 +- crates/engine/util/src/reorg.rs | 2 +- crates/ethereum/payload/src/lib.rs | 16 ++-- crates/optimism/payload/src/builder.rs | 16 ++-- crates/revm/src/test_utils.rs | 4 +- .../rpc-eth-api/src/helpers/pending_block.rs | 3 +- crates/rpc/rpc-eth-types/src/cache/db.rs | 8 +- crates/rpc/rpc-eth-types/src/simulate.rs | 2 +- crates/rpc/rpc/src/validation.rs | 4 +- crates/stages/stages/src/stages/merkle.rs | 21 +++-- .../src/providers/bundle_state_provider.rs | 8 +- .../src/providers/database/provider.rs | 20 ++--- .../src/providers/state/historical.rs | 30 ++++--- .../provider/src/providers/state/latest.rs | 79 ++++++++++++++++--- .../provider/src/providers/state/macros.rs | 4 +- .../storage/provider/src/test_utils/mock.rs | 4 +- crates/storage/provider/src/test_utils/mod.rs | 9 +-- .../storage/provider/src/test_utils/noop.rs | 4 +- crates/storage/storage-api/src/trie.rs | 37 ++++++++- crates/trie/db/src/state.rs | 50 +++++++++++- 32 files changed, 257 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac08c5994a14..072891328a95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6415,8 +6415,6 @@ dependencies = [ "reth-tasks", "reth-tracing", "reth-transaction-pool", - "reth-trie", - "reth-trie-db", "serde_json", "similar-asserts", "tempfile", @@ -6716,7 +6714,6 @@ dependencies = [ "reth-static-file-types", "reth-trie", "reth-trie-common", - "reth-trie-db", "secp256k1", "serde", "serde_json", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index a152bea2681e..f98447128788 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -54,8 +54,6 @@ reth-payload-primitives.workspace = true reth-payload-validator.workspace = true reth-basic-payload-builder.workspace = true reth-static-file.workspace = true -reth-trie = { workspace = true, features = ["metrics"] } -reth-trie-db = { workspace = true, features = ["metrics"] } reth-node-api.workspace = true reth-node-core.workspace = true reth-ethereum-payload-builder.workspace = true diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index 9e900cbe6ee4..2b493ebcbc2e 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -43,8 +43,6 @@ use reth_transaction_pool::{ blobstore::InMemoryBlobStore, BlobStore, EthPooledTransaction, PoolConfig, TransactionOrigin, TransactionPool, TransactionValidationTaskExecutor, }; -use reth_trie::StateRoot; -use reth_trie_db::DatabaseStateRoot; use std::{path::PathBuf, str::FromStr, sync::Arc}; use tracing::*; @@ -273,10 +271,8 @@ impl> Command { debug!(target: "reth::cli", ?execution_outcome, "Executed block"); let hashed_post_state = state_provider.hashed_post_state(execution_outcome.state()); - let (state_root, trie_updates) = StateRoot::overlay_root_with_updates( - provider_factory.provider()?.tx_ref(), - hashed_post_state.clone(), - )?; + let (state_root, trie_updates) = + state_provider.state_root_from_state_with_updates(hashed_post_state.clone())?; if state_root != block_with_senders.state_root { eyre::bail!( diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index c7a05ace3ebf..b796a0890706 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -24,13 +24,11 @@ use reth_provider::{ providers::ProviderNodeTypes, writer::UnifiedStorageWriter, AccountExtReader, ChainSpecProvider, HashedPostStateProvider, HashingWriter, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, - StateWriter, StorageReader, + StateRootProvider, StateRootProviderExt, StateWriter, StorageReader, }; use reth_revm::database::StateProviderDatabase; use reth_stages::StageId; use reth_tasks::TaskExecutor; -use reth_trie::StateRoot; -use reth_trie_db::DatabaseStateRoot; use std::{path::PathBuf, sync::Arc}; use tracing::*; @@ -154,10 +152,10 @@ impl> Command { let execution_outcome = ExecutionOutcome::from((block_execution_output, block.number)); // Unpacked `BundleState::state_root_slow` function - let (in_memory_state_root, in_memory_updates) = StateRoot::overlay_root_with_updates( - provider.tx_ref(), - state_provider.hashed_post_state(execution_outcome.state()), - )?; + let (in_memory_state_root, in_memory_updates) = state_provider + .state_root_from_state_with_updates( + state_provider.hashed_post_state(execution_outcome.state()), + )?; if in_memory_state_root == block.state_root { info!(target: "reth::cli", state_root = ?in_memory_state_root, "Computed in-memory state root matches"); @@ -182,10 +180,8 @@ impl> Command { let accounts = provider_rw.basic_accounts(account_lists)?; provider_rw.insert_account_for_hashing(accounts)?; - let (state_root, incremental_trie_updates) = StateRoot::incremental_root_with_updates( - provider_rw.tx_ref(), - block.number..=block.number, - )?; + let (state_root, incremental_trie_updates) = + state_provider.incremental_state_root_with_updates(block.number..=block.number)?; if state_root != block.state_root { eyre::bail!( "Computed incremental state root mismatch. Expected: {:?}. Got: {:?}", diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index 3fa6de2b402c..de1b194d567f 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -24,7 +24,6 @@ reth-provider.workspace = true reth-execution-types.workspace = true reth-stages-api.workspace = true reth-trie = { workspace = true, features = ["metrics"] } -reth-trie-db = { workspace = true, features = ["metrics"] } reth-trie-parallel.workspace = true reth-network.workspace = true reth-consensus.workspace = true @@ -55,6 +54,7 @@ reth-provider = { workspace = true, features = ["test-utils"] } reth-evm = { workspace = true, features = ["test-utils"] } reth-consensus = { workspace = true, features = ["test-utils"] } reth-testing-utils.workspace = true +reth-trie-db = { workspace = true, features = ["test-utils"] } reth-revm.workspace = true reth-evm-ethereum.workspace = true reth-execution-types.workspace = true @@ -75,7 +75,6 @@ test-utils = [ "reth-db/test-utils", "reth-db-api/test-utils", "reth-provider/test-utils", - "reth-trie-db/test-utils", "reth-trie/test-utils" ] optimism = [ diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 3f5e74a7cf13..7925e39ed502 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -25,12 +25,11 @@ use reth_provider::{ BlockExecutionWriter, BlockNumReader, BlockWriter, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, ChainSpecProvider, ChainSplit, ChainSplitTarget, DBProvider, DisplayBlocksChain, HashedPostStateProvider, HeaderProvider, - ProviderError, StaticFileProviderFactory, StorageLocation, + LatestStateProviderRef, ProviderError, StateRootProvider, StaticFileProviderFactory, + StorageLocation, }; use reth_stages_api::{MetricEvent, MetricEventsSender}; use reth_storage_errors::provider::{ProviderResult, RootMismatch}; -use reth_trie::{hashed_cursor::HashedPostStateCursorFactory, StateRoot}; -use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseStateRoot}; use std::{ collections::{btree_map::Entry, BTreeMap, HashSet}, sync::Arc, @@ -1216,8 +1215,6 @@ where ) -> Result<(), CanonicalError> { let (blocks, state, chain_trie_updates) = chain.into_inner(); let hashed_state = self.externals.provider_factory.hashed_post_state(state.state()); - let prefix_sets = hashed_state.construct_prefix_sets().freeze(); - let hashed_state_sorted = hashed_state.into_sorted(); // Compute state root or retrieve cached trie updates before opening write transaction. let block_hash_numbers = @@ -1237,14 +1234,8 @@ where // State root calculation can take a while, and we're sure no write transaction // will be open in parallel. See https://github.com/paradigmxyz/reth/issues/6168. .disable_long_read_transaction_safety(); - let (state_root, trie_updates) = StateRoot::from_tx(provider.tx_ref()) - .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( - DatabaseHashedCursorFactory::new(provider.tx_ref()), - &hashed_state_sorted, - )) - .with_prefix_sets(prefix_sets) - .root_with_updates() - .map_err(Into::::into)?; + let (state_root, trie_updates) = LatestStateProviderRef::new(&provider) + .state_root_from_state_with_updates(hashed_state.clone())?; let tip = blocks.tip(); if state_root != tip.state_root { return Err(ProviderError::StateRootMismatch(Box::new(RootMismatch { @@ -1265,7 +1256,7 @@ where .append_blocks_with_state( blocks.into_blocks().collect(), state, - hashed_state_sorted, + hashed_state.into_sorted(), trie_updates, ) .map_err(|e| CanonicalError::CanonicalCommit(e.to_string()))?; @@ -1403,6 +1394,7 @@ mod tests { use reth_revm::primitives::AccountInfo; use reth_stages_api::StageCheckpoint; use reth_trie::{root::state_root_unhashed, StateRoot}; + use reth_trie_db::DatabaseStateRoot; use std::collections::HashMap; fn setup_externals( diff --git a/crates/blockchain-tree/src/chain.rs b/crates/blockchain-tree/src/chain.rs index a73d87f3a445..1be932b17537 100644 --- a/crates/blockchain-tree/src/chain.rs +++ b/crates/blockchain-tree/src/chain.rs @@ -235,7 +235,7 @@ impl AppendableChain { .map_err(ProviderError::from)? } else { let hashed_state = provider.hashed_post_state(initial_execution_outcome.state()); - let state_root = provider.state_root(hashed_state)?; + let state_root = provider.state_root_from_state(hashed_state)?; (state_root, None) }; if block.state_root != state_root { diff --git a/crates/chain-state/src/in_memory.rs b/crates/chain-state/src/in_memory.rs index 95c327980b04..33ad75a42806 100644 --- a/crates/chain-state/src/in_memory.rs +++ b/crates/chain-state/src/in_memory.rs @@ -997,7 +997,7 @@ mod tests { } impl StateRootProvider for MockStateProvider { - fn state_root(&self, _hashed_state: HashedPostState) -> ProviderResult { + fn state_root_from_state(&self, _hashed_state: HashedPostState) -> ProviderResult { Ok(B256::random()) } @@ -1005,7 +1005,7 @@ mod tests { Ok(B256::random()) } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, _hashed_state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { diff --git a/crates/chain-state/src/memory_overlay.rs b/crates/chain-state/src/memory_overlay.rs index 05e761f99980..0160d848e1fb 100644 --- a/crates/chain-state/src/memory_overlay.rs +++ b/crates/chain-state/src/memory_overlay.rs @@ -117,7 +117,7 @@ macro_rules! impl_state_provider { } impl $($tokens)* StateRootProvider for $type { - fn state_root(&self, state: HashedPostState) -> ProviderResult { + fn state_root_from_state(&self, state: HashedPostState) -> ProviderResult { self.state_root_from_nodes(TrieInput::from_state(state)) } @@ -127,7 +127,7 @@ macro_rules! impl_state_provider { self.historical.state_root_from_nodes(input) } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml index 90acb82d71d7..4c403c2d75a4 100644 --- a/crates/cli/commands/Cargo.toml +++ b/crates/cli/commands/Cargo.toml @@ -45,8 +45,7 @@ reth-stages.workspace = true reth-stages-types = { workspace = true, optional = true } reth-static-file-types = { workspace = true, features = ["clap"] } reth-static-file.workspace = true -reth-trie = { workspace = true, features = ["metrics"] } -reth-trie-db = { workspace = true, features = ["metrics"] } +reth-trie = { workspace = true, optional = true } reth-trie-common = { workspace = true, optional = true } # ethereum @@ -113,6 +112,7 @@ arbitrary = [ "reth-codecs?/arbitrary", "reth-prune-types?/arbitrary", "reth-stages-types?/arbitrary", + "reth-trie", "reth-trie-common?/arbitrary", "alloy-consensus/arbitrary", ] diff --git a/crates/cli/commands/src/recover/storage_tries.rs b/crates/cli/commands/src/recover/storage_tries.rs index f879c393c6b1..f9b65010fa32 100644 --- a/crates/cli/commands/src/recover/storage_tries.rs +++ b/crates/cli/commands/src/recover/storage_tries.rs @@ -8,9 +8,10 @@ use reth_db_api::{ cursor::{DbCursorRO, DbDupCursorRW}, transaction::DbTx, }; -use reth_provider::{BlockNumReader, HeaderProvider, ProviderError}; -use reth_trie::StateRoot; -use reth_trie_db::DatabaseStateRoot; +use reth_provider::{ + BlockNumReader, HeaderProvider, LatestStateProviderRef, ProviderError, StateRootProviderExt, +}; + use tracing::*; /// `reth recover storage-tries` command @@ -50,7 +51,7 @@ impl> Command entry = storage_trie_cursor.next()?; } - let state_root = StateRoot::from_tx(tx_mut).root()?; + let state_root = LatestStateProviderRef::new(&provider.0).state_root()?; if state_root != best_header.state_root { eyre::bail!( "Recovery failed. Incorrect state root. Expected: {:?}. Received: {:?}", diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index acb3351afa6f..6d58304f54c7 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -236,7 +236,7 @@ where // Calculate the state root and trie updates after re-execution. They should match // the original ones. let (re_executed_root, trie_output) = - state_provider.state_root_with_updates(hashed_state)?; + state_provider.state_root_from_state_with_updates(hashed_state)?; if let Some((original_updates, original_root)) = trie_updates { if re_executed_root != original_root { let filename = format!("{}_{}.state_root.diff", block.number, block.hash()); diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 8263f2edc438..0a301a8ecb20 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -2253,7 +2253,7 @@ where result } else { debug!(target: "engine::tree", block=?sealed_block.num_hash(), persistence_in_progress, "Failed to compute state root in parallel"); - state_provider.state_root_with_updates(hashed_state.clone())? + state_provider.state_root_from_state_with_updates(hashed_state.clone())? }; if state_root != block.state_root { diff --git a/crates/engine/util/src/reorg.rs b/crates/engine/util/src/reorg.rs index efe0cfce2563..f6e9c43e25a5 100644 --- a/crates/engine/util/src/reorg.rs +++ b/crates/engine/util/src/reorg.rs @@ -422,7 +422,7 @@ where gas_used: cumulative_gas_used, blob_gas_used: blob_gas_used.map(Into::into), excess_blob_gas: excess_blob_gas.map(Into::into), - state_root: state_provider.state_root(hashed_state)?, + state_root: state_provider.state_root_from_state(hashed_state)?, }, body: BlockBody { transactions, diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 41f2f42bab5a..7e9a2e54e67d 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -376,13 +376,15 @@ where // calculate the state root let hashed_state = db.database.db.hashed_post_state(execution_outcome.state()); let (state_root, trie_output) = { - db.database.inner().state_root_with_updates(hashed_state.clone()).inspect_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_header.hash(), - %err, - "failed to calculate state root for payload" - ); - })? + db.database.inner().state_root_from_state_with_updates(hashed_state.clone()).inspect_err( + |err| { + warn!(target: "payload_builder", + parent_hash=%parent_header.hash(), + %err, + "failed to calculate state root for payload" + ); + }, + )? }; // create the block header diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index bb8a75378e65..4f19c2bf7451 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -369,13 +369,15 @@ where let state_provider = state.database.as_ref(); let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); let (state_root, trie_output) = { - state_provider.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { - warn!(target: "payload_builder", - parent_header=%ctx.parent().hash(), - %err, - "failed to calculate state root for payload" - ); - })? + state_provider.state_root_from_state_with_updates(hashed_state.clone()).inspect_err( + |err| { + warn!(target: "payload_builder", + parent_header=%ctx.parent().hash(), + %err, + "failed to calculate state root for payload" + ); + }, + )? }; // create the block header diff --git a/crates/revm/src/test_utils.rs b/crates/revm/src/test_utils.rs index 6beb61c2cd59..607479576138 100644 --- a/crates/revm/src/test_utils.rs +++ b/crates/revm/src/test_utils.rs @@ -72,7 +72,7 @@ impl BlockHashReader for StateProviderTest { } impl StateRootProvider for StateProviderTest { - fn state_root(&self, _hashed_state: HashedPostState) -> ProviderResult { + fn state_root_from_state(&self, _hashed_state: HashedPostState) -> ProviderResult { unimplemented!("state root computation is not supported") } @@ -80,7 +80,7 @@ impl StateRootProvider for StateProviderTest { unimplemented!("state root computation is not supported") } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, _hashed_state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index b697c6870c5a..b258578ffc25 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -396,7 +396,8 @@ pub trait LoadPendingBlock: execution_outcome.block_logs_bloom(block_number).expect("Block is present"); // calculate the state root - let state_root = db.database.state_root(hashed_state).map_err(Self::Error::from_eth_err)?; + let state_root = + db.database.state_root_from_state(hashed_state).map_err(Self::Error::from_eth_err)?; // create the block header let transactions_root = calculate_transaction_root(&executed_txs); diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index 34da3b562c61..1e9fdccb2a73 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -23,11 +23,11 @@ pub type StateCacheDb<'a> = CacheDB(pub &'a dyn StateProvider); impl reth_storage_api::StateRootProvider for StateProviderTraitObjWrapper<'_> { - fn state_root( + fn state_root_from_state( &self, hashed_state: reth_trie::HashedPostState, ) -> reth_errors::ProviderResult { - self.0.state_root(hashed_state) + self.0.state_root_from_state(hashed_state) } fn state_root_from_nodes( @@ -37,11 +37,11 @@ impl reth_storage_api::StateRootProvider for StateProviderTraitObjWrapper<'_> { self.0.state_root_from_nodes(input) } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, hashed_state: reth_trie::HashedPostState, ) -> reth_errors::ProviderResult<(B256, reth_trie::updates::TrieUpdates)> { - self.0.state_root_with_updates(hashed_state) + self.0.state_root_from_state_with_updates(hashed_state) } fn state_root_from_nodes_with_updates( diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 58a695b7533e..472de36482e0 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -246,7 +246,7 @@ pub fn build_block>( } } - let state_root = db.db.state_root(hashed_state).map_err(T::Error::from_eth_err)?; + let state_root = db.db.state_root_from_state(hashed_state).map_err(T::Error::from_eth_err)?; let header = alloy_consensus::Header { beneficiary: block_env.coinbase, diff --git a/crates/rpc/rpc/src/validation.rs b/crates/rpc/rpc/src/validation.rs index 6754f1c1a4e9..c4799bbb1d61 100644 --- a/crates/rpc/rpc/src/validation.rs +++ b/crates/rpc/rpc/src/validation.rs @@ -178,8 +178,8 @@ where self.ensure_payment(&block, &output, &message)?; - let state_root = - state_provider.state_root(state_provider.hashed_post_state(&output.state))?; + let state_root = state_provider + .state_root_from_state(state_provider.hashed_post_state(&output.state))?; if state_root != block.state_root { return Err(ConsensusError::BodyStateRootDiff( diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index 2d2503b53919..72122dd838b7 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -5,15 +5,14 @@ use reth_db::tables; use reth_db_api::transaction::{DbTx, DbTxMut}; use reth_primitives::{GotExpected, SealedHeader}; use reth_provider::{ - DBProvider, HeaderProvider, ProviderError, StageCheckpointReader, StageCheckpointWriter, - StatsReader, TrieWriter, + DBProvider, HeaderProvider, LatestStateProviderRef, ProviderError, StageCheckpointReader, + StageCheckpointWriter, StateCommitmentProvider, StateRootProviderExt, StatsReader, TrieWriter, }; use reth_stages_api::{ BlockErrorKind, EntitiesCheckpoint, ExecInput, ExecOutput, MerkleCheckpoint, Stage, StageCheckpoint, StageError, StageId, UnwindInput, UnwindOutput, }; -use reth_trie::{IntermediateStateRootState, StateRoot, StateRootProgress, StoredSubNode}; -use reth_trie_db::DatabaseStateRoot; +use reth_trie::{IntermediateStateRootState, StateRootProgress, StoredSubNode}; use std::fmt::Debug; use tracing::*; @@ -137,7 +136,8 @@ where + StatsReader + HeaderProvider + StageCheckpointReader - + StageCheckpointWriter, + + StageCheckpointWriter + + StateCommitmentProvider, { /// Return the id of the stage fn id(&self) -> StageId { @@ -210,10 +210,8 @@ where as u64, }); - let tx = provider.tx_ref(); - let progress = StateRoot::from_tx(tx) - .with_intermediate_state(checkpoint.map(IntermediateStateRootState::from)) - .root_with_progress() + let progress = LatestStateProviderRef::new(provider).state_root_with_progress + (checkpoint.map(IntermediateStateRootState::from)) .map_err(|e| { error!(target: "sync::stages::merkle", %e, ?current_block_number, ?to_block, "State root with progress failed! {INVALID_STATE_ROOT_ERROR_MESSAGE}"); StageError::Fatal(Box::new(e)) @@ -250,7 +248,7 @@ where } else { debug!(target: "sync::stages::merkle::exec", current = ?current_block_number, target = ?to_block, "Updating trie"); let (root, updates) = - StateRoot::incremental_root_with_updates(provider.tx_ref(), range) + LatestStateProviderRef::new(provider).incremental_state_root_with_updates(range) .map_err(|e| { error!(target: "sync::stages::merkle", %e, ?current_block_number, ?to_block, "Incremental state root failed! {INVALID_STATE_ROOT_ERROR_MESSAGE}"); StageError::Fatal(Box::new(e)) @@ -321,7 +319,8 @@ where if range.is_empty() { info!(target: "sync::stages::merkle::unwind", "Nothing to unwind"); } else { - let (block_root, updates) = StateRoot::incremental_root_with_updates(tx, range) + let (block_root, updates) = LatestStateProviderRef::new(provider) + .incremental_state_root_with_updates(range) .map_err(|e| StageError::Fatal(Box::new(e)))?; // Validate the calculated state root diff --git a/crates/storage/provider/src/providers/bundle_state_provider.rs b/crates/storage/provider/src/providers/bundle_state_provider.rs index 1b1bc12e8cd7..441878c9c7e8 100644 --- a/crates/storage/provider/src/providers/bundle_state_provider.rs +++ b/crates/storage/provider/src/providers/bundle_state_provider.rs @@ -82,25 +82,25 @@ impl AccountReader for BundleStat impl StateRootProvider for BundleStateProvider { - fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult { + fn state_root_from_state(&self, hashed_state: HashedPostState) -> ProviderResult { let bundle_state = self.block_execution_data_provider.execution_outcome().state(); let mut state = self.hashed_post_state(bundle_state); state.extend(hashed_state); - self.state_provider.state_root(state) + self.state_provider.state_root_from_state(state) } fn state_root_from_nodes(&self, _input: TrieInput) -> ProviderResult { unimplemented!() } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, hashed_state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { let bundle_state = self.block_execution_data_provider.execution_outcome().state(); let mut state = self.hashed_post_state(bundle_state); state.extend(hashed_state); - self.state_provider.state_root_with_updates(state) + self.state_provider.state_root_from_state_with_updates(state) } fn state_root_from_nodes_with_updates( diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 9d84a2dd5ddd..498a45bdbb94 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -17,8 +17,8 @@ use crate::{ KeyHasherProvider, LatestStateProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, PruneCheckpointReader, PruneCheckpointWriter, RevertsInit, StageCheckpointReader, StateChangeWriter, StateCommitmentProvider, StateProviderBox, - StateReader, StateWriter, StaticFileProviderFactory, StatsReader, StorageLocation, - StorageReader, StorageTrieWriter, TransactionVariant, TransactionsProvider, + StateReader, StateRootProviderExt, StateWriter, StaticFileProviderFactory, StatsReader, + StorageLocation, StorageReader, StorageTrieWriter, TransactionVariant, TransactionsProvider, TransactionsProviderExt, TrieWriter, WithdrawalsProvider, }; use alloy_consensus::Header; @@ -61,9 +61,9 @@ use reth_storage_errors::provider::{ProviderResult, RootMismatch}; use reth_trie::{ prefix_set::{PrefixSet, PrefixSetMut, TriePrefixSets}, updates::{StorageTrieUpdates, TrieUpdates}, - HashedPostStateSorted, KeyHasher, Nibbles, StateRoot, StoredNibbles, + HashedPostStateSorted, KeyHasher, Nibbles, StoredNibbles, }; -use reth_trie_db::{DatabaseStateRoot, DatabaseStorageTrieCursor, StateCommitment}; +use reth_trie_db::{DatabaseStorageTrieCursor, StateCommitment}; use revm::{ db::states::{PlainStateReverts, PlainStorageChangeset, PlainStorageRevert, StateChangeset}, primitives::{BlockEnv, CfgEnvWithHandlerCfg}, @@ -304,10 +304,8 @@ impl DatabaseProvider::into)?; + let (new_state_root, trie_updates) = LatestStateProviderRef::new(self) + .state_root_from_prefix_sets_with_updates(prefix_sets)?; let parent_number = range.start().saturating_sub(1); let parent_state_root = self @@ -2554,10 +2552,8 @@ impl HashingWriter for DatabaseProvi .collect(), destroyed_accounts, }; - let (state_root, trie_updates) = StateRoot::from_tx(&self.tx) - .with_prefix_sets(prefix_sets) - .root_with_updates() - .map_err(Into::::into)?; + let (state_root, trie_updates) = LatestStateProviderRef::new(self) + .state_root_from_prefix_sets_with_updates(prefix_sets)?; if state_root != expected_state_root { return Err(ProviderError::StateRootMismatch(Box::new(RootMismatch { root: GotExpected { got: state_root, expected: expected_state_root }, diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 659ec330d1ad..58a2a15a0a9f 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -24,8 +24,7 @@ use reth_trie::{ proof::{Proof, StorageProof}, updates::TrieUpdates, witness::TrieWitness, - AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StateRoot, StorageRoot, - TrieInput, + AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StorageRoot, TrieInput, }; use reth_trie_db::{ DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot, @@ -291,27 +290,36 @@ impl BlockHashReader impl StateRootProvider for HistoricalStateProviderRef<'_, Provider> { - fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult { + fn state_root_from_state(&self, hashed_state: HashedPostState) -> ProviderResult { let mut revert_state = self.revert_state()?; revert_state.extend(hashed_state); - StateRoot::overlay_root(self.tx(), revert_state) - .map_err(|err| ProviderError::Database(err.into())) + ::StateRoot::overlay_root( + self.tx(), + revert_state, + ) + .map_err(|err| ProviderError::Database(err.into())) } fn state_root_from_nodes(&self, mut input: TrieInput) -> ProviderResult { input.prepend(self.revert_state()?); - StateRoot::overlay_root_from_nodes(self.tx(), input) - .map_err(|err| ProviderError::Database(err.into())) + ::StateRoot::overlay_root_from_nodes( + self.tx(), + input, + ) + .map_err(|err| ProviderError::Database(err.into())) } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, hashed_state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { let mut revert_state = self.revert_state()?; revert_state.extend(hashed_state); - StateRoot::overlay_root_with_updates(self.tx(), revert_state) - .map_err(|err| ProviderError::Database(err.into())) + ::StateRoot::overlay_root_with_updates( + self.tx(), + revert_state, + ) + .map_err(|err| ProviderError::Database(err.into())) } fn state_root_from_nodes_with_updates( @@ -319,7 +327,7 @@ impl StateRootP mut input: TrieInput, ) -> ProviderResult<(B256, TrieUpdates)> { input.prepend(self.revert_state()?); - StateRoot::overlay_root_from_nodes_with_updates(self.tx(), input) + ::StateRoot::overlay_root_from_nodes_with_updates(self.tx(), input) .map_err(|err| ProviderError::Database(err.into())) } } diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 4bc0318a6123..df7766b75a1b 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -11,15 +11,14 @@ use reth_db_api::{cursor::DbDupCursorRO, transaction::DbTx}; use reth_primitives::{Account, Bytecode}; use reth_storage_api::{ DBProvider, HashedStorageProvider, KeyHasherProvider, StateCommitmentProvider, - StateProofProvider, StorageRootProvider, + StateProofProvider, StateRootProviderExt, StorageRootProvider, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{ proof::{Proof, StorageProof}, updates::TrieUpdates, witness::TrieWitness, - AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StateRoot, StorageRoot, - TrieInput, + AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StorageRoot, TrieInput, }; use reth_trie_db::{ DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot, @@ -68,31 +67,87 @@ impl BlockHashReader for LatestStateProviderRef<'_, P impl StateRootProvider for LatestStateProviderRef<'_, Provider> { - fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult { - StateRoot::overlay_root(self.tx(), hashed_state) - .map_err(|err| ProviderError::Database(err.into())) + fn state_root_from_state(&self, hashed_state: HashedPostState) -> ProviderResult { + ::StateRoot::overlay_root( + self.tx(), + hashed_state, + ) + .map_err(|err| ProviderError::Database(err.into())) } fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult { - StateRoot::overlay_root_from_nodes(self.tx(), input) - .map_err(|err| ProviderError::Database(err.into())) + ::StateRoot::overlay_root_from_nodes( + self.tx(), + input, + ) + .map_err(|err| ProviderError::Database(err.into())) } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, hashed_state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { - StateRoot::overlay_root_with_updates(self.tx(), hashed_state) - .map_err(|err| ProviderError::Database(err.into())) + ::StateRoot::overlay_root_with_updates( + self.tx(), + hashed_state, + ) + .map_err(|err| ProviderError::Database(err.into())) } fn state_root_from_nodes_with_updates( &self, input: TrieInput, ) -> ProviderResult<(B256, TrieUpdates)> { - StateRoot::overlay_root_from_nodes_with_updates(self.tx(), input) + ::StateRoot::overlay_root_from_nodes_with_updates(self.tx(), input) + .map_err(|err| ProviderError::Database(err.into())) + } +} + +impl StateRootProviderExt + for LatestStateProviderRef<'_, Provider> +{ + fn state_root(&self) -> ProviderResult { + ::StateRoot::root(self.tx()) + .map_err(|err| ProviderError::Database(err.into())) + } + + fn state_root_with_updates(&self) -> ProviderResult<(B256, TrieUpdates)> { + ::StateRoot::root_with_updates(self.tx()) .map_err(|err| ProviderError::Database(err.into())) } + + fn incremental_state_root_with_updates( + &self, + range: std::ops::RangeInclusive, + ) -> ProviderResult<(B256, TrieUpdates)> { + ::StateRoot::incremental_root_with_updates( + self.tx(), + range, + ) + .map_err(|err| ProviderError::Database(err.into())) + } + + fn state_root_from_prefix_sets_with_updates( + &self, + prefix_set: reth_trie::prefix_set::TriePrefixSets, + ) -> ProviderResult<(B256, TrieUpdates)> { + ::StateRoot::root_from_prefix_sets_with_updates( + self.tx(), + prefix_set, + ) + .map_err(|err| ProviderError::Database(err.into())) + } + + fn state_root_with_progress( + &self, + state: Option, + ) -> ProviderResult { + ::StateRoot::root_with_progress( + self.tx(), + state, + ) + .map_err(|err| ProviderError::Database(err.into())) + } } impl StorageRootProvider diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index 82ff634b1d9d..4479530bded5 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -42,9 +42,9 @@ macro_rules! delegate_provider_impls { fn bytecode_by_hash(&self, code_hash: alloy_primitives::B256) -> reth_storage_errors::provider::ProviderResult>; } StateRootProvider $(where [$($generics)*])? { - fn state_root(&self, state: reth_trie::HashedPostState) -> reth_storage_errors::provider::ProviderResult; + fn state_root_from_state(&self, state: reth_trie::HashedPostState) -> reth_storage_errors::provider::ProviderResult; fn state_root_from_nodes(&self, input: reth_trie::TrieInput) -> reth_storage_errors::provider::ProviderResult; - fn state_root_with_updates(&self, state: reth_trie::HashedPostState) -> reth_storage_errors::provider::ProviderResult<(alloy_primitives::B256, reth_trie::updates::TrieUpdates)>; + fn state_root_from_state_with_updates(&self, state: reth_trie::HashedPostState) -> reth_storage_errors::provider::ProviderResult<(alloy_primitives::B256, reth_trie::updates::TrieUpdates)>; fn state_root_from_nodes_with_updates(&self, input: reth_trie::TrieInput) -> reth_storage_errors::provider::ProviderResult<(alloy_primitives::B256, reth_trie::updates::TrieUpdates)>; } StorageRootProvider $(where [$($generics)*])? { diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index a52da1338699..dc9ae65efef0 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -600,7 +600,7 @@ impl StageCheckpointReader for MockEthProvider { } impl StateRootProvider for MockEthProvider { - fn state_root(&self, _state: HashedPostState) -> ProviderResult { + fn state_root_from_state(&self, _state: HashedPostState) -> ProviderResult { Ok(self.state_roots.lock().pop().unwrap_or_default()) } @@ -608,7 +608,7 @@ impl StateRootProvider for MockEthProvider { Ok(self.state_roots.lock().pop().unwrap_or_default()) } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, _state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { diff --git a/crates/storage/provider/src/test_utils/mod.rs b/crates/storage/provider/src/test_utils/mod.rs index 2c3795573c20..47551fa9db14 100644 --- a/crates/storage/provider/src/test_utils/mod.rs +++ b/crates/storage/provider/src/test_utils/mod.rs @@ -1,5 +1,5 @@ use crate::{ - providers::{ProviderNodeTypes, StaticFileProvider}, + providers::{LatestStateProviderRef, ProviderNodeTypes, StaticFileProvider}, HashingWriter, ProviderFactory, TrieWriter, }; use alloy_primitives::B256; @@ -11,8 +11,7 @@ use reth_db::{ use reth_errors::ProviderResult; use reth_node_types::NodeTypesWithDBAdapter; use reth_primitives::{Account, StorageEntry}; -use reth_trie::StateRoot; -use reth_trie_db::DatabaseStateRoot; +use reth_storage_api::StateRootProviderExt; use std::sync::Arc; pub mod blocks; @@ -78,9 +77,7 @@ pub fn insert_genesis>( }); provider.insert_storage_for_hashing(alloc_storage)?; - let (root, updates) = StateRoot::from_tx(provider.tx_ref()) - .root_with_updates() - .map_err(Into::::into)?; + let (root, updates) = LatestStateProviderRef::new(&provider.0).state_root_with_updates()?; provider.write_trie_updates(&updates).unwrap(); provider.commit()?; diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 4ad60b864146..daddf81169da 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -333,7 +333,7 @@ impl ChangeSetReader for NoopProvider { } impl StateRootProvider for NoopProvider { - fn state_root(&self, _state: HashedPostState) -> ProviderResult { + fn state_root_from_state(&self, _state: HashedPostState) -> ProviderResult { Ok(B256::default()) } @@ -341,7 +341,7 @@ impl StateRootProvider for NoopProvider { Ok(B256::default()) } - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, _state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)> { diff --git a/crates/storage/storage-api/src/trie.rs b/crates/storage/storage-api/src/trie.rs index c8f12da07167..0dd3ec72fba6 100644 --- a/crates/storage/storage-api/src/trie.rs +++ b/crates/storage/storage-api/src/trie.rs @@ -1,11 +1,13 @@ use alloy_primitives::{ map::{HashMap, HashSet}, - Address, Bytes, B256, + Address, BlockNumber, Bytes, B256, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ + prefix_set::TriePrefixSets, updates::{StorageTrieUpdates, TrieUpdates}, - AccountProof, HashedPostState, HashedStorage, MultiProof, StorageProof, TrieInput, + AccountProof, HashedPostState, HashedStorage, IntermediateStateRootState, MultiProof, + StateRootProgress, StorageProof, TrieInput, }; /// A type that can compute the state root of a given post state. @@ -18,7 +20,7 @@ pub trait StateRootProvider: Send + Sync { /// It is recommended to provide a different implementation from /// `state_root_with_updates` since it affects the memory usage during state root /// computation. - fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult; + fn state_root_from_state(&self, hashed_state: HashedPostState) -> ProviderResult; /// Returns the state root of the `HashedPostState` on top of the current state but re-uses the /// intermediate nodes to speed up the computation. It's up to the caller to construct the @@ -27,7 +29,7 @@ pub trait StateRootProvider: Send + Sync { /// Returns the state root of the `HashedPostState` on top of the current state with trie /// updates to be committed to the database. - fn state_root_with_updates( + fn state_root_from_state_with_updates( &self, hashed_state: HashedPostState, ) -> ProviderResult<(B256, TrieUpdates)>; @@ -40,6 +42,33 @@ pub trait StateRootProvider: Send + Sync { ) -> ProviderResult<(B256, TrieUpdates)>; } +/// A trait that is used to compute the state root of the latest state stored in the database. +pub trait StateRootProviderExt: Send + Sync { + /// Returns the state root of the current state. + fn state_root(&self) -> ProviderResult; + + /// Returns the state root of the current state and trie updates. + fn state_root_with_updates(&self) -> ProviderResult<(B256, TrieUpdates)>; + + /// Returns the state root with trie updates associated with the given block range. + fn incremental_state_root_with_updates( + &self, + range: std::ops::RangeInclusive, + ) -> ProviderResult<(B256, TrieUpdates)>; + + /// Returns the state root progress. + fn state_root_with_progress( + &self, + state: Option, + ) -> ProviderResult; + + /// Returns the state root of the current state with the provided prefix sets updated. + fn state_root_from_prefix_sets_with_updates( + &self, + prefix_set: TriePrefixSets, + ) -> ProviderResult<(B256, TrieUpdates)>; +} + /// A type that can compute the storage root for a given account. #[auto_impl::auto_impl(&, Box, Arc)] pub trait StorageRootProvider: Send + Sync { diff --git a/crates/trie/db/src/state.rs b/crates/trie/db/src/state.rs index 5aaf3ebe5b0f..39a4191f589c 100644 --- a/crates/trie/db/src/state.rs +++ b/crates/trie/db/src/state.rs @@ -9,9 +9,10 @@ use reth_db_api::{ use reth_execution_errors::StateRootError; use reth_storage_errors::db::DatabaseError; use reth_trie::{ - hashed_cursor::HashedPostStateCursorFactory, trie_cursor::InMemoryTrieCursorFactory, - updates::TrieUpdates, HashedPostState, HashedStorage, KeccakKeyHasher, KeyHasher, StateRoot, - StateRootProgress, TrieInput, + hashed_cursor::HashedPostStateCursorFactory, prefix_set::TriePrefixSets, + trie_cursor::InMemoryTrieCursorFactory, updates::TrieUpdates, HashedPostState, HashedStorage, + IntermediateStateRootState, KeccakKeyHasher, KeyHasher, StateRoot, StateRootProgress, + TrieInput, }; use std::{collections::HashMap, ops::RangeInclusive}; use tracing::debug; @@ -117,6 +118,27 @@ pub trait DatabaseStateRoot<'a, TX>: Sized { tx: &'a TX, input: TrieInput, ) -> Result<(B256, TrieUpdates), StateRootError>; + + /// Calculates the state root for the current state stored in the database. + fn root(tx: &'a TX) -> Result; + + /// Calculates the state root for the current state stored in the database and returns + /// trie updates. + fn root_with_updates(tx: &'a TX) -> Result<(B256, TrieUpdates), StateRootError>; + + /// Calculates the state root for the current state stored in the database updating the paths + /// associated with the provided prefix sets and returns the trie updates. + fn root_from_prefix_sets_with_updates( + tx: &'a TX, + prefix_sets: TriePrefixSets, + ) -> Result<(B256, TrieUpdates), StateRootError>; + + /// Calculates the state root for the current state stored in the database and returns the + /// intermediate progress of the computation. + fn root_with_progress( + tx: &'a TX, + state: Option, + ) -> Result; } /// Extends [`HashedPostState`] with operations specific for working with a database transaction. @@ -214,6 +236,28 @@ impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> .with_prefix_sets(input.prefix_sets.freeze()) .root_with_updates() } + + fn root(tx: &'a TX) -> Result { + Self::from_tx(tx).root() + } + + fn root_with_updates(tx: &'a TX) -> Result<(B256, TrieUpdates), StateRootError> { + Self::from_tx(tx).root_with_updates() + } + + fn root_from_prefix_sets_with_updates( + tx: &'a TX, + prefix_sets: TriePrefixSets, + ) -> Result<(B256, TrieUpdates), StateRootError> { + Self::from_tx(tx).with_prefix_sets(prefix_sets).root_with_updates() + } + + fn root_with_progress( + tx: &'a TX, + state: Option, + ) -> Result { + Self::from_tx(tx).with_intermediate_state(state).root_with_progress() + } } impl DatabaseHashedPostState for HashedPostState { From f275c6c0ae94e4e68275fb4e1558722be0747583 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 00:08:23 +0800 Subject: [PATCH 13/38] chore: address PR feedback and enhance test coverage --- Cargo.lock | 2 + crates/scroll/primitives/src/lib.rs | 3 +- crates/scroll/primitives/src/poseidon.rs | 4 + crates/scroll/state-commitment/Cargo.toml | 12 +- crates/scroll/state-commitment/src/lib.rs | 4 + crates/scroll/state-commitment/src/root.rs | 309 +++--------------- .../scroll/state-commitment/src/test_utils.rs | 95 ++++++ crates/scroll/state-commitment/tests/trie.rs | 244 ++++++++++++++ crates/scroll/trie/Cargo.toml | 2 +- crates/scroll/trie/src/branch.rs | 8 +- crates/scroll/trie/src/hash_builder.rs | 30 +- crates/scroll/trie/src/leaf.rs | 2 +- crates/scroll/trie/src/lib.rs | 11 +- crates/scroll/trie/src/sub_tree.rs | 4 +- crates/trie/trie/Cargo.toml | 1 + crates/trie/trie/src/node_iter.rs | 15 +- crates/trie/trie/src/proof.rs | 22 +- crates/trie/trie/src/state.rs | 27 +- crates/trie/trie/src/trie.rs | 10 +- crates/trie/trie/src/updates.rs | 20 +- crates/trie/trie/src/walker.rs | 12 +- crates/trie/trie/src/witness.rs | 7 +- 22 files changed, 496 insertions(+), 348 deletions(-) create mode 100644 crates/scroll/state-commitment/src/test_utils.rs create mode 100644 crates/scroll/state-commitment/tests/trie.rs diff --git a/Cargo.lock b/Cargo.lock index 8ef4ee89c92d..dd755ad44b07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9764,6 +9764,7 @@ dependencies = [ "proptest", "proptest-arbitrary-interop", "reth-db", + "reth-db-api", "reth-execution-errors", "reth-metrics", "reth-primitives", @@ -9772,6 +9773,7 @@ dependencies = [ "reth-trie-common", "reth-trie-db", "scroll-primitives", + "scroll-state-commitment", "scroll-trie", "tracing", "tracing-subscriber", diff --git a/crates/scroll/primitives/src/lib.rs b/crates/scroll/primitives/src/lib.rs index 6a8e24911e05..f213d7e86a8a 100644 --- a/crates/scroll/primitives/src/lib.rs +++ b/crates/scroll/primitives/src/lib.rs @@ -34,8 +34,7 @@ impl From for ScrollTrieAccount { code_size: Default::default(), nonce: value.nonce, balance: value.balance, - // TODO(frisitano): introduce storage root - storage_root: Default::default(), + storage_root: value.storage_root, code_hash: value.code_hash, } } diff --git a/crates/scroll/primitives/src/poseidon.rs b/crates/scroll/primitives/src/poseidon.rs index 66a718b93f24..fef2adb44970 100644 --- a/crates/scroll/primitives/src/poseidon.rs +++ b/crates/scroll/primitives/src/poseidon.rs @@ -1,3 +1,4 @@ +use alloy_primitives::B256; pub use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; /// Type that is used to represent a field element in binary representation. @@ -16,6 +17,9 @@ pub const DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT: u64 = 256; pub const DOMAIN_TWO_FIELD_ELEMENTS: Fr = Fr::from_raw([DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT * 2, 0, 0, 0]); +/// The root hash of an empty binary Merle Patricia trie. +pub const EMPTY_ROOT_HASH: B256 = B256::ZERO; + /// Hash two field elements using poseidon. pub fn hash(element_1: Fr, element_2: Fr) -> Fr { hash_with_domain(&[element_1, element_2], DOMAIN_TWO_FIELD_ELEMENTS) diff --git a/crates/scroll/state-commitment/Cargo.toml b/crates/scroll/state-commitment/Cargo.toml index 35312faeda26..b9173e3cf05f 100644 --- a/crates/scroll/state-commitment/Cargo.toml +++ b/crates/scroll/state-commitment/Cargo.toml @@ -9,10 +9,11 @@ repository.workspace = true exclude.workspace = true [dependencies] +alloy-consensus = { workspace = true, optional = true} alloy-primitives.workspace = true alloy-rlp.workspace = true reth-execution-errors.workspace = true -reth-trie.workspace = true +reth-trie = { workspace = true, features = ["scroll"]} reth-primitives.workspace = true tracing.workspace = true scroll-trie.workspace = true @@ -25,10 +26,15 @@ reth-db = { workspace = true, features = ["test-utils"] } reth-metrics = { workspace = true, optional = true } metrics = { workspace = true, optional = true } +# zktrie +zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie", optional = true } +zktrie = { path = "/Users/f/dev/scroll/zktrie", features = ["rs_zktrie"], optional = true } + [dev-dependencies] +scroll-state-commitment = { workspace = true, features = ["test-utils"]} alloy-consensus.workspace = true reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } -reth-trie = { workspace = true, features = ["test-utils"] } +reth-trie = { workspace = true, features = ["test-utils", "scroll"] } reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } reth-provider = { workspace = true, features = ["test-utils"] } proptest.workspace = true @@ -36,11 +42,13 @@ proptest-arbitrary-interop.workspace = true zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie" } zktrie = { path = "/Users/f/dev/scroll/zktrie", features = ["rs_zktrie"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] } +reth-db-api.workspace = true [lints] workspace = true [features] +test-utils = ["dep:zktrie_rust", "dep:zktrie", "dep:alloy-consensus"] metrics = ["reth-metrics", "dep:metrics"] diff --git a/crates/scroll/state-commitment/src/lib.rs b/crates/scroll/state-commitment/src/lib.rs index ca07610dfdc5..430bcd6dccac 100644 --- a/crates/scroll/state-commitment/src/lib.rs +++ b/crates/scroll/state-commitment/src/lib.rs @@ -6,6 +6,10 @@ pub use root::{StateRoot, StorageRoot}; mod key; mod value; +/// test utils for the state commitment +#[cfg(feature = "test-utils")] +pub mod test_utils; + // RE-EXPORTS pub use key::PoseidonKeyHasher; pub use value::PosiedonValueHasher; diff --git a/crates/scroll/state-commitment/src/root.rs b/crates/scroll/state-commitment/src/root.rs index 1836109c48e7..2d5774fce33d 100644 --- a/crates/scroll/state-commitment/src/root.rs +++ b/crates/scroll/state-commitment/src/root.rs @@ -2,7 +2,6 @@ use super::{PoseidonKeyHasher, PosiedonValueHasher}; use alloy_primitives::{Address, BlockNumber, B256}; use reth_db::transaction::DbTx; use reth_execution_errors::{StateRootError, StorageRootError}; -use reth_primitives::constants::EMPTY_ROOT_HASH; use reth_trie::{ hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory, HashedStorageCursor}, key::BitsCompatibility, @@ -12,17 +11,18 @@ use reth_trie::{ trie_cursor::{InMemoryTrieCursorFactory, TrieCursorFactory}, updates::{StorageTrieUpdates, TrieUpdates}, walker::TrieWalker, - HashedPostState, IntermediateStateRootState, Nibbles, StateRootProgress, TrieAccount, - TrieInput, + HashedPostState, HashedStorage, IntermediateStateRootState, Nibbles, StateRootProgress, + TrieAccount, TrieInput, }; -use scroll_primitives::ScrollTrieAccount; +use scroll_primitives::{poseidon::EMPTY_ROOT_HASH, ScrollTrieAccount}; use scroll_trie::HashBuilder; use tracing::{debug, trace}; #[cfg(feature = "metrics")] use crate::metrics::{StateRootMetrics, TrieRootMetrics}; -// TODO(frisitano): Instead of introducing this new type we should +// TODO(frisitano): Instead of introducing this new type we should make StateRoot generic over +// the [`HashBuilder`] and key traversal types /// `StateRoot` is used to compute the root node of a state trie. #[derive(Debug)] @@ -467,7 +467,8 @@ where } use reth_trie_db::{ - DatabaseHashedCursorFactory, DatabaseStateRoot, DatabaseTrieCursorFactory, PrefixSetLoader, + DatabaseHashedCursorFactory, DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory, + PrefixSetLoader, }; use std::ops::RangeInclusive; @@ -561,269 +562,49 @@ impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> } } -#[cfg(test)] -mod test { - use super::StateRoot; - use alloy_consensus::constants::KECCAK_EMPTY; - use alloy_primitives::{ - aliases::U248, b256, hex_literal::hex, keccak256, Address, FixedBytes, Uint, B256, U256, - }; - use proptest::{prelude::ProptestConfig, proptest}; - use proptest_arbitrary_interop::arb; - use reth_db::{ - cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, - tables, - test_utils::TempDatabase, - transaction::{DbTx, DbTxMut}, - DatabaseEnv, - }; - use reth_primitives::{constants::EMPTY_ROOT_HASH, Account, StorageEntry}; - use reth_provider::{ - test_utils::create_test_provider_factory, DatabaseProviderRW, StorageTrieWriter, TrieWriter, - }; - use reth_trie::{ - prefix_set::PrefixSetMut, - test_utils::{state_root, state_root_prehashed, storage_root, storage_root_prehashed}, - BranchNodeCompact, StorageRoot, TrieMask, - }; - use reth_trie_db::{DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory}; - use std::{ - collections::{BTreeMap, HashMap}, - ops::Mul, - str::FromStr, - sync::Arc, - }; - - use alloy_rlp::Encodable; - use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; - use reth_trie::{ - prefix_set::TriePrefixSets, trie_cursor::InMemoryTrieCursorFactory, - updates::StorageTrieUpdates, HashedPostState, IntermediateStateRootState, Nibbles, - StateRootProgress, TrieAccount, - }; - use std::sync::Once; - use tracing_subscriber::{self, fmt::format::FmtSpan}; - use zktrie::HashField; - use zktrie_rust::{ - db::SimpleDb, - hash::AsHash, - types::{Hashable, TrieHashScheme}, - }; - - static INIT: Once = Once::new(); - - pub fn init_test_logger() { - INIT.call_once(|| { - tracing_subscriber::fmt() - .with_test_writer() // Capture logs for test output - .with_span_events(FmtSpan::CLOSE) // Optional: Add span events - .with_env_filter("trace") // Set log level as needed - .init(); - }); - } - - // TODO(frisitano): Clean up tests and add tests for storage trie. - - type State = BTreeMap)>; - - proptest! { - #![proptest_config(ProptestConfig { - cases: 1, ..ProptestConfig::default() - })] - - #[test] - fn fuzz_in_memory_account_nodes(mut init_state: BTreeMap)>, state_updates: [BTreeMap>; 4]) { - // init_test_logger(); - - let mut init_state: BTreeMap = init_state.into_iter().take(1).map(|(mut address, (nonce, mut balance, bytecode_hash))| { - // set the largest byte to 0 - >::as_mut(&mut address)[31] = 0; - // set the most significant 8 bits to 0 - unsafe { - balance.as_limbs_mut()[3] &= 0x00FFFFFFFFFFFFFF; - } - let account = Account { balance, nonce: nonce.into(), bytecode_hash }; - (address, account) - }).collect(); - let state_updates: Vec> = state_updates.into_iter().map(|update| { - let update = update.into_iter().take(1).map(|(mut address, mut update)| { - // set the largest byte to 0 - >::as_mut(&mut address)[31] = 0; - // set the most significant 8 bits to 0 - let account = if let Some(mut balance) = update { - unsafe { - balance.as_limbs_mut()[3] &= 0x00FFFFFFFFFFFFFF; - } - Some(Account { balance, ..Default::default() }) - } else { None }; - (address, account) - }).collect::>(); - update - }).collect(); - - - let factory = create_test_provider_factory(); - let provider = factory.provider_rw().unwrap(); - let mut hashed_account_cursor = provider.tx_ref().cursor_write::().unwrap(); - - // Insert init state into database - for (hashed_address, account) in init_state.clone().into_iter() { - hashed_account_cursor.upsert(reverse_bits(hashed_address), account).unwrap(); - } - - // Compute initial root and updates - let (_, mut trie_nodes) = StateRoot::from_tx(provider.tx_ref()) - .root_with_updates() - .unwrap(); - - let mut state = init_state; - for state_update in state_updates { - // Insert state updates into database - let mut hashed_state = HashedPostState::default(); - for (hashed_address, account) in state_update.into_iter().take(4) { - if let Some(account) = account { - hashed_account_cursor.upsert(reverse_bits(hashed_address), account).unwrap(); - hashed_state.accounts.insert(reverse_bits(hashed_address), Some(account)); - state.insert(hashed_address, account); - } else { - hashed_state.accounts.insert(reverse_bits(hashed_address), None); - state.remove(&hashed_address); - } - } - - // Compute root with in-memory trie nodes overlay - let (state_root, trie_updates) = StateRoot::from_tx(provider.tx_ref()) - .with_prefix_sets(hashed_state.construct_prefix_sets().freeze()) - .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( - DatabaseTrieCursorFactory::new(provider.tx_ref()), &trie_nodes.clone().into_sorted()) - ) - .root_with_updates() - .unwrap(); - - trie_nodes.extend(trie_updates); - - // Verify the result - let expected_root = state_root_zktrie( - state.iter().map(|(key, account)| (*key, (*account, std::iter::empty()))) - ); - assert_eq!(expected_root.0, state_root.0); - - } - } - - - } - - fn reverse_bits(b256: B256) -> B256 { - let mut b256 = b256.0; - for byte in b256.iter_mut() { - *byte = byte.reverse_bits(); - } - B256::from(b256) - } - - fn state_root_zktrie(accounts: I) -> B256 - where - I: IntoIterator, - S: IntoIterator, - { - let mut trie = zktrie(); - const COMPRESSION_FLAG: u32 = 8; - for (address, (account, storage)) in accounts.into_iter() { - let mut key = address.0; - key.reverse(); - let key = AsHash::from_bytes(&key).unwrap(); - let mut account_bytes = Vec::with_capacity(5); - - account_bytes.push(U256::from_limbs([account.nonce, 0, 0, 0]).to_be_bytes()); - account_bytes.push(account.balance.to_be_bytes()); - account_bytes.push([0u8; 32]); - account_bytes.push(account.bytecode_hash.unwrap_or(KECCAK_EMPTY).0); - account_bytes.push([0u8; 32]); - - // for bytes in account_bytes.iter() { - // println!("{:?}", bytes); - // } - - trie.try_update(&key, COMPRESSION_FLAG, account_bytes).unwrap(); - } - trie.prepare_root().unwrap(); - let mut root = trie.root().to_bytes(); - root.reverse(); - B256::from_slice(&root) - // 00 - } - - #[test] - fn test_basic_state_root_with_updates_succeeds() { - let address_1 = Address::with_last_byte(0); - let address_2 = Address::with_last_byte(3); - let address_3 = Address::with_last_byte(7); - let account_1 = Account { balance: Uint::from(1), ..Default::default() }; - let account_2 = Account { balance: Uint::from(2), ..Default::default() }; - let account_3 = Account { balance: Uint::from(3), ..Default::default() }; - - let factory = create_test_provider_factory(); - let tx = factory.provider_rw().unwrap(); - - insert_account(tx.tx_ref(), address_1, account_1, &Default::default()); - insert_account(tx.tx_ref(), address_2, account_2, &Default::default()); - insert_account(tx.tx_ref(), address_3, account_3, &Default::default()); - - tx.commit().unwrap(); - - let tx = factory.provider_rw().unwrap(); - let (root, updates) = StateRoot::from_tx(tx.tx_ref()).root_with_updates().unwrap(); +impl<'a, TX: DbTx> DatabaseStorageRoot<'a, TX> + for StorageRoot, DatabaseHashedCursorFactory<'a, TX>> +{ + fn from_tx(tx: &'a TX, address: Address) -> Self { + Self::new( + DatabaseTrieCursorFactory::new(tx), + DatabaseHashedCursorFactory::new(tx), + address, + #[cfg(feature = "metrics")] + TrieRootMetrics::new(TrieType::Storage), + ) } - fn test_state_root_with_state(state: State) { - let factory = create_test_provider_factory(); - let tx = factory.provider_rw().unwrap(); - - for (address, (account, storage)) in &state { - insert_account(tx.tx_ref(), *address, *account, storage) - } - tx.commit().unwrap(); - let expected = state_root(state); - - let tx = factory.provider_rw().unwrap(); - let got = StateRoot::from_tx(tx.tx_ref()).root().unwrap(); - assert_eq!(expected, got); + fn from_tx_hashed(tx: &'a TX, hashed_address: B256) -> Self { + Self::new_hashed( + DatabaseTrieCursorFactory::new(tx), + DatabaseHashedCursorFactory::new(tx), + hashed_address, + #[cfg(feature = "metrics")] + TrieRootMetrics::new(TrieType::Storage), + ) } - fn insert_account( - tx: &impl DbTxMut, + fn overlay_root( + tx: &'a TX, address: Address, - account: Account, - storage: &BTreeMap, - ) { - let hashed_address = keccak256(address); - tx.put::(hashed_address, account).unwrap(); - insert_storage(tx, hashed_address, storage); - } - - fn insert_storage(tx: &impl DbTxMut, hashed_address: B256, storage: &BTreeMap) { - for (k, v) in storage { - tx.put::( - hashed_address, - StorageEntry { key: keccak256(k), value: *v }, - ) - .unwrap(); - } - } - - fn poseidon_hash_scheme(a: &[u8; 32], b: &[u8; 32], domain: &[u8; 32]) -> Option<[u8; 32]> { - let a = Fr::from_repr_vartime(*a)?; - let b = Fr::from_repr_vartime(*b)?; - let domain = Fr::from_repr_vartime(*domain)?; - Some(hash_with_domain(&[a, b], domain).to_repr()) - } - - fn zktrie() -> zktrie_rust::raw::ZkTrieImpl, SimpleDb, 248> { - zktrie::init_hash_scheme_simple(poseidon_hash_scheme); - zktrie_rust::raw::ZkTrieImpl::, SimpleDb, 248>::new_zktrie_impl( - SimpleDb::new(), + hashed_storage: HashedStorage, + ) -> Result { + let prefix_set = hashed_storage.construct_prefix_set().freeze(); + // TODO: replace keccak with KH: KeyHasher when integrating with upstream + let state_sorted = HashedPostState::from_hashed_storage( + alloy_primitives::keccak256(address), + hashed_storage, ) - .unwrap() + .into_sorted(); + StorageRoot::new( + DatabaseTrieCursorFactory::new(tx), + HashedPostStateCursorFactory::new(DatabaseHashedCursorFactory::new(tx), &state_sorted), + address, + #[cfg(feature = "metrics")] + TrieRootMetrics::new(TrieType::Storage), + ) + .with_prefix_set(prefix_set) + .root() } } diff --git a/crates/scroll/state-commitment/src/test_utils.rs b/crates/scroll/state-commitment/src/test_utils.rs new file mode 100644 index 000000000000..aaf018c58100 --- /dev/null +++ b/crates/scroll/state-commitment/src/test_utils.rs @@ -0,0 +1,95 @@ +use alloy_consensus::constants::KECCAK_EMPTY; +use alloy_primitives::{B256, U256}; +use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; +use reth_primitives::Account; +use zktrie::HashField; +use zktrie_rust::{db::SimpleDb, hash::AsHash, types::Hashable}; + +const ACCOUNT_COMPRESSION_FLAG: u32 = 8; +const STORAGE_COMPRESSION_FLAG: u32 = 1; + +/// Reverses the ordering of bits in a [`B256`] type. +pub fn b256_reverse_bits(b256: B256) -> B256 { + let mut b256 = b256.0; + for byte in b256.iter_mut() { + *byte = byte.reverse_bits(); + } + B256::from(b256) +} + +/// Clear the most significant byte of a [`B256`] type. +pub fn b256_clear_msb(mut b256: B256) -> B256 { + // set the largest byte to 0 + >::as_mut(&mut b256)[31] = 0; + b256 +} + +/// Clear the most significant byte of a [`U256`] type. +pub fn u256_clear_msb(mut balance: U256) -> U256 { + // set the most significant 8 bits to 0 + unsafe { + balance.as_limbs_mut()[3] &= 0x00FFFFFFFFFFFFFF; + } + balance +} + +/// Calculates the state root of a set of accounts and their storage entries using the zktrie +/// implementation. +pub fn state_root(accounts: I) -> B256 +where + I: IntoIterator, + S: IntoIterator, +{ + let mut trie = zktrie(); + for (address, (account, storage)) in accounts.into_iter() { + let key = parse_key_as_hash(address); + let mut account_bytes = Vec::with_capacity(5); + + account_bytes.push(U256::from_limbs([account.nonce, 0, 0, 0]).to_be_bytes()); + account_bytes.push(account.balance.to_be_bytes()); + account_bytes.push(storage_root(storage).0); + account_bytes.push(account.bytecode_hash.unwrap_or(KECCAK_EMPTY).0); + account_bytes.push([0u8; 32]); + + trie.try_update(&key, ACCOUNT_COMPRESSION_FLAG, account_bytes).unwrap(); + } + trie.prepare_root().unwrap(); + let root = trie.root().to_bytes(); + B256::from_slice(&root) +} + +/// Calculates the storage root of a set of storage entries using the zktrie implementation. +pub fn storage_root(storage: S) -> B256 +where + S: IntoIterator, +{ + let mut storage_trie = zktrie(); + for (key, value) in storage.into_iter() { + let key = parse_key_as_hash(key); + storage_trie.try_update(&key, STORAGE_COMPRESSION_FLAG, vec![value.to_be_bytes()]).unwrap(); + } + storage_trie.prepare_root().unwrap(); + let root = storage_trie.root().to_bytes(); + B256::from_slice(&root) +} + +fn parse_key_as_hash(key: B256) -> AsHash { + let mut key = key.0; + key.reverse(); + AsHash::from_bytes(&key).unwrap() +} + +fn poseidon_hash_scheme(a: &[u8; 32], b: &[u8; 32], domain: &[u8; 32]) -> Option<[u8; 32]> { + let a = Fr::from_repr_vartime(*a)?; + let b = Fr::from_repr_vartime(*b)?; + let domain = Fr::from_repr_vartime(*domain)?; + Some(hash_with_domain(&[a, b], domain).to_repr()) +} + +fn zktrie() -> zktrie_rust::raw::ZkTrieImpl, SimpleDb, 248> { + zktrie::init_hash_scheme_simple(poseidon_hash_scheme); + zktrie_rust::raw::ZkTrieImpl::, SimpleDb, 248>::new_zktrie_impl( + SimpleDb::new(), + ) + .unwrap() +} diff --git a/crates/scroll/state-commitment/tests/trie.rs b/crates/scroll/state-commitment/tests/trie.rs new file mode 100644 index 000000000000..fe2c54f432c8 --- /dev/null +++ b/crates/scroll/state-commitment/tests/trie.rs @@ -0,0 +1,244 @@ +#![allow(missing_docs)] + +use alloy_primitives::{Address, Uint, B256, U256}; +use proptest::{prelude::ProptestConfig, proptest}; +use proptest_arbitrary_interop::arb; +use reth_db::{ + cursor::{DbCursorRO, DbCursorRW, DbDupCursorRW}, + tables, + transaction::DbTxMut, +}; +use reth_primitives::{Account, StorageEntry}; +use reth_provider::test_utils::create_test_provider_factory; +use reth_trie::{ + trie_cursor::InMemoryTrieCursorFactory, updates::TrieUpdates, HashedPostState, HashedStorage, +}; +use reth_trie_db::{DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory}; +use scroll_state_commitment::{ + test_utils::{b256_clear_msb, b256_reverse_bits, u256_clear_msb}, + PoseidonKeyHasher, StateRoot, StorageRoot, +}; +use std::{collections::BTreeMap, sync::Once}; +use tracing_subscriber::{self, fmt::format::FmtSpan}; + +static INIT: Once = Once::new(); + +pub fn init_test_logger() { + INIT.call_once(|| { + tracing_subscriber::fmt() + .with_test_writer() // Capture logs for test output + .with_span_events(FmtSpan::CLOSE) // Optional: Add span events + .with_env_filter("trace") // Set log level as needed + .init(); + }); +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 6, ..ProptestConfig::default() + })] + + #[test] + fn fuzz_in_memory_account_nodes(mut init_state: BTreeMap)>, state_updates: [BTreeMap>; 10]) { + let init_state: BTreeMap = init_state.into_iter().map(|(hashed_address, (nonce, balance, bytecode_hash))| { + let hashed_address = b256_clear_msb(hashed_address); + let balance = u256_clear_msb(balance); + let account = Account { balance, nonce: nonce.into(), bytecode_hash }; + (hashed_address, account) + }).collect(); + let state_updates: Vec> = state_updates.into_iter().map(|update| { + let update = update.into_iter().map(|(hashed_address, update)| { + let hashed_address = b256_clear_msb(hashed_address); + let account = if let Some(balance) = update { + Some(Account { balance: u256_clear_msb(balance), ..Default::default() }) + } else { None }; + (hashed_address, account) + }).collect::>(); + update + }).collect(); + + + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + let mut hashed_account_cursor = provider.tx_ref().cursor_write::().unwrap(); + + // Insert init state into database + for (hashed_address, account) in init_state.clone().into_iter() { + hashed_account_cursor.upsert(b256_reverse_bits(hashed_address), account).unwrap(); + } + + // Compute initial root and updates + let (_, mut trie_nodes) = StateRoot::from_tx(provider.tx_ref()) + .root_with_updates() + .unwrap(); + + let mut state = init_state; + for state_update in state_updates { + // Insert state updates into database + let mut hashed_state = HashedPostState::default(); + for (hashed_address, account) in state_update.into_iter() { + if let Some(account) = account { + hashed_account_cursor.upsert(b256_reverse_bits(hashed_address), account).unwrap(); + hashed_state.accounts.insert(b256_reverse_bits(hashed_address), Some(account)); + state.insert(hashed_address, account); + } else { + hashed_state.accounts.insert(b256_reverse_bits(hashed_address), None); + state.remove(&hashed_address); + } + } + + // Compute root with in-memory trie nodes overlay + let (state_root, trie_updates) = StateRoot::from_tx(provider.tx_ref()) + .with_prefix_sets(hashed_state.construct_prefix_sets().freeze()) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( + DatabaseTrieCursorFactory::new(provider.tx_ref()), &trie_nodes.clone().into_sorted()) + ) + .root_with_updates() + .unwrap(); + + trie_nodes.extend(trie_updates); + + // Verify the result + let expected_root = scroll_state_commitment::test_utils::state_root( + state.iter().map(|(key, account)| (*key, (*account, std::iter::empty()))) + ); + assert_eq!(expected_root.0, state_root.0); + + } + } + + #[test] + fn fuzz_in_memory_storage_nodes(mut init_storage: BTreeMap, storage_updates: [(bool, BTreeMap); 10]) { + let hashed_address = B256::random(); + let factory = create_test_provider_factory(); + let provider = factory.provider_rw().unwrap(); + let mut hashed_storage_cursor = + provider.tx_ref().cursor_write::().unwrap(); + + // Insert init state into database + let init_storage: BTreeMap = init_storage.into_iter().map(|(slot, value)| { + let hashed_slot = b256_clear_msb(slot); + hashed_storage_cursor + .upsert(hashed_address, StorageEntry { key: b256_reverse_bits(hashed_slot), value }) + .unwrap(); + (hashed_slot, value) + }).collect(); + let storage_updates: Vec<(bool, BTreeMap)> = storage_updates.into_iter().map(|(is_deleted, updates)| { + let updates = updates.into_iter().map(|(slot, value)| { + let slot = b256_clear_msb(slot); + (slot, value) + }).collect(); + (is_deleted, updates) + }).collect(); + + // Compute initial storage root and updates + let (_, _, mut storage_trie_nodes) = + StorageRoot::from_tx_hashed(provider.tx_ref(), hashed_address).root_with_updates().unwrap(); + + let mut storage = init_storage; + for (is_deleted, mut storage_update) in storage_updates { + // Insert state updates into database + if is_deleted && hashed_storage_cursor.seek_exact(hashed_address).unwrap().is_some() { + hashed_storage_cursor.delete_current_duplicates().unwrap(); + } + let mut hashed_storage = HashedStorage::new(is_deleted); + for (hashed_slot, value) in storage_update.clone() { + hashed_storage_cursor + .upsert(hashed_address, StorageEntry { key: b256_reverse_bits(hashed_slot), value }) + .unwrap(); + hashed_storage.storage.insert(b256_reverse_bits(hashed_slot), value); + } + + // Compute root with in-memory trie nodes overlay + let mut trie_nodes = TrieUpdates::default(); + trie_nodes.insert_storage_updates(hashed_address, storage_trie_nodes.clone()); + let (storage_root, _, trie_updates) = + StorageRoot::from_tx_hashed(provider.tx_ref(), hashed_address) + .with_prefix_set(hashed_storage.construct_prefix_set().freeze()) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( + DatabaseTrieCursorFactory::new(provider.tx_ref()), + &trie_nodes.into_sorted(), + )) + .root_with_updates() + .unwrap(); + + storage_trie_nodes.extend(trie_updates); + + // Verify the result + if is_deleted { + storage.clear(); + } + storage.append(&mut storage_update); + let expected_root = scroll_state_commitment::test_utils::storage_root(storage.clone()); + assert_eq!(expected_root, storage_root); + } + } +} + +#[test] +fn test_basic_state_root_with_updates_succeeds() { + let address_1 = Address::with_last_byte(0); + let address_2 = Address::with_last_byte(3); + let address_3 = Address::with_last_byte(7); + let account_1 = Account { balance: Uint::from(1), ..Default::default() }; + let account_2 = Account { balance: Uint::from(2), ..Default::default() }; + let account_3 = Account { balance: Uint::from(3), ..Default::default() }; + + let factory = create_test_provider_factory(); + let tx = factory.provider_rw().unwrap(); + + insert_account(tx.tx_ref(), address_1, account_1, &Default::default()); + insert_account(tx.tx_ref(), address_2, account_2, &Default::default()); + insert_account(tx.tx_ref(), address_3, account_3, &Default::default()); + + tx.commit().unwrap(); + + let tx = factory.provider_rw().unwrap(); + let (_root, _updates) = StateRoot::from_tx(tx.tx_ref()).root_with_updates().unwrap(); +} + +fn insert_account( + tx: &impl DbTxMut, + address: Address, + account: Account, + storage: &BTreeMap, +) { + let hashed_address = PoseidonKeyHasher::hash_key(address); + tx.put::(hashed_address, account).unwrap(); + insert_storage(tx, hashed_address, storage); +} + +fn insert_storage(tx: &impl DbTxMut, hashed_address: B256, storage: &BTreeMap) { + for (k, v) in storage { + tx.put::( + hashed_address, + StorageEntry { key: PoseidonKeyHasher::hash_key(k), value: *v }, + ) + .unwrap(); + } +} +#[test] +fn arbitrary_storage_root() { + proptest!(ProptestConfig::with_cases(10), |(item in arb::<(Address, std::collections::BTreeMap)>())| { + let (address, storage) = item; + + let hashed_address = PoseidonKeyHasher::hash_key(address); + let factory = create_test_provider_factory(); + let tx = factory.provider_rw().unwrap(); + let storage: BTreeMap = storage.into_iter().map(|(key, value)| { + let key = b256_clear_msb(key); + tx.tx_ref().put::( + hashed_address, + StorageEntry { key, value: value }, + ) + .unwrap(); + (b256_reverse_bits(key), value) + }).collect(); + tx.commit().unwrap(); + + let tx = factory.provider_rw().unwrap(); + let got = StorageRoot::from_tx(tx.tx_ref(), address).root().unwrap(); + let expected = scroll_state_commitment::test_utils::storage_root(storage.into_iter()); + assert_eq!(expected, got); + }); +} diff --git a/crates/scroll/trie/Cargo.toml b/crates/scroll/trie/Cargo.toml index cd1debd3d96b..c8113dba0e62 100644 --- a/crates/scroll/trie/Cargo.toml +++ b/crates/scroll/trie/Cargo.toml @@ -12,7 +12,7 @@ exclude.workspace = true alloy-trie = { workspace = true, features = ["serde"] } alloy-primitives.workspace = true scroll-primitives.workspace = true -tracing = { version = "0.1", default-features = false } +tracing.workspace = true reth-trie.workspace = true [dev-dependencies] diff --git a/crates/scroll/trie/src/branch.rs b/crates/scroll/trie/src/branch.rs index e6b92dba7fd3..78753e721b02 100644 --- a/crates/scroll/trie/src/branch.rs +++ b/crates/scroll/trie/src/branch.rs @@ -97,10 +97,10 @@ impl<'a> BranchNodeRef<'a> { fn hashing_domain(&self) -> Fr { match *self.state_mask { - 0b1011 => Fr::from(BRANCH_NODE_LBRT_DOMAIN), - 0b1111 => Fr::from(BRANCH_NODE_LTRT_DOMAIN), - 0b0111 => Fr::from(BRANCH_NODE_LTRB_DOMAIN), - 0b0011 => Fr::from(BRANCH_NODE_LBRB_DOMAIN), + 0b1011 => BRANCH_NODE_LBRT_DOMAIN, + 0b1111 => BRANCH_NODE_LTRT_DOMAIN, + 0b0111 => BRANCH_NODE_LTRB_DOMAIN, + 0b0011 => BRANCH_NODE_LBRB_DOMAIN, _ => unreachable!("invalid branch node state mask"), } } diff --git a/crates/scroll/trie/src/hash_builder.rs b/crates/scroll/trie/src/hash_builder.rs index e79435e1c9f6..8598137bf586 100644 --- a/crates/scroll/trie/src/hash_builder.rs +++ b/crates/scroll/trie/src/hash_builder.rs @@ -8,9 +8,10 @@ use alloy_trie::{ hash_builder::{HashBuilderValue, HashBuilderValueRef}, nodes::LeafNodeRef, proof::{ProofNodes, ProofRetainer}, - BranchNodeCompact, Nibbles, TrieMask, EMPTY_ROOT_HASH, + BranchNodeCompact, Nibbles, TrieMask, }; use core::cmp; +use scroll_primitives::poseidon::EMPTY_ROOT_HASH; use tracing::trace; #[derive(Debug, Default)] @@ -142,7 +143,9 @@ impl HashBuilder { fn current_root(&self) -> B256 { if let Some(node_ref) = self.stack.last() { - *node_ref + let mut root = *node_ref; + root.reverse(); + root } else { EMPTY_ROOT_HASH } @@ -525,7 +528,7 @@ mod test { let key_fr = Fr::from_repr_vartime(key.encode_leaf_key()) .expect("key is valid field element"); let value = Fr::from_repr_vartime(*value).expect("value is a valid field element"); - let hash = hash_with_domain(&[key_fr, value], crate::LEAF_NODE_DOMAIN.into()); + let hash = hash_with_domain(&[key_fr, value], crate::LEAF_NODE_DOMAIN); (key.clone(), hash) }) .collect(); @@ -549,26 +552,21 @@ mod test { let expected: B256 = { let node_000 = hash_with_domain( &[*leaf_hashes.get(&leaf_1_key).unwrap(), *leaf_hashes.get(&leaf_2_key).unwrap()], - crate::BRANCH_NODE_LTRT_DOMAIN.into(), + crate::BRANCH_NODE_LTRT_DOMAIN, ); let node_00 = hash_with_domain( &[node_000, *leaf_hashes.get(&leaf_3_key).unwrap()], - crate::BRANCH_NODE_LBRT_DOMAIN.into(), + crate::BRANCH_NODE_LBRT_DOMAIN, ); - let node_0 = - hash_with_domain(&[node_00, Fr::zero()], crate::BRANCH_NODE_LBRT_DOMAIN.into()); + let node_0 = hash_with_domain(&[node_00, Fr::zero()], crate::BRANCH_NODE_LBRT_DOMAIN); let node_111 = hash_with_domain( &[*leaf_hashes.get(&leaf_4_key).unwrap(), *leaf_hashes.get(&leaf_5_key).unwrap()], - crate::BRANCH_NODE_LTRT_DOMAIN.into(), + crate::BRANCH_NODE_LTRT_DOMAIN, ); - let node_11 = - hash_with_domain(&[Fr::zero(), node_111], crate::BRANCH_NODE_LTRB_DOMAIN.into()); - let node_1 = - hash_with_domain(&[Fr::zero(), node_11], crate::BRANCH_NODE_LTRB_DOMAIN.into()); - - hash_with_domain(&[node_0, node_1], crate::BRANCH_NODE_LBRB_DOMAIN.into()) - .to_repr() - .into() + let node_11 = hash_with_domain(&[Fr::zero(), node_111], crate::BRANCH_NODE_LTRB_DOMAIN); + let node_1 = hash_with_domain(&[Fr::zero(), node_11], crate::BRANCH_NODE_LTRB_DOMAIN); + + hash_with_domain(&[node_0, node_1], crate::BRANCH_NODE_LBRB_DOMAIN).to_repr().into() }; assert_eq!(expected, root); diff --git a/crates/scroll/trie/src/leaf.rs b/crates/scroll/trie/src/leaf.rs index ce1692c95333..b58651ee93c9 100644 --- a/crates/scroll/trie/src/leaf.rs +++ b/crates/scroll/trie/src/leaf.rs @@ -18,6 +18,6 @@ impl HashLeaf for LeafNodeRef<'_> { ) .expect("leaf value is a valid field element"); println!("leaf value: {:?}", leaf_value.to_repr()); - hash_with_domain(&[leaf_key, leaf_value], Fr::from(LEAF_NODE_DOMAIN)).to_repr().into() + hash_with_domain(&[leaf_key, leaf_value], LEAF_NODE_DOMAIN).to_repr().into() } } diff --git a/crates/scroll/trie/src/lib.rs b/crates/scroll/trie/src/lib.rs index 71cb6fb2b6db..df147e4e6e2c 100644 --- a/crates/scroll/trie/src/lib.rs +++ b/crates/scroll/trie/src/lib.rs @@ -9,18 +9,19 @@ mod hash_builder; pub use hash_builder::HashBuilder; mod leaf; mod sub_tree; +use scroll_primitives::poseidon::Fr; /// The hashing domain for leaf nodes. -pub const LEAF_NODE_DOMAIN: u64 = 4; +pub const LEAF_NODE_DOMAIN: Fr = Fr::from_raw([4, 0, 0, 0]); /// The hashing domain for a branch node with two terminal children. -pub const BRANCH_NODE_LTRT_DOMAIN: u64 = 6; +pub const BRANCH_NODE_LTRT_DOMAIN: Fr = Fr::from_raw([6, 0, 0, 0]); /// The hashing domain for a branch node with a left terminal child and a right branch child. -pub const BRANCH_NODE_LTRB_DOMAIN: u64 = 7; +pub const BRANCH_NODE_LTRB_DOMAIN: Fr = Fr::from_raw([7, 0, 0, 0]); /// The hashing domain for a branch node with a left branch child and a right terminal child. -pub const BRANCH_NODE_LBRT_DOMAIN: u64 = 8; +pub const BRANCH_NODE_LBRT_DOMAIN: Fr = Fr::from_raw([8, 0, 0, 0]); /// The hashing domain for a branch node with two branch children. -pub const BRANCH_NODE_LBRB_DOMAIN: u64 = 9; +pub const BRANCH_NODE_LBRB_DOMAIN: Fr = Fr::from_raw([9, 0, 0, 0]); diff --git a/crates/scroll/trie/src/sub_tree.rs b/crates/scroll/trie/src/sub_tree.rs index 7eb722021a85..de995b78c2a2 100644 --- a/crates/scroll/trie/src/sub_tree.rs +++ b/crates/scroll/trie/src/sub_tree.rs @@ -25,9 +25,9 @@ impl<'a> SubTreeRef<'a> { Fr::from_repr_vartime(self.child.0).expect("child is a valid field element"); for bit in self.key.as_slice().iter().rev() { tree_root = if *bit == 0 { - hash_with_domain(&[tree_root, Fr::zero()], Fr::from(BRANCH_NODE_LBRT_DOMAIN)) + hash_with_domain(&[tree_root, Fr::zero()], BRANCH_NODE_LBRT_DOMAIN) } else { - hash_with_domain(&[Fr::zero(), tree_root], Fr::from(BRANCH_NODE_LTRB_DOMAIN)) + hash_with_domain(&[Fr::zero(), tree_root], BRANCH_NODE_LTRB_DOMAIN) }; } tree_root.to_repr().into() diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index 800b7406f791..f25e381c906e 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -76,6 +76,7 @@ metrics = ["reth-metrics", "dep:metrics"] serde = ["dep:serde"] serde-bincode-compat = ["serde_with"] test-utils = ["triehash", "reth-trie-common/test-utils"] +scroll = [] [[bench]] name = "prefix_set" diff --git a/crates/trie/trie/src/node_iter.rs b/crates/trie/trie/src/node_iter.rs index e3563a33a3e3..f9325ea97b03 100644 --- a/crates/trie/trie/src/node_iter.rs +++ b/crates/trie/trie/src/node_iter.rs @@ -1,10 +1,10 @@ -use crate::{ - hashed_cursor::HashedCursor, key::BitsCompatibility, trie_cursor::TrieCursor, - walker::TrieWalker, Nibbles, -}; +use crate::{hashed_cursor::HashedCursor, trie_cursor::TrieCursor, walker::TrieWalker, Nibbles}; use alloy_primitives::B256; use reth_storage_errors::db::DatabaseError; +#[cfg(feature = "scroll")] +use crate::key::BitsCompatibility; + /// Represents a branch node in the trie. #[derive(Debug)] pub struct TrieBranchNode { @@ -110,8 +110,11 @@ where // If the walker's key is less than the unpacked hashed key, // reset the checked status and continue if self.walker.key().map_or(false, |key| { - // TODO(frisitano): replace this with key abstraction. - key < &Nibbles::unpack_and_truncate_bits(hashed_key) + #[cfg(not(feature = "scroll"))] + let cmp = key < &Nibbles::unpack(hashed_key); + #[cfg(feature = "scroll")] + let cmp = key < &Nibbles::unpack_and_truncate_bits(hashed_key); + cmp }) { self.current_walker_key_checked = false; continue diff --git a/crates/trie/trie/src/proof.rs b/crates/trie/trie/src/proof.rs index 1f5385cb6281..e99d686aca7f 100644 --- a/crates/trie/trie/src/proof.rs +++ b/crates/trie/trie/src/proof.rs @@ -1,6 +1,5 @@ use crate::{ hashed_cursor::{HashedCursorFactory, HashedStorageCursor}, - key::BitsCompatibility, node_iter::{TrieElement, TrieNodeIter}, prefix_set::{PrefixSetMut, TriePrefixSetsMut}, trie_cursor::TrieCursorFactory, @@ -97,13 +96,11 @@ where // Create the walker. let mut prefix_set = self.prefix_sets.account_prefix_set.clone(); - // TODO(frisitano): replace this with key abstraction. - prefix_set.extend_keys(targets.keys().map(|x| Nibbles::unpack_and_truncate_bits(x))); + prefix_set.extend_keys(targets.keys().map(Nibbles::unpack)); let walker = TrieWalker::new(trie_cursor, prefix_set.freeze()); // Create a hash builder to rebuild the root node since it is not available in the database. - // TODO(frisitano): replace this with key abstraction. - let retainer = targets.keys().map(|x| Nibbles::unpack_and_truncate_bits(x)).collect(); + let retainer = targets.keys().map(Nibbles::unpack).collect(); let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer); let mut storages = HashMap::default(); @@ -134,11 +131,7 @@ where let account = TrieAccount::from((account, storage_multiproof.root)); account.encode(&mut account_rlp as &mut dyn BufMut); - hash_builder.add_leaf( - // TODO(frisitano): replace this with key abstraction. - Nibbles::unpack_and_truncate_bits(hashed_address), - &account_rlp, - ); + hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); storages.insert(hashed_address, storage_multiproof); } } @@ -231,11 +224,7 @@ where return Ok(StorageMultiProof::empty()) } - let target_nibbles = targets - .into_iter() - // TODO(frisitano): replace this with key abstraction. - .map(|x| Nibbles::unpack_and_truncate_bits(x)) - .collect::>(); + let target_nibbles = targets.into_iter().map(Nibbles::unpack).collect::>(); self.prefix_set.extend_keys(target_nibbles.clone()); let trie_cursor = self.trie_cursor_factory.storage_trie_cursor(self.hashed_address)?; @@ -251,8 +240,7 @@ where } TrieElement::Leaf(hashed_slot, value) => { hash_builder.add_leaf( - // TODO(frisitano): replace this with key abstraction. - Nibbles::unpack_and_truncate_bits(hashed_slot), + Nibbles::unpack(hashed_slot), alloy_rlp::encode_fixed_size(&value).as_ref(), ); } diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index 0f737c760653..0a1aecc043e8 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -1,5 +1,4 @@ use crate::{ - key::BitsCompatibility, prefix_set::{PrefixSetMut, TriePrefixSetsMut}, Nibbles, }; @@ -13,6 +12,9 @@ use std::{ collections::{hash_map, HashMap, HashSet}, }; +#[cfg(feature = "scroll")] +use crate::key::BitsCompatibility; + /// Representation of in-memory hashed state. #[derive(PartialEq, Eq, Clone, Default, Debug)] pub struct HashedPostState { @@ -114,9 +116,12 @@ impl HashedPostState { let mut account_prefix_set = PrefixSetMut::with_capacity(self.accounts.len()); let mut destroyed_accounts = HashSet::default(); for (hashed_address, account) in &self.accounts { - account_prefix_set - // TODO(frisitano): replace this with key abstraction. - .insert(Nibbles::unpack_and_truncate_bits(hashed_address)); + // TODO(frisitano): replace with key abstraction + #[cfg(feature = "scroll")] + let nibbles = Nibbles::unpack_and_truncate_bits(hashed_address); + #[cfg(not(feature = "scroll"))] + let nibbles = Nibbles::unpack(hashed_address); + account_prefix_set.insert(nibbles); if account.is_none() { destroyed_accounts.insert(*hashed_address); @@ -126,8 +131,12 @@ impl HashedPostState { // Populate storage prefix sets. let mut storage_prefix_sets = HashMap::with_capacity(self.storages.len()); for (hashed_address, hashed_storage) in &self.storages { - // TODO(frisitano): replace this with key abstraction. - account_prefix_set.insert(Nibbles::unpack_and_truncate_bits(hashed_address)); + // TODO(frisitano): replace this with abstraction. + #[cfg(feature = "scroll")] + let nibbles = Nibbles::unpack_and_truncate_bits(hashed_address); + #[cfg(not(feature = "scroll"))] + let nibbles = Nibbles::unpack(hashed_address); + account_prefix_set.insert(nibbles); storage_prefix_sets.insert(*hashed_address, hashed_storage.construct_prefix_set()); } @@ -241,7 +250,11 @@ impl HashedStorage { let mut prefix_set = PrefixSetMut::with_capacity(self.storage.len()); for hashed_slot in self.storage.keys() { // TODO(frisitano): replace this with key abstraction. - prefix_set.insert(Nibbles::unpack_and_truncate_bits(hashed_slot)); + #[cfg(feature = "scroll")] + let nibbles = Nibbles::unpack_and_truncate_bits(hashed_slot); + #[cfg(not(feature = "scroll"))] + let nibbles = Nibbles::unpack(hashed_slot); + prefix_set.insert(nibbles); } prefix_set } diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index 4ac4c5e2229f..b8aa133d6fa3 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -1,6 +1,5 @@ use crate::{ hashed_cursor::{HashedCursorFactory, HashedStorageCursor}, - key::BitsCompatibility, node_iter::{TrieElement, TrieNodeIter}, prefix_set::{PrefixSet, TriePrefixSets}, progress::{IntermediateStateRootState, StateRootProgress}, @@ -229,11 +228,7 @@ where account_rlp.clear(); let account = TrieAccount::from((account, storage_root)); account.encode(&mut account_rlp as &mut dyn BufMut); - hash_builder.add_leaf( - // TODO(frisitano): replace this with key abstraction. - Nibbles::unpack_and_truncate_bits(hashed_address), - &account_rlp, - ); + hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); // Decide if we need to return intermediate progress. let total_updates_len = updated_storage_nodes + @@ -429,8 +424,7 @@ where TrieElement::Leaf(hashed_slot, value) => { tracker.inc_leaf(); hash_builder.add_leaf( - // TODO(frisitano): replace this with key abstraction. - Nibbles::unpack_and_truncate_bits(hashed_slot), + Nibbles::unpack(hashed_slot), alloy_rlp::encode_fixed_size(&value).as_ref(), ); } diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index dadc251aed62..c0fb9d9f73c1 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -253,11 +253,13 @@ mod serde_nibbles_set { Vec::::deserialize(deserializer)? .into_iter() .map(|node| { - // TODO(frisitano): replace this with key abstraction. - Ok(Nibbles::unpack_and_truncate_bits( - alloy_primitives::hex::decode(node) - .map_err(|err| D::Error::custom(err.to_string()))?, - )) + let data = alloy_primitives::hex::decode(node) + .map_err(|err| D::Error::custom(err.to_string()))?; + #[cfg(not(feature = "scroll"))] + let result = Ok(Nibbles::unpack(data)); + #[cfg(feature = "scroll")] + let result = Ok(Nibbles::unpack_and_truncate_bits(data)); + result }) .collect::, _>>() } @@ -279,6 +281,7 @@ mod serde_nibbles_map { Deserialize, Deserializer, Serialize, Serializer, }; + #[cfg(feature = "scroll")] use crate::key::BitsCompatibility; pub(super) fn serialize( @@ -294,8 +297,10 @@ mod serde_nibbles_map { storage_nodes.sort_unstable_by_key(|node| node.0); for (k, v) in storage_nodes { // pack, then hex encode the Nibbles - // TODO(frisitano): replace this with key abstraction. + #[cfg(feature = "scroll")] let packed = alloy_primitives::hex::encode(k.pack_bits()); + #[cfg(not(feature = "scroll"))] + let packed = alloy_primitives::hex::encode(k.pack()); map_serializer.serialize_entry(&packed, &v)?; } map_serializer.end() @@ -331,7 +336,10 @@ mod serde_nibbles_map { hex::decode(&key).map_err(|err| Error::custom(err.to_string()))?; // TODO(frisitano): replace this with key abstraction. + #[cfg(feature = "scroll")] let nibbles = Nibbles::unpack_and_truncate_bits(&decoded_key); + #[cfg(not(feature = "scroll"))] + let nibbles = Nibbles::unpack(&decoded_key); result.insert(nibbles, value); } diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index 2f2426b96207..d571a6ce489b 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -85,9 +85,17 @@ impl TrieWalker { .and_then(|key| { if self.can_skip_current_node { // TODO(frisitano): replace this with key abstraction. - key.increment_bit().map(|inc| inc.pack_bits()) + #[cfg(not(feature = "scroll"))] + let key = key.increment_bit().map(|inc| inc.pack()); + #[cfg(feature = "scroll")] + let key = key.increment_bit().map(|inc| inc.pack_bits()); + key } else { - Some(key.pack_bits()) + #[cfg(feature = "scroll")] + let key = Some(key.pack_bits()); + #[cfg(not(feature = "scroll"))] + let key = Some(key.pack()); + key } }) .map(|mut key| { diff --git a/crates/trie/trie/src/witness.rs b/crates/trie/trie/src/witness.rs index 47593671759e..971f10cfbae1 100644 --- a/crates/trie/trie/src/witness.rs +++ b/crates/trie/trie/src/witness.rs @@ -1,6 +1,5 @@ use crate::{ hashed_cursor::{HashedCursor, HashedCursorFactory}, - key::BitsCompatibility, prefix_set::TriePrefixSetsMut, proof::{Proof, StorageProof}, trie_cursor::TrieCursorFactory, @@ -153,8 +152,7 @@ where Self::next_root_from_proofs(storage_trie_nodes, |key: Nibbles| { // Right pad the target with 0s. - // TODO(frisitano): remove this with key abstraction - let mut padded_key = key.pack_bits(); + let mut padded_key = key.pack(); padded_key.resize(32, 0); let target_key = B256::from_slice(&padded_key); let storage_prefix_set = self @@ -181,8 +179,7 @@ where Self::next_root_from_proofs(account_trie_nodes, |key: Nibbles| { // Right pad the target with 0s. - //TODO(frisitano): remove this with key abstraction - let mut padded_key = key.pack_bits(); + let mut padded_key = key.pack(); padded_key.resize(32, 0); let targets = HashMap::from_iter([(B256::from_slice(&padded_key), HashSet::default())]); let proof = From 8cfb70e47d66dc4c71bb576a7dcc5db4ff628192 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 12:13:54 +0800 Subject: [PATCH 14/38] fix: add merge files --- Cargo.lock | 5 +---- crates/engine/tree/src/tree/root.rs | 8 +++++++- crates/rpc/rpc-eth-api/Cargo.toml | 1 - crates/rpc/rpc/Cargo.toml | 1 - crates/storage/provider/src/test_utils/noop.rs | 4 +++- crates/trie/db/src/prefix_set.rs | 3 +-- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62a5dce28980..b69aef5ab400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7524,7 +7524,6 @@ dependencies = [ "reth-revm", "reth-scroll-revm", "reth-transaction-pool", - "reth-trie", "tracing", ] @@ -8451,7 +8450,6 @@ dependencies = [ "reth-rpc-types-compat", "reth-scroll-revm", "reth-transaction-pool", - "reth-trie", "sha2 0.10.8", "thiserror 1.0.69", "tracing", @@ -8864,7 +8862,6 @@ dependencies = [ "reth-tasks", "reth-testing-utils", "reth-transaction-pool", - "reth-trie", "revm-inspectors", "revm-primitives", "serde", @@ -9046,7 +9043,6 @@ dependencies = [ "reth-scroll-storage", "reth-tasks", "reth-transaction-pool", - "reth-trie", "revm-inspectors", "revm-primitives", "tokio", @@ -9350,6 +9346,7 @@ dependencies = [ "reth-primitives", "reth-primitives-traits", "reth-prune-types", + "reth-scroll-revm", "reth-stages-types", "reth-storage-errors", "reth-trie", diff --git a/crates/engine/tree/src/tree/root.rs b/crates/engine/tree/src/tree/root.rs index c760f479e711..05691a40ae53 100644 --- a/crates/engine/tree/src/tree/root.rs +++ b/crates/engine/tree/src/tree/root.rs @@ -3,6 +3,7 @@ use alloy_primitives::map::{HashMap, HashSet}; use reth_provider::{ providers::ConsistentDbView, BlockReader, DBProvider, DatabaseProviderFactory, + StateCommitmentProvider, }; use reth_trie::{ proof::Proof, updates::TrieUpdates, HashedPostState, HashedStorage, MultiProof, Nibbles, @@ -176,7 +177,12 @@ pub(crate) struct StateRootTask { #[allow(dead_code)] impl StateRootTask where - Factory: DatabaseProviderFactory + Clone + Send + Sync + 'static, + Factory: DatabaseProviderFactory + + StateCommitmentProvider + + Clone + + Send + + Sync + + 'static, { /// Creates a new state root task with the unified message channel pub(crate) fn new( diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index a72dc2fdfaf9..46a6d5136e95 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -65,7 +65,6 @@ tracing.workspace = true js-tracer = ["revm-inspectors/js-tracer", "reth-rpc-eth-types/js-tracer"] client = ["jsonrpsee/client", "jsonrpsee/async-client"] scroll = [ - "reth-trie/scroll", "reth-provider/scroll", "reth-execution-types/scroll", "reth-revm/scroll", diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index aadf682a1d5d..92ee311194e5 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -106,7 +106,6 @@ jsonrpsee = { workspace = true, features = ["client"] } [features] js-tracer = ["revm-inspectors/js-tracer", "reth-rpc-eth-types/js-tracer"] scroll = [ - "reth-trie/scroll", "reth-evm/scroll", "reth-rpc-eth-types/scroll", "reth-primitives/scroll", diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 750fea7b33a8..cec243f71b3c 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -27,7 +27,9 @@ use reth_primitives::{ }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; -use reth_storage_api::{NodePrimitivesProvider, StateProofProvider, StorageRootProvider}; +use reth_storage_api::{ + HashedPostStateProvider, NodePrimitivesProvider, StateProofProvider, StorageRootProvider, +}; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput, diff --git a/crates/trie/db/src/prefix_set.rs b/crates/trie/db/src/prefix_set.rs index b8cb380ff0ee..95ff6d91f374 100644 --- a/crates/trie/db/src/prefix_set.rs +++ b/crates/trie/db/src/prefix_set.rs @@ -10,9 +10,8 @@ use reth_db_api::{ use reth_primitives::StorageEntry; use reth_trie::{ prefix_set::{PrefixSetMut, TriePrefixSets}, - KeyHasher, + KeyHasher, Nibbles, }; -use reth_trie_common::Nibbles; use std::{ collections::{HashMap, HashSet}, marker::PhantomData, From 534a90754febc885fe6a03bc27e0ff388e86e869 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 12:58:56 +0800 Subject: [PATCH 15/38] fix lint --- crates/primitives-traits/src/account.rs | 2 +- crates/storage/provider/src/writer/mod.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index 03790dd982a8..27c03dc96b6e 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -58,7 +58,7 @@ impl Account { pub fn is_empty(&self) -> bool { self.nonce == 0 && self.balance.is_zero() && - self.bytecode_hash.map_or(true, |hash| hash == KECCAK_EMPTY) + self.bytecode_hash.is_none_or(|hash| hash == KECCAK_EMPTY) } /// Returns an account bytecode's hash. diff --git a/crates/storage/provider/src/writer/mod.rs b/crates/storage/provider/src/writer/mod.rs index dacb36ad77b2..ad54e9b2bdf8 100644 --- a/crates/storage/provider/src/writer/mod.rs +++ b/crates/storage/provider/src/writer/mod.rs @@ -1135,12 +1135,13 @@ mod tests { let mut state = State::builder().with_bundle_update().build(); let assert_state_root = |state: &State, expected: &PreState, msg| { + #[cfg(feature = "scroll")] + let bundle_state = &(state.bundle_state.clone(), &()).into(); + #[cfg(not(feature = "scroll"))] + let bundle_state = &state.bundle_state; assert_eq!( - StateRoot::overlay_root( - tx, - provider_factory.hashed_post_state(&state.bundle_state) - ) - .unwrap(), + StateRoot::overlay_root(tx, provider_factory.hashed_post_state(bundle_state)) + .unwrap(), state_root(expected.clone().into_iter().map(|(address, (account, storage))| ( address, (account, storage.into_iter()) From d2c9d32d4b40fa0683c8f843dfefec72ce79aaeb Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 13:14:14 +0800 Subject: [PATCH 16/38] fix lint --- crates/net/eth-wire/src/capability.rs | 3 +-- crates/revm/src/batch.rs | 2 +- crates/tracing/src/formatter.rs | 2 +- crates/transaction-pool/src/maintain.rs | 3 +-- crates/transaction-pool/src/pool/best.rs | 3 +-- crates/trie/trie/src/trie_cursor/subnode.rs | 6 ++---- 6 files changed, 7 insertions(+), 12 deletions(-) diff --git a/crates/net/eth-wire/src/capability.rs b/crates/net/eth-wire/src/capability.rs index 625971e0e7bd..06790c52c4a3 100644 --- a/crates/net/eth-wire/src/capability.rs +++ b/crates/net/eth-wire/src/capability.rs @@ -318,8 +318,7 @@ pub fn shared_capability_offsets( // If multiple versions are shared of the same (equal name) capability, the numerically // highest wins, others are ignored if shared_capabilities - .get(&peer_capability.name) - .map_or(true, |v| peer_capability.version > v.version) + .get(&peer_capability.name).is_none_or(|v| peer_capability.version > v.version) { shared_capabilities.insert( peer_capability.name.clone(), diff --git a/crates/revm/src/batch.rs b/crates/revm/src/batch.rs index 15ba049250f5..f55c3aeee259 100644 --- a/crates/revm/src/batch.rs +++ b/crates/revm/src/batch.rs @@ -102,7 +102,7 @@ impl BlockBatchRecord { /// Returns the [`BundleRetention`] for the given block based on the configured prune modes. pub fn bundle_retention(&self, block_number: BlockNumber) -> BundleRetention { - if self.tip.map_or(true, |tip| { + if self.tip.is_none_or(|tip| { !self .prune_modes .account_history diff --git a/crates/tracing/src/formatter.rs b/crates/tracing/src/formatter.rs index 1322377f1c9f..202a92136d26 100644 --- a/crates/tracing/src/formatter.rs +++ b/crates/tracing/src/formatter.rs @@ -54,7 +54,7 @@ impl LogFormat { .unwrap_or_else(|_| // If `RUST_LOG_TARGET` is not set, show target in logs only if the max enabled // level is higher than INFO (DEBUG, TRACE) - filter.max_level_hint().map_or(true, |max_level| max_level > tracing::Level::INFO)); + filter.max_level_hint().is_none_or(|max_level| max_level > tracing::Level::INFO)); match self { Self::Json => { diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 02f218d4b098..7826a0e3406e 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -460,8 +460,7 @@ impl FinalizedBlockTracker { fn update(&mut self, finalized_block: Option) -> Option { let finalized = finalized_block?; self.last_finalized_block - .replace(finalized) - .map_or(true, |last| last < finalized) + .replace(finalized).is_none_or(|last| last < finalized) .then_some(finalized) } } diff --git a/crates/transaction-pool/src/pool/best.rs b/crates/transaction-pool/src/pool/best.rs index a4c91aae7268..284487e14a11 100644 --- a/crates/transaction-pool/src/pool/best.rs +++ b/crates/transaction-pool/src/pool/best.rs @@ -56,8 +56,7 @@ impl Iterator for BestTransactionsWithFees { // the transaction if best.transaction.max_fee_per_gas() >= self.base_fee as u128 && best.transaction - .max_fee_per_blob_gas() - .map_or(true, |fee| fee >= self.base_fee_per_blob_gas as u128) + .max_fee_per_blob_gas().is_none_or(|fee| fee >= self.base_fee_per_blob_gas as u128) { return Some(best); } diff --git a/crates/trie/trie/src/trie_cursor/subnode.rs b/crates/trie/trie/src/trie_cursor/subnode.rs index c928028eb157..c51b713d59a6 100644 --- a/crates/trie/trie/src/trie_cursor/subnode.rs +++ b/crates/trie/trie/src/trie_cursor/subnode.rs @@ -75,16 +75,14 @@ impl CursorSubNode { #[inline] pub fn state_flag(&self) -> bool { self.node - .as_ref() - .map_or(true, |node| self.nibble < 0 || node.state_mask.is_bit_set(self.nibble as u8)) + .as_ref().is_none_or(|node| self.nibble < 0 || node.state_mask.is_bit_set(self.nibble as u8)) } /// Returns `true` if the tree flag is set for the current nibble. #[inline] pub fn tree_flag(&self) -> bool { self.node - .as_ref() - .map_or(true, |node| self.nibble < 0 || node.tree_mask.is_bit_set(self.nibble as u8)) + .as_ref().is_none_or(|node| self.nibble < 0 || node.tree_mask.is_bit_set(self.nibble as u8)) } /// Returns `true` if the current nibble has a root hash. From 963eb212d619edd961b5e7e9e9279fa7271f5d87 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 13:23:01 +0800 Subject: [PATCH 17/38] fmt --- crates/net/eth-wire/src/capability.rs | 3 ++- crates/transaction-pool/src/maintain.rs | 3 ++- crates/transaction-pool/src/pool/best.rs | 3 ++- crates/trie/trie/src/trie_cursor/subnode.rs | 6 ++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/net/eth-wire/src/capability.rs b/crates/net/eth-wire/src/capability.rs index 06790c52c4a3..8d969eac889b 100644 --- a/crates/net/eth-wire/src/capability.rs +++ b/crates/net/eth-wire/src/capability.rs @@ -318,7 +318,8 @@ pub fn shared_capability_offsets( // If multiple versions are shared of the same (equal name) capability, the numerically // highest wins, others are ignored if shared_capabilities - .get(&peer_capability.name).is_none_or(|v| peer_capability.version > v.version) + .get(&peer_capability.name) + .is_none_or(|v| peer_capability.version > v.version) { shared_capabilities.insert( peer_capability.name.clone(), diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 7826a0e3406e..6763deb02e60 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -460,7 +460,8 @@ impl FinalizedBlockTracker { fn update(&mut self, finalized_block: Option) -> Option { let finalized = finalized_block?; self.last_finalized_block - .replace(finalized).is_none_or(|last| last < finalized) + .replace(finalized) + .is_none_or(|last| last < finalized) .then_some(finalized) } } diff --git a/crates/transaction-pool/src/pool/best.rs b/crates/transaction-pool/src/pool/best.rs index 284487e14a11..ed94bc676236 100644 --- a/crates/transaction-pool/src/pool/best.rs +++ b/crates/transaction-pool/src/pool/best.rs @@ -56,7 +56,8 @@ impl Iterator for BestTransactionsWithFees { // the transaction if best.transaction.max_fee_per_gas() >= self.base_fee as u128 && best.transaction - .max_fee_per_blob_gas().is_none_or(|fee| fee >= self.base_fee_per_blob_gas as u128) + .max_fee_per_blob_gas() + .is_none_or(|fee| fee >= self.base_fee_per_blob_gas as u128) { return Some(best); } diff --git a/crates/trie/trie/src/trie_cursor/subnode.rs b/crates/trie/trie/src/trie_cursor/subnode.rs index c51b713d59a6..457c1ba4685b 100644 --- a/crates/trie/trie/src/trie_cursor/subnode.rs +++ b/crates/trie/trie/src/trie_cursor/subnode.rs @@ -75,14 +75,16 @@ impl CursorSubNode { #[inline] pub fn state_flag(&self) -> bool { self.node - .as_ref().is_none_or(|node| self.nibble < 0 || node.state_mask.is_bit_set(self.nibble as u8)) + .as_ref() + .is_none_or(|node| self.nibble < 0 || node.state_mask.is_bit_set(self.nibble as u8)) } /// Returns `true` if the tree flag is set for the current nibble. #[inline] pub fn tree_flag(&self) -> bool { self.node - .as_ref().is_none_or(|node| self.nibble < 0 || node.tree_mask.is_bit_set(self.nibble as u8)) + .as_ref() + .is_none_or(|node| self.nibble < 0 || node.tree_mask.is_bit_set(self.nibble as u8)) } /// Returns `true` if the current nibble has a root hash. From 26621042b39398eb22a81e8b7dd1d69f4428393c Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 14:20:56 +0800 Subject: [PATCH 18/38] add KeyHasher generic to DatabaseHashedStorage::from_reverts trait --- .../provider/src/providers/state/historical.rs | 8 +++++++- crates/trie/db/src/storage.rs | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index bbbf4b949ed0..cba511aad03b 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -156,7 +156,13 @@ impl<'b, Provider: DBProvider + BlockNumReader + StateCommitmentProvider> ); } - Ok(HashedStorage::from_reverts(self.tx(), address, self.block_number)?) + Ok( + HashedStorage::from_reverts::<::KeyHasher>( + self.tx(), + address, + self.block_number, + )?, + ) } fn history_info( diff --git a/crates/trie/db/src/storage.rs b/crates/trie/db/src/storage.rs index 6a3bbe1b9651..c2aa04ee98ad 100644 --- a/crates/trie/db/src/storage.rs +++ b/crates/trie/db/src/storage.rs @@ -6,7 +6,8 @@ use reth_db::{cursor::DbCursorRO, models::BlockNumberAddress, tables, DatabaseEr use reth_db_api::transaction::DbTx; use reth_execution_errors::StorageRootError; use reth_trie::{ - hashed_cursor::HashedPostStateCursorFactory, HashedPostState, HashedStorage, StorageRoot, + hashed_cursor::HashedPostStateCursorFactory, HashedPostState, HashedStorage, KeyHasher, + StorageRoot, }; #[cfg(feature = "metrics")] @@ -32,7 +33,11 @@ pub trait DatabaseStorageRoot<'a, TX> { pub trait DatabaseHashedStorage: Sized { /// Initializes [`HashedStorage`] from reverts. Iterates over storage reverts from the specified /// block up to the current tip and aggregates them into hashed storage in reverse. - fn from_reverts(tx: &TX, address: Address, from: BlockNumber) -> Result; + fn from_reverts( + tx: &TX, + address: Address, + from: BlockNumber, + ) -> Result; } impl<'a, TX: DbTx> DatabaseStorageRoot<'a, TX> @@ -79,13 +84,17 @@ impl<'a, TX: DbTx> DatabaseStorageRoot<'a, TX> } impl DatabaseHashedStorage for HashedStorage { - fn from_reverts(tx: &TX, address: Address, from: BlockNumber) -> Result { + fn from_reverts( + tx: &TX, + address: Address, + from: BlockNumber, + ) -> Result { let mut storage = Self::new(false); let mut storage_changesets_cursor = tx.cursor_read::()?; for entry in storage_changesets_cursor.walk_range(BlockNumberAddress((from, address))..)? { let (BlockNumberAddress((_, storage_address)), storage_change) = entry?; if storage_address == address { - let hashed_slot = keccak256(storage_change.key); + let hashed_slot = KH::hash_key(storage_change.key); if let hash_map::Entry::Vacant(entry) = storage.storage.entry(hashed_slot) { entry.insert(storage_change.value); } From 893de64c3fd7f6f851d587d44ad48bf13293bcda Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 14:58:41 +0800 Subject: [PATCH 19/38] add merge files --- Cargo.lock | 8 ++++---- crates/chain-state/src/memory_overlay.rs | 2 +- crates/rpc/rpc-eth-types/src/simulate.rs | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b69aef5ab400..8180e83e4069 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4606,9 +4606,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.165" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libloading" @@ -4617,7 +4617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -11354,7 +11354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3637e734239e12ab152cd269302500bd063f37624ee210cd04b4936ed671f3b1" dependencies = [ "cc", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] diff --git a/crates/chain-state/src/memory_overlay.rs b/crates/chain-state/src/memory_overlay.rs index 4b5aea8ad8f3..b39c4e57b97d 100644 --- a/crates/chain-state/src/memory_overlay.rs +++ b/crates/chain-state/src/memory_overlay.rs @@ -179,7 +179,7 @@ macro_rules! impl_state_provider { ) -> ProviderResult { let state = &self.trie_state().state; let mut hashed_storage = - state.storages.get(&keccak256(address)).cloned().unwrap_or_default(); + state.storages.get(&self.hash_key(address.as_ref())).cloned().unwrap_or_default(); hashed_storage.extend(&storage); self.historical.storage_multiproof(address, slots, hashed_storage) } diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index f37b38d065f3..323253216e11 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -14,7 +14,6 @@ use reth_primitives::{ }; use reth_rpc_server_types::result::rpc_err; use reth_rpc_types_compat::{block::from_block, TransactionCompat}; -use reth_storage_api::{KeyHasherProvider, StateRootProvider}; use revm::Database; use revm_primitives::{Address, BlockEnv, Bytes, ExecutionResult, TxKind, B256, U256}; From 810a4f83ec1441a4c98a287fb60e50a022e3f643 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 15:09:06 +0800 Subject: [PATCH 20/38] add merge files --- crates/storage/provider/src/providers/state/historical.rs | 4 ++-- crates/storage/provider/src/providers/state/latest.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 8f9267301812..10268a760b0d 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -24,8 +24,8 @@ use reth_trie::{ proof::{Proof, StorageProof}, updates::TrieUpdates, witness::TrieWitness, - AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StateRoot, - StorageMultiProof, StorageRoot, TrieInput, + AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StorageMultiProof, + StorageRoot, TrieInput, }; use reth_trie_db::{ DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot, diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index cdd6ebd582b8..bb3954372c71 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -18,8 +18,8 @@ use reth_trie::{ proof::{Proof, StorageProof}, updates::TrieUpdates, witness::TrieWitness, - AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StateRoot, - StorageMultiProof, StorageRoot, TrieInput, + AccountProof, HashedPostState, HashedStorage, KeyHasher, MultiProof, StorageMultiProof, + StorageRoot, TrieInput, }; use reth_trie_db::{ DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot, From 94c9788e398c7ae3dcb93353d69cf3e3a327a9ba Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 2 Dec 2024 15:26:09 +0800 Subject: [PATCH 21/38] fix: propagate feature --- crates/blockchain-tree/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index b1ee7874c2bb..3fac728a66ed 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -76,6 +76,7 @@ test-utils = [ "reth-primitives/test-utils", "reth-revm/test-utils", "reth-stages-api/test-utils", + "reth-trie-db/test-utils", "reth-db/test-utils", "reth-db-api/test-utils", "reth-provider/test-utils", From 5f143f1d53d7491bb9bcd7fbbeb5a63fda9e573e Mon Sep 17 00:00:00 2001 From: frisitano Date: Tue, 3 Dec 2024 15:09:59 +0800 Subject: [PATCH 22/38] add merge files --- Cargo.lock | 132 ++++++++++-------- Cargo.toml | 7 +- crates/primitives-traits/src/account.rs | 21 ++- crates/primitives/src/alloy_compat.rs | 2 +- crates/primitives/src/transaction/mod.rs | 2 +- crates/scroll/primitives/Cargo.toml | 1 - .../primitives/src/account_extension.rs | 4 +- crates/scroll/primitives/src/lib.rs | 37 ----- crates/scroll/primitives/src/poseidon.rs | 6 +- crates/scroll/revm/src/states/account_info.rs | 5 +- crates/scroll/revm/src/test_utils.rs | 2 +- crates/scroll/state-commitment/Cargo.toml | 40 ++++-- crates/scroll/state-commitment/src/account.rs | 38 +++++ .../scroll/state-commitment/src/commitment.rs | 26 ++++ crates/scroll/state-commitment/src/key.rs | 11 +- crates/scroll/state-commitment/src/lib.rs | 6 + crates/scroll/state-commitment/src/root.rs | 45 ++++-- .../scroll/state-commitment/src/test_utils.rs | 31 +++- crates/scroll/state-commitment/src/value.rs | 10 +- crates/scroll/state-commitment/tests/trie.rs | 80 +++++++---- crates/scroll/storage/src/lib.rs | 2 +- crates/scroll/trie/Cargo.toml | 13 +- crates/scroll/trie/src/branch.rs | 17 +-- crates/scroll/trie/src/hash_builder.rs | 26 ++-- crates/scroll/trie/src/leaf.rs | 3 +- crates/scroll/trie/src/lib.rs | 2 +- crates/scroll/trie/src/sub_tree.rs | 4 +- crates/trie/trie/Cargo.toml | 3 +- crates/trie/trie/src/key.rs | 9 +- crates/trie/trie/src/walker.rs | 6 +- 30 files changed, 376 insertions(+), 215 deletions(-) create mode 100644 crates/scroll/state-commitment/src/account.rs create mode 100644 crates/scroll/state-commitment/src/commitment.rs diff --git a/Cargo.lock b/Cargo.lock index 7bda15eba4a9..bacfb73e3b94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1877,8 +1877,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" dependencies = [ "crossterm", - "strum", - "strum_macros", + "strum 0.26.3", + "strum_macros 0.26.4", "unicode-width 0.2.0", ] @@ -2657,7 +2657,7 @@ dependencies = [ "base16ct", "crypto-bigint", "digest 0.10.7", - "ff 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ff", "generic-array", "group", "pkcs8", @@ -3513,7 +3513,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ff", "rand_core 0.6.4", "subtle", ] @@ -8145,7 +8145,7 @@ dependencies = [ "secp256k1", "serde", "shellexpand", - "strum", + "strum 0.26.3", "thiserror 1.0.69", "tokio", "toml", @@ -8746,7 +8746,7 @@ dependencies = [ "reth-testing-utils", "reth-trie", "reth-trie-db", - "strum", + "strum 0.26.3", "tempfile", "tokio", "tracing", @@ -9204,6 +9204,37 @@ dependencies = [ "serde", ] +[[package]] +name = "reth-scroll-state-commitment" +version = "1.1.2" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "metrics", + "poseidon-bn254", + "proptest", + "proptest-arbitrary-interop", + "reth-db", + "reth-db-api", + "reth-execution-errors", + "reth-metrics", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-scroll-execution", + "reth-scroll-primitives", + "reth-scroll-state-commitment", + "reth-scroll-trie", + "reth-trie", + "reth-trie-common", + "reth-trie-db", + "tracing", + "tracing-subscriber", + "zktrie", + "zktrie_rust", +] + [[package]] name = "reth-scroll-storage" version = "1.1.2" @@ -9218,6 +9249,20 @@ dependencies = [ "reth-storage-errors", ] +[[package]] +name = "reth-scroll-trie" +version = "1.1.2" +dependencies = [ + "alloy-primitives", + "alloy-trie", + "hex-literal", + "proptest-arbitrary-interop", + "reth-scroll-primitives", + "reth-trie", + "tracing", + "tracing-subscriber", +] + [[package]] name = "reth-stages" version = "1.1.2" @@ -9512,6 +9557,7 @@ dependencies = [ "reth-storage-errors", "reth-trie-common", "serde_json", + "smallvec", "tracing", "triehash", ] @@ -10120,58 +10166,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scroll-primitives" -version = "1.1.0" -dependencies = [ - "alloy-primitives", - "poseidon-bn254", - "reth-trie", -] - -[[package]] -name = "scroll-state-commitment" -version = "1.1.0" -dependencies = [ - "alloy-consensus", - "alloy-primitives", - "alloy-rlp", - "metrics", - "poseidon-bn254", - "proptest", - "proptest-arbitrary-interop", - "reth-db", - "reth-db-api", - "reth-execution-errors", - "reth-metrics", - "reth-primitives", - "reth-provider", - "reth-trie", - "reth-trie-common", - "reth-trie-db", - "scroll-primitives", - "scroll-state-commitment", - "scroll-trie", - "tracing", - "tracing-subscriber", - "zktrie", - "zktrie_rust", -] - -[[package]] -name = "scroll-trie" -version = "1.1.0" -dependencies = [ - "alloy-primitives", - "alloy-trie", - "hex-literal", - "proptest-arbitrary-interop", - "reth-trie", - "scroll-primitives", - "tracing", - "tracing-subscriber", -] - [[package]] name = "sdd" version = "3.0.4" @@ -12405,6 +12399,28 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "zktrie" +version = "0.3.0" +dependencies = [ + "gobuild", + "zktrie_rust", +] + +[[package]] +name = "zktrie_rust" +version = "0.3.0" +dependencies = [ + "hex", + "lazy_static", + "log", + "num", + "num-derive", + "num-traits", + "strum 0.24.1", + "strum_macros 0.24.3", +] + [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index 7de6537cbdb6..d05d3a4b11e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -411,6 +411,8 @@ reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } reth-rpc-types-compat = { path = "crates/rpc/rpc-types-compat" } reth-scroll-execution = { path = "crates/scroll/execution" } reth-scroll-primitives = { path = "crates/scroll/primitives" } +reth-scroll-state-commitment = { path = "crates/scroll/state-commitment" } +reth-scroll-trie = { path = "crates/scroll/trie" } reth-scroll-revm = { path = "crates/scroll/revm" } reth-scroll-storage = { path = "crates/scroll/storage" } reth-stages = { path = "crates/stages/stages" } @@ -431,11 +433,6 @@ reth-trie-db = { path = "crates/trie/db" } reth-trie-parallel = { path = "crates/trie/parallel" } reth-trie-sparse = { path = "crates/trie/sparse" } -# scroll -scroll-state-commitment = { path = "crates/scroll/state-commitment" } -scroll-trie = { path = "crates/scroll/trie" } -scroll-primitives = { path = "crates/scroll/primitives" } - # revm revm = { package = "reth-scroll-revm", path = "crates/scroll/revm", default-features = false } revm-inspectors = "0.11.0" diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index 27c03dc96b6e..d2d6dffb5723 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -87,6 +87,25 @@ impl Account { } } +#[cfg(feature = "scroll")] +impl Account { + /// Returns the code size (number of bytes) for the code in this account. + /// In case of no bytecode, returns 0. + pub fn get_code_size(&self) -> u64 { + self.account_extension.as_ref().unwrap().code_size + } + + /// Returns the account poseidon code hash. + /// In the case of no bytecode returns [`reth_scroll_primitives::poseidon::POSEIDON_EMPTY`] + pub fn get_poseidon_code_hash(&self) -> B256 { + self.account_extension + .as_ref() + .unwrap() + .poseidon_code_hash + .unwrap_or(reth_scroll_primitives::poseidon::POSEIDON_EMPTY) + } +} + /// Bytecode for an account. /// /// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support. @@ -235,7 +254,7 @@ impl From for AccountInfo { .account_extension .unwrap_or_default() .poseidon_code_hash - .unwrap_or(reth_scroll_primitives::POSEIDON_EMPTY), + .unwrap_or(reth_scroll_primitives::poseidon::POSEIDON_EMPTY), } } } diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index f190316bcc8e..550b79f6d682 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -166,7 +166,7 @@ impl TryFrom for TransactionSigned { Transaction::L1Message(reth_scroll_primitives::TxL1Message { queue_index: fields.queue_index, gas_limit: inner.gas_limit(), - to: inner.to().ok_or(ConversionError::Custom( + to: inner.to().ok_or_else(|| ConversionError::Custom( "Scroll L1 message transaction do not support create transaction" .to_string(), ))?, diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index eff579cba59e..ef3037db505a 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -425,7 +425,7 @@ impl Transaction { /// Returns true if the transaction is a Scroll L1 messaging transaction. #[cfg(all(feature = "scroll", not(feature = "optimism")))] #[inline] - pub fn is_l1_message(&self) -> bool { + pub const fn is_l1_message(&self) -> bool { matches!(self, Self::L1Message(_)) } diff --git a/crates/scroll/primitives/Cargo.toml b/crates/scroll/primitives/Cargo.toml index 6b00f3458f5e..4a0230276415 100644 --- a/crates/scroll/primitives/Cargo.toml +++ b/crates/scroll/primitives/Cargo.toml @@ -22,7 +22,6 @@ alloy-serde.workspace = true # reth reth-codecs = { workspace = true, optional = true } reth-codecs-derive = { workspace = true, optional = true } -reth-trie.workspace = true # scroll poseidon-bn254 = { workspace = true, features = ["bn254"] } diff --git a/crates/scroll/primitives/src/account_extension.rs b/crates/scroll/primitives/src/account_extension.rs index 6c732c18c38f..39709b55dd03 100644 --- a/crates/scroll/primitives/src/account_extension.rs +++ b/crates/scroll/primitives/src/account_extension.rs @@ -1,4 +1,4 @@ -use crate::{hash_code, POSEIDON_EMPTY}; +use crate::poseidon::{hash_code, POSEIDON_EMPTY}; use alloy_primitives::B256; use serde::{Deserialize, Serialize}; @@ -35,6 +35,8 @@ impl AccountExtension { impl From<(u64, B256)> for AccountExtension { fn from(value: (u64, B256)) -> Self { + // TODO (scroll): Looks like we can create an [`AccountExtension`] with non-zero + // code size with poseidon_code_hash = None Self { code_size: value.0, poseidon_code_hash: (value.1 != POSEIDON_EMPTY).then_some(value.1), diff --git a/crates/scroll/primitives/src/lib.rs b/crates/scroll/primitives/src/lib.rs index 45ca66a2b0cf..ac4d81a40525 100644 --- a/crates/scroll/primitives/src/lib.rs +++ b/crates/scroll/primitives/src/lib.rs @@ -2,9 +2,6 @@ #![warn(unused_crate_dependencies)] -use alloy_primitives::{B256, U256}; -use reth_trie::TrieAccount; - pub use execution_context::ScrollPostExecutionContext; mod execution_context; @@ -18,37 +15,3 @@ pub mod l1_transaction; /// Poseidon hashing primitives. pub mod poseidon; - -/// A Scroll account as represented in the trie. -#[derive(Debug)] -pub struct ScrollTrieAccount { - /// nonce - pub nonce: u64, - /// code size - pub code_size: u64, - /// balance - pub balance: U256, - /// storage root - pub storage_root: B256, - /// keccak code hash - pub code_hash: B256, - /// poseidon code hash - pub poseidon_code_hash: B256, -} - -// TODO: Temporary method to convert from standard ethereum `TrieAccount` to `ScrollTrieAccount` -// TODO: Fix cast -impl From for ScrollTrieAccount { - fn from(value: TrieAccount) -> Self { - ScrollTrieAccount { - // TODO(frisitano): introduce code size and poseidon code hash following integration - // with Account changes - poseidon_code_hash: Default::default(), - code_size: Default::default(), - nonce: value.nonce, - balance: value.balance, - storage_root: value.storage_root, - code_hash: value.code_hash, - } - } -} diff --git a/crates/scroll/primitives/src/poseidon.rs b/crates/scroll/primitives/src/poseidon.rs index 6c30560cbc59..4a559c551d40 100644 --- a/crates/scroll/primitives/src/poseidon.rs +++ b/crates/scroll/primitives/src/poseidon.rs @@ -5,6 +5,9 @@ pub use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; pub const POSEIDON_EMPTY: B256 = b256!("2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864"); +/// The root hash of an empty binary Merle Patricia trie. +pub const EMPTY_ROOT_HASH: B256 = B256::ZERO; + /// Type that is used to represent a field element in binary representation. pub type FieldElementBytes = ::Repr; @@ -21,9 +24,6 @@ pub const DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT: u64 = 256; pub const DOMAIN_TWO_FIELD_ELEMENTS: Fr = Fr::from_raw([DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT * 2, 0, 0, 0]); -/// The root hash of an empty binary Merle Patricia trie. -pub const EMPTY_ROOT_HASH: B256 = B256::ZERO; - /// Hash two field elements using poseidon. pub fn hash(element_1: Fr, element_2: Fr) -> Fr { hash_with_domain(&[element_1, element_2], DOMAIN_TWO_FIELD_ELEMENTS) diff --git a/crates/scroll/revm/src/states/account_info.rs b/crates/scroll/revm/src/states/account_info.rs index ad21e46f14ce..ffd6785118ed 100644 --- a/crates/scroll/revm/src/states/account_info.rs +++ b/crates/scroll/revm/src/states/account_info.rs @@ -1,4 +1,7 @@ -use reth_scroll_primitives::{hash_code, ScrollPostExecutionContext, POSEIDON_EMPTY}; +use reth_scroll_primitives::{ + poseidon::{hash_code, POSEIDON_EMPTY}, + ScrollPostExecutionContext, +}; use revm::primitives::{AccountInfo, Bytecode, B256, KECCAK_EMPTY, U256}; /// The Scroll account information. Code copy of [`AccountInfo`]. Provides additional `code_size` diff --git a/crates/scroll/revm/src/test_utils.rs b/crates/scroll/revm/src/test_utils.rs index f6538100dd87..b06844699178 100644 --- a/crates/scroll/revm/src/test_utils.rs +++ b/crates/scroll/revm/src/test_utils.rs @@ -5,7 +5,7 @@ use crate::{ }, ScrollAccountInfo, }; -use reth_scroll_primitives::{hash_code, POSEIDON_EMPTY}; +use reth_scroll_primitives::poseidon::{hash_code, POSEIDON_EMPTY}; use revm::db::{ states::{reverts::AccountInfoRevert, PlainStateReverts, StateChangeset}, AccountRevert, diff --git a/crates/scroll/state-commitment/Cargo.toml b/crates/scroll/state-commitment/Cargo.toml index b9173e3cf05f..2f8f96c029d8 100644 --- a/crates/scroll/state-commitment/Cargo.toml +++ b/crates/scroll/state-commitment/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "scroll-state-commitment" +name = "reth-scroll-state-commitment" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -13,14 +13,16 @@ alloy-consensus = { workspace = true, optional = true} alloy-primitives.workspace = true alloy-rlp.workspace = true reth-execution-errors.workspace = true -reth-trie = { workspace = true, features = ["scroll"]} +reth-trie = { workspace = true } +reth-scroll-execution.workspace = true reth-primitives.workspace = true tracing.workspace = true -scroll-trie.workspace = true -scroll-primitives.workspace = true -poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", branch = "master" } +reth-scroll-trie.workspace = true +reth-scroll-primitives.workspace = true +poseidon-bn254.workspace = true reth-trie-db.workspace = true reth-db = { workspace = true, features = ["test-utils"] } +reth-primitives-traits = { workspace = true } # `metrics` feature reth-metrics = { workspace = true, optional = true } @@ -31,12 +33,12 @@ zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie", optional = true } zktrie = { path = "/Users/f/dev/scroll/zktrie", features = ["rs_zktrie"], optional = true } [dev-dependencies] -scroll-state-commitment = { workspace = true, features = ["test-utils"]} +reth-scroll-state-commitment = { workspace = true, features = ["test-utils"]} alloy-consensus.workspace = true reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } -reth-trie = { workspace = true, features = ["test-utils", "scroll"] } +reth-trie = { workspace = true, features = ["test-utils" ] } reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } -reth-provider = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils" ] } proptest.workspace = true proptest-arbitrary-interop.workspace = true zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie" } @@ -48,7 +50,27 @@ reth-db-api.workspace = true workspace = true [features] -test-utils = ["dep:zktrie_rust", "dep:zktrie", "dep:alloy-consensus"] +scroll = [ + "reth-trie/scroll", + "reth-primitives-traits/scroll", + "reth-provider/scroll", + "reth-trie/scroll" +] +test-utils = [ + "dep:zktrie_rust", + "dep:zktrie", + "dep:alloy-consensus", + "reth-db/test-utils", + "reth-db-api/test-utils", + "reth-primitives/test-utils", + "reth-primitives-traits/test-utils", + "reth-provider/test-utils", + "reth-scroll-execution/test-utils", + "reth-scroll-state-commitment/test-utils", + "reth-trie/test-utils", + "reth-trie-common/test-utils", + "reth-trie-db/test-utils" + ] metrics = ["reth-metrics", "dep:metrics"] diff --git a/crates/scroll/state-commitment/src/account.rs b/crates/scroll/state-commitment/src/account.rs new file mode 100644 index 000000000000..ba615b5c20eb --- /dev/null +++ b/crates/scroll/state-commitment/src/account.rs @@ -0,0 +1,38 @@ +use alloy_primitives::{B256, U256}; +use reth_primitives_traits::Account; + +/// A Scroll account as represented in the trie. +#[derive(Debug)] +pub struct ScrollTrieAccount { + /// nonce + pub nonce: u64, + /// code size + pub code_size: u64, + /// balance + pub balance: U256, + /// storage root + pub storage_root: B256, + /// keccak code hash + pub code_hash: B256, + /// poseidon code hash + pub poseidon_code_hash: B256, +} + +impl From<(Account, B256)> for ScrollTrieAccount { + fn from((account, storage_root): (Account, B256)) -> Self { + Self { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash: account.get_bytecode_hash(), + #[cfg(feature = "scroll")] + poseidon_code_hash: account.get_poseidon_code_hash(), + #[cfg(feature = "scroll")] + code_size: account.get_code_size(), + #[cfg(not(feature = "scroll"))] + poseidon_code_hash: B256::default(), + #[cfg(not(feature = "scroll"))] + code_size: 0, + } + } +} diff --git a/crates/scroll/state-commitment/src/commitment.rs b/crates/scroll/state-commitment/src/commitment.rs new file mode 100644 index 000000000000..bf6c27721706 --- /dev/null +++ b/crates/scroll/state-commitment/src/commitment.rs @@ -0,0 +1,26 @@ +use super::{PoseidonKeyHasher, StateRoot, StorageRoot}; +use reth_db::transaction::DbTx; +use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory, StateCommitment}; + +/// The state commitment type for Scroll's binary Merkle Patricia Trie. +#[derive(Debug)] +#[non_exhaustive] +pub struct BinaryMerklePatriciaTrie; + +impl StateCommitment for BinaryMerklePatriciaTrie { + type KeyHasher = PoseidonKeyHasher; + type StateRoot<'a, TX: DbTx + 'a> = + StateRoot, DatabaseHashedCursorFactory<'a, TX>>; + type StorageRoot<'a, TX: DbTx + 'a> = + StorageRoot, DatabaseHashedCursorFactory<'a, TX>>; + // TODO(scroll): replace with scroll proof type + type StateProof<'a, TX: DbTx + 'a> = reth_trie::proof::Proof< + DatabaseTrieCursorFactory<'a, TX>, + DatabaseHashedCursorFactory<'a, TX>, + >; + // TODO(scroll): replace with scroll witness type + type StateWitness<'a, TX: DbTx + 'a> = reth_trie::witness::TrieWitness< + DatabaseTrieCursorFactory<'a, TX>, + DatabaseHashedCursorFactory<'a, TX>, + >; +} diff --git a/crates/scroll/state-commitment/src/key.rs b/crates/scroll/state-commitment/src/key.rs index 3442ff527da8..01dfca0c8d85 100644 --- a/crates/scroll/state-commitment/src/key.rs +++ b/crates/scroll/state-commitment/src/key.rs @@ -1,14 +1,17 @@ use alloy_primitives::B256; -use scroll_primitives::poseidon::{split_and_hash_be_bytes, PrimeField, FIELD_ELEMENT_REPR_BYTES}; +use reth_scroll_primitives::poseidon::{ + split_and_hash_be_bytes, PrimeField, FIELD_ELEMENT_REPR_BYTES, +}; +use reth_trie::KeyHasher; // TODO(frisitano): Implement `KeyHasher` trait from upstream. Also consider introducing a // `HashingScheme` trait that combines both `KeyHasher` and `ValueHasher` traits via GATs. /// An implementation of a key hasher that uses Poseidon. -#[derive(Debug)] +#[derive(Clone, Debug, Default)] pub struct PoseidonKeyHasher; -impl PoseidonKeyHasher { +impl KeyHasher for PoseidonKeyHasher { /// Hashes the key using the Poseidon hash function. /// /// The bytes are expected to be provided in big endian format. @@ -17,7 +20,7 @@ impl PoseidonKeyHasher { /// binary representation of a field element (32). /// /// Returns the hash digest in little endian representation with bits reversed. - pub fn hash_key>(bytes: T) -> B256 { + fn hash_key>(bytes: T) -> B256 { debug_assert!(bytes.as_ref().len() <= FIELD_ELEMENT_REPR_BYTES); let mut bytes = split_and_hash_be_bytes(bytes.as_ref()).to_repr(); bytes.iter_mut().for_each(|byte| *byte = byte.reverse_bits()); diff --git a/crates/scroll/state-commitment/src/lib.rs b/crates/scroll/state-commitment/src/lib.rs index 430bcd6dccac..5f8577dc582a 100644 --- a/crates/scroll/state-commitment/src/lib.rs +++ b/crates/scroll/state-commitment/src/lib.rs @@ -1,5 +1,11 @@ //! The implementation of scrolls binary Merkle Patricia Trie used a cryptographic state commitment. +mod account; +pub use account::ScrollTrieAccount; + +mod commitment; +pub use commitment::BinaryMerklePatriciaTrie; + mod root; pub use root::{StateRoot, StorageRoot}; diff --git a/crates/scroll/state-commitment/src/root.rs b/crates/scroll/state-commitment/src/root.rs index 2d5774fce33d..ac457f19fde2 100644 --- a/crates/scroll/state-commitment/src/root.rs +++ b/crates/scroll/state-commitment/src/root.rs @@ -1,7 +1,9 @@ -use super::{PoseidonKeyHasher, PosiedonValueHasher}; +use super::{PoseidonKeyHasher, PosiedonValueHasher, ScrollTrieAccount}; use alloy_primitives::{Address, BlockNumber, B256}; use reth_db::transaction::DbTx; use reth_execution_errors::{StateRootError, StorageRootError}; +use reth_scroll_primitives::poseidon::EMPTY_ROOT_HASH; +use reth_scroll_trie::HashBuilder; use reth_trie::{ hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory, HashedStorageCursor}, key::BitsCompatibility, @@ -11,15 +13,13 @@ use reth_trie::{ trie_cursor::{InMemoryTrieCursorFactory, TrieCursorFactory}, updates::{StorageTrieUpdates, TrieUpdates}, walker::TrieWalker, - HashedPostState, HashedStorage, IntermediateStateRootState, Nibbles, StateRootProgress, - TrieAccount, TrieInput, + HashedPostState, HashedStorage, IntermediateStateRootState, KeyHasher, Nibbles, + StateRootProgress, TrieInput, }; -use scroll_primitives::{poseidon::EMPTY_ROOT_HASH, ScrollTrieAccount}; -use scroll_trie::HashBuilder; use tracing::{debug, trace}; #[cfg(feature = "metrics")] -use crate::metrics::{StateRootMetrics, TrieRootMetrics}; +use reth_trie::metrics::{StateRootMetrics, TrieRootMetrics, TrieType}; // TODO(frisitano): Instead of introducing this new type we should make StateRoot generic over // the [`HashBuilder`] and key traversal types @@ -231,8 +231,7 @@ where storage_root_calculator.root()? }; - let account: ScrollTrieAccount = - TrieAccount::from((account, storage_root)).into(); + let account = ScrollTrieAccount::from((account, storage_root)); let account_hash = PosiedonValueHasher::hash_account(account); hash_builder.add_leaf( Nibbles::unpack_and_truncate_bits(hashed_address), @@ -267,9 +266,10 @@ where let root = hash_builder.root(); + let removed_keys = account_node_iter.walker.take_removed_keys(); trie_updates.finalize( - account_node_iter.walker, hash_builder.into(), + removed_keys, self.prefix_sets.destroyed_accounts, ); @@ -444,7 +444,8 @@ where let root = hash_builder.root(); let mut trie_updates = StorageTrieUpdates::default(); - trie_updates.finalize(storage_node_iter.walker, hash_builder.into()); + let removed_keys = storage_node_iter.walker.take_removed_keys(); + trie_updates.finalize(hash_builder.into(), removed_keys); let stats = tracker.finish(); @@ -483,7 +484,7 @@ impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> tx: &'a TX, range: RangeInclusive, ) -> Result { - let loaded_prefix_sets = PrefixSetLoader::new(tx).load(range)?; + let loaded_prefix_sets = PrefixSetLoader::<_, PoseidonKeyHasher>::new(tx).load(range)?; Ok(Self::from_tx(tx).with_prefix_sets(loaded_prefix_sets)) } @@ -560,6 +561,28 @@ impl<'a, TX: DbTx> DatabaseStateRoot<'a, TX> .with_prefix_sets(input.prefix_sets.freeze()) .root_with_updates() } + + fn root(tx: &'a TX) -> Result { + Self::from_tx(tx).root() + } + + fn root_with_updates(tx: &'a TX) -> Result<(B256, TrieUpdates), StateRootError> { + Self::from_tx(tx).root_with_updates() + } + + fn root_from_prefix_sets_with_updates( + tx: &'a TX, + prefix_sets: TriePrefixSets, + ) -> Result<(B256, TrieUpdates), StateRootError> { + Self::from_tx(tx).with_prefix_sets(prefix_sets).root_with_updates() + } + + fn root_with_progress( + tx: &'a TX, + state: Option, + ) -> Result { + Self::from_tx(tx).with_intermediate_state(state).root_with_progress() + } } impl<'a, TX: DbTx> DatabaseStorageRoot<'a, TX> diff --git a/crates/scroll/state-commitment/src/test_utils.rs b/crates/scroll/state-commitment/src/test_utils.rs index aaf018c58100..8791c6eeb36a 100644 --- a/crates/scroll/state-commitment/src/test_utils.rs +++ b/crates/scroll/state-commitment/src/test_utils.rs @@ -11,19 +11,26 @@ const STORAGE_COMPRESSION_FLAG: u32 = 1; /// Reverses the ordering of bits in a [`B256`] type. pub fn b256_reverse_bits(b256: B256) -> B256 { let mut b256 = b256.0; - for byte in b256.iter_mut() { + for byte in &mut b256 { *byte = byte.reverse_bits(); } B256::from(b256) } -/// Clear the most significant byte of a [`B256`] type. -pub fn b256_clear_msb(mut b256: B256) -> B256 { +/// Clear the last byte of a [`B256`] type. +pub fn b256_clear_last_byte(mut b256: B256) -> B256 { // set the largest byte to 0 >::as_mut(&mut b256)[31] = 0; b256 } +/// Clear the first byte of a [`B256`] type. +pub fn b256_clear_first_byte(mut b256: B256) -> B256 { + // set the smallest byte to 0 + >::as_mut(&mut b256)[0] = 0; + b256 +} + /// Clear the most significant byte of a [`U256`] type. pub fn u256_clear_msb(mut balance: U256) -> U256 { // set the most significant 8 bits to 0 @@ -41,15 +48,25 @@ where S: IntoIterator, { let mut trie = zktrie(); - for (address, (account, storage)) in accounts.into_iter() { + for (address, (account, storage)) in accounts { let key = parse_key_as_hash(address); let mut account_bytes = Vec::with_capacity(5); - account_bytes.push(U256::from_limbs([account.nonce, 0, 0, 0]).to_be_bytes()); + #[cfg(feature = "scroll")] + let code_size = account.get_code_size(); + #[cfg(not(feature = "scroll"))] + let code_size = 0; + + #[cfg(feature = "scroll")] + let poseidon_code_hash = account.get_poseidon_code_hash(); + #[cfg(not(feature = "scroll"))] + let poseidon_code_hash = B256::default(); + + account_bytes.push(U256::from_limbs([account.nonce, code_size, 0, 0]).to_be_bytes()); account_bytes.push(account.balance.to_be_bytes()); account_bytes.push(storage_root(storage).0); account_bytes.push(account.bytecode_hash.unwrap_or(KECCAK_EMPTY).0); - account_bytes.push([0u8; 32]); + account_bytes.push(poseidon_code_hash.0); trie.try_update(&key, ACCOUNT_COMPRESSION_FLAG, account_bytes).unwrap(); } @@ -64,7 +81,7 @@ where S: IntoIterator, { let mut storage_trie = zktrie(); - for (key, value) in storage.into_iter() { + for (key, value) in storage { let key = parse_key_as_hash(key); storage_trie.try_update(&key, STORAGE_COMPRESSION_FLAG, vec![value.to_be_bytes()]).unwrap(); } diff --git a/crates/scroll/state-commitment/src/value.rs b/crates/scroll/state-commitment/src/value.rs index c24ded698757..bc625babf852 100644 --- a/crates/scroll/state-commitment/src/value.rs +++ b/crates/scroll/state-commitment/src/value.rs @@ -1,10 +1,8 @@ +use crate::ScrollTrieAccount; use alloy_primitives::{B256, U256}; -use scroll_primitives::{ - poseidon::{ - field_element_from_be_bytes, hash_with_domain, split_and_hash_be_bytes, FieldElementBytes, - Fr, PrimeField, DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT, - }, - ScrollTrieAccount, +use reth_scroll_primitives::poseidon::{ + field_element_from_be_bytes, hash_with_domain, split_and_hash_be_bytes, FieldElementBytes, Fr, + PrimeField, DOMAIN_MULTIPLIER_PER_FIELD_ELEMENT, }; /// An implementation of a value hasher that uses Poseidon. diff --git a/crates/scroll/state-commitment/tests/trie.rs b/crates/scroll/state-commitment/tests/trie.rs index fe2c54f432c8..405bb0ebb067 100644 --- a/crates/scroll/state-commitment/tests/trie.rs +++ b/crates/scroll/state-commitment/tests/trie.rs @@ -10,17 +10,22 @@ use reth_db::{ }; use reth_primitives::{Account, StorageEntry}; use reth_provider::test_utils::create_test_provider_factory; +use reth_scroll_state_commitment::{ + test_utils::{b256_clear_last_byte, b256_reverse_bits, u256_clear_msb}, + PoseidonKeyHasher, StateRoot, StorageRoot, +}; + use reth_trie::{ trie_cursor::InMemoryTrieCursorFactory, updates::TrieUpdates, HashedPostState, HashedStorage, + KeyHasher, }; use reth_trie_db::{DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory}; -use scroll_state_commitment::{ - test_utils::{b256_clear_msb, b256_reverse_bits, u256_clear_msb}, - PoseidonKeyHasher, StateRoot, StorageRoot, -}; use std::{collections::BTreeMap, sync::Once}; use tracing_subscriber::{self, fmt::format::FmtSpan}; +#[cfg(feature = "scroll")] +use reth_scroll_state_commitment::test_utils::b256_clear_first_byte; + static INIT: Once = Once::new(); pub fn init_test_logger() { @@ -39,22 +44,28 @@ proptest! { })] #[test] - fn fuzz_in_memory_account_nodes(mut init_state: BTreeMap)>, state_updates: [BTreeMap>; 10]) { - let init_state: BTreeMap = init_state.into_iter().map(|(hashed_address, (nonce, balance, bytecode_hash))| { - let hashed_address = b256_clear_msb(hashed_address); + fn fuzz_in_memory_account_nodes(mut init_state: BTreeMap)>, state_updates: [BTreeMap>; 10]) { + let init_state: BTreeMap = init_state.into_iter().map(|(hashed_address, (nonce, balance, code))| { + let hashed_address = b256_clear_last_byte(hashed_address); let balance = u256_clear_msb(balance); - let account = Account { balance, nonce: nonce.into(), bytecode_hash }; + #[cfg(feature = "scroll")] + let account_extension = code.map( |(_, code_hash, code_size)| (code_size, b256_clear_first_byte(code_hash)).into()).or_else(|| Some(Default::default())); + let account = Account { balance, nonce: nonce.into(), bytecode_hash: code.as_ref().map(|(code_hash, _, _)| *code_hash), + #[cfg(feature = "scroll")] + account_extension + }; (hashed_address, account) }).collect(); let state_updates: Vec> = state_updates.into_iter().map(|update| { - let update = update.into_iter().map(|(hashed_address, update)| { - let hashed_address = b256_clear_msb(hashed_address); - let account = if let Some(balance) = update { - Some(Account { balance: u256_clear_msb(balance), ..Default::default() }) - } else { None }; + update.into_iter().map(|(hashed_address, update)| { + let hashed_address = b256_clear_last_byte(hashed_address); + let account = update.map(|balance| Account { + balance: u256_clear_msb(balance), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + ..Default::default() }); (hashed_address, account) - }).collect::>(); - update + }).collect::>() }).collect(); @@ -63,7 +74,7 @@ proptest! { let mut hashed_account_cursor = provider.tx_ref().cursor_write::().unwrap(); // Insert init state into database - for (hashed_address, account) in init_state.clone().into_iter() { + for (hashed_address, account) in init_state.clone() { hashed_account_cursor.upsert(b256_reverse_bits(hashed_address), account).unwrap(); } @@ -76,7 +87,7 @@ proptest! { for state_update in state_updates { // Insert state updates into database let mut hashed_state = HashedPostState::default(); - for (hashed_address, account) in state_update.into_iter() { + for (hashed_address, account) in state_update { if let Some(account) = account { hashed_account_cursor.upsert(b256_reverse_bits(hashed_address), account).unwrap(); hashed_state.accounts.insert(b256_reverse_bits(hashed_address), Some(account)); @@ -99,7 +110,7 @@ proptest! { trie_nodes.extend(trie_updates); // Verify the result - let expected_root = scroll_state_commitment::test_utils::state_root( + let expected_root = reth_scroll_state_commitment::test_utils::state_root( state.iter().map(|(key, account)| (*key, (*account, std::iter::empty()))) ); assert_eq!(expected_root.0, state_root.0); @@ -117,7 +128,7 @@ proptest! { // Insert init state into database let init_storage: BTreeMap = init_storage.into_iter().map(|(slot, value)| { - let hashed_slot = b256_clear_msb(slot); + let hashed_slot = b256_clear_last_byte(slot); hashed_storage_cursor .upsert(hashed_address, StorageEntry { key: b256_reverse_bits(hashed_slot), value }) .unwrap(); @@ -125,7 +136,7 @@ proptest! { }).collect(); let storage_updates: Vec<(bool, BTreeMap)> = storage_updates.into_iter().map(|(is_deleted, updates)| { let updates = updates.into_iter().map(|(slot, value)| { - let slot = b256_clear_msb(slot); + let slot = b256_clear_last_byte(slot); (slot, value) }).collect(); (is_deleted, updates) @@ -169,7 +180,7 @@ proptest! { storage.clear(); } storage.append(&mut storage_update); - let expected_root = scroll_state_commitment::test_utils::storage_root(storage.clone()); + let expected_root = reth_scroll_state_commitment::test_utils::storage_root(storage.clone()); assert_eq!(expected_root, storage_root); } } @@ -180,9 +191,24 @@ fn test_basic_state_root_with_updates_succeeds() { let address_1 = Address::with_last_byte(0); let address_2 = Address::with_last_byte(3); let address_3 = Address::with_last_byte(7); - let account_1 = Account { balance: Uint::from(1), ..Default::default() }; - let account_2 = Account { balance: Uint::from(2), ..Default::default() }; - let account_3 = Account { balance: Uint::from(3), ..Default::default() }; + let account_1 = Account { + balance: Uint::from(1), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + ..Default::default() + }; + let account_2 = Account { + balance: Uint::from(2), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + ..Default::default() + }; + let account_3 = Account { + balance: Uint::from(3), + #[cfg(feature = "scroll")] + account_extension: Some(Default::default()), + ..Default::default() + }; let factory = create_test_provider_factory(); let tx = factory.provider_rw().unwrap(); @@ -226,10 +252,10 @@ fn arbitrary_storage_root() { let factory = create_test_provider_factory(); let tx = factory.provider_rw().unwrap(); let storage: BTreeMap = storage.into_iter().map(|(key, value)| { - let key = b256_clear_msb(key); + let key = b256_clear_last_byte(key); tx.tx_ref().put::( hashed_address, - StorageEntry { key, value: value }, + StorageEntry { key, value }, ) .unwrap(); (b256_reverse_bits(key), value) @@ -238,7 +264,7 @@ fn arbitrary_storage_root() { let tx = factory.provider_rw().unwrap(); let got = StorageRoot::from_tx(tx.tx_ref(), address).root().unwrap(); - let expected = scroll_state_commitment::test_utils::storage_root(storage.into_iter()); + let expected = reth_scroll_state_commitment::test_utils::storage_root(storage.into_iter()); assert_eq!(expected, got); }); } diff --git a/crates/scroll/storage/src/lib.rs b/crates/scroll/storage/src/lib.rs index 1810369a8096..5e333750e0b2 100644 --- a/crates/scroll/storage/src/lib.rs +++ b/crates/scroll/storage/src/lib.rs @@ -105,7 +105,7 @@ mod tests { use reth_codecs::{test_utils::UnusedBits, validate_bitflag_backwards_compat}; use reth_primitives_traits::Account; use reth_revm::{test_utils::StateProviderTest, Database}; - use reth_scroll_primitives::{hash_code, AccountExtension}; + use reth_scroll_primitives::{poseidon::hash_code, AccountExtension}; #[test] fn test_ensure_account_backwards_compatibility() { diff --git a/crates/scroll/trie/Cargo.toml b/crates/scroll/trie/Cargo.toml index c8113dba0e62..729357aa9bcd 100644 --- a/crates/scroll/trie/Cargo.toml +++ b/crates/scroll/trie/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "scroll-trie" +name = "reth-scroll-trie" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -8,10 +8,13 @@ homepage.workspace = true repository.workspace = true exclude.workspace = true +[lints] +workspace = true + [dependencies] alloy-trie = { workspace = true, features = ["serde"] } alloy-primitives.workspace = true -scroll-primitives.workspace = true +reth-scroll-primitives.workspace = true tracing.workspace = true reth-trie.workspace = true @@ -20,5 +23,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = ["e hex-literal = "0.4" proptest-arbitrary-interop.workspace = true -[lints] -workspace = true +[features] +scroll = [ + "reth-trie/scroll" +] \ No newline at end of file diff --git a/crates/scroll/trie/src/branch.rs b/crates/scroll/trie/src/branch.rs index 78753e721b02..48cf9acf59c5 100644 --- a/crates/scroll/trie/src/branch.rs +++ b/crates/scroll/trie/src/branch.rs @@ -5,7 +5,7 @@ use super::{ use alloy_primitives::{hex, B256}; use alloy_trie::TrieMask; use core::{fmt, ops::Range, slice::Iter}; -use scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; +use reth_scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; #[allow(unused_imports)] use alloc::vec::Vec; @@ -16,14 +16,14 @@ pub(crate) const CHILD_INDEX_RANGE: Range = 0..2; /// A trie mask to extract the two child indexes from a branch node. pub(crate) const CHILD_INDEX_MASK: TrieMask = TrieMask::new(0b11); -/// A reference to [BranchNode] and its state mask. +/// A reference to branch node and its state mask. /// NOTE: The stack may contain more items that specified in the state mask. #[derive(Clone)] pub(crate) struct BranchNodeRef<'a> { /// Reference to the collection of hash nodes. /// NOTE: The referenced stack might have more items than the number of children /// for this node. We should only ever access items starting from - /// [BranchNodeRef::first_child_index]. + /// [`BranchNodeRef::first_child_index`]. pub stack: &'a [B256], /// Reference to bitmask indicating the presence of children at /// the respective nibble positions. @@ -131,13 +131,10 @@ impl<'a> Iterator for BranchChildrenIter<'a> { #[inline] fn next(&mut self) -> Option { let i = self.range.next()?; - let value = if self.state_mask.is_bit_set(i) { - // SAFETY: `first_child_index` guarantees that `stack` is exactly - // `state_mask.count_ones()` long. - Some(unsafe { self.stack_iter.next().unwrap_unchecked() }) - } else { - None - }; + let value = self + .state_mask + .is_bit_set(i) + .then(|| unsafe { self.stack_iter.next().unwrap_unchecked() }); Some((i, value)) } diff --git a/crates/scroll/trie/src/hash_builder.rs b/crates/scroll/trie/src/hash_builder.rs index 8598137bf586..6eaa737a3f4c 100644 --- a/crates/scroll/trie/src/hash_builder.rs +++ b/crates/scroll/trie/src/hash_builder.rs @@ -11,7 +11,7 @@ use alloy_trie::{ BranchNodeCompact, Nibbles, TrieMask, }; use core::cmp; -use scroll_primitives::poseidon::EMPTY_ROOT_HASH; +use reth_scroll_primitives::poseidon::EMPTY_ROOT_HASH; use tracing::trace; #[derive(Debug, Default)] @@ -35,7 +35,7 @@ pub struct HashBuilder { impl HashBuilder { /// Enables the Hash Builder to store updated branch nodes. /// - /// Call [HashBuilder::split] to get the updates to branch nodes. + /// Call [`HashBuilder::split`] to get the updates to branch nodes. pub fn with_updates(mut self, retain_updates: bool) -> Self { self.set_updates(retain_updates); self @@ -49,14 +49,14 @@ impl HashBuilder { /// Enables the Hash Builder to store updated branch nodes. /// - /// Call [HashBuilder::split] to get the updates to branch nodes. + /// Call [`HashBuilder::split`] to get the updates to branch nodes. pub fn set_updates(&mut self, retain_updates: bool) { if retain_updates { self.updated_branch_nodes = Some(HashMap::default()); } } - /// Splits the [HashBuilder] into a [HashBuilder] and hash builder updates. + /// Splits the [`HashBuilder`] into a [`HashBuilder`] and hash builder updates. pub fn split(mut self) -> (Self, HashMap) { let updates = self.updated_branch_nodes.take(); (self, updates.unwrap_or_default()) @@ -68,7 +68,7 @@ impl HashBuilder { } /// The number of total updates accrued. - /// Returns `0` if [Self::with_updates] was not called. + /// Returns `0` if [`Self::with_updates`] was not called. pub fn updates_len(&self) -> usize { self.updated_branch_nodes.as_ref().map(|u| u.len()).unwrap_or(0) } @@ -221,7 +221,6 @@ impl HashBuilder { self.state_masks[len] |= TrieMask::new(0b100 << extra_digit); let leaf_node = LeafNodeRef::new(¤t, leaf_value); let leaf_hash = leaf_node.hash_leaf(); - println!("leaf hash: {:?}", leaf_hash.0); trace!( target: "trie::hash_builder", ?leaf_node, @@ -409,7 +408,7 @@ impl HashBuilder { // TODO(frisitano): Introduce generic for the HashBuilder. impl From for HashBuilder { fn from(hash_builder: reth_trie::HashBuilder) -> Self { - HashBuilder { + Self { key: hash_builder.key, value: hash_builder.value, stack: hash_builder @@ -429,7 +428,7 @@ impl From for HashBuilder { impl From for reth_trie::HashBuilder { fn from(value: HashBuilder) -> Self { - reth_trie::HashBuilder { + Self { key: value.key, value: value.value, stack: value @@ -455,8 +454,8 @@ mod test { use super::*; use alloc::collections::BTreeMap; use hex_literal::hex; + use reth_scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; use reth_trie::key::BitsCompatibility; - use scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; #[test] fn test_convert_to_bit_representation() { @@ -535,9 +534,9 @@ mod test { let mut hb = HashBuilder::default().with_updates(true); - leaf_values.iter().for_each(|(key, val)| { + for (key, val) in &leaf_values { hb.add_leaf(key.clone(), val); - }); + } let root = hb.root(); @@ -566,7 +565,10 @@ mod test { let node_11 = hash_with_domain(&[Fr::zero(), node_111], crate::BRANCH_NODE_LTRB_DOMAIN); let node_1 = hash_with_domain(&[Fr::zero(), node_11], crate::BRANCH_NODE_LTRB_DOMAIN); - hash_with_domain(&[node_0, node_1], crate::BRANCH_NODE_LBRB_DOMAIN).to_repr().into() + let mut root = + hash_with_domain(&[node_0, node_1], crate::BRANCH_NODE_LBRB_DOMAIN).to_repr(); + root.reverse(); + root.into() }; assert_eq!(expected, root); diff --git a/crates/scroll/trie/src/leaf.rs b/crates/scroll/trie/src/leaf.rs index b58651ee93c9..17be70943834 100644 --- a/crates/scroll/trie/src/leaf.rs +++ b/crates/scroll/trie/src/leaf.rs @@ -1,7 +1,7 @@ use super::LEAF_NODE_DOMAIN; use alloy_primitives::B256; +use reth_scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; use reth_trie::{key::BitsCompatibility, LeafNodeRef}; -use scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; /// A trait used to hash the leaf node. pub(crate) trait HashLeaf { @@ -17,7 +17,6 @@ impl HashLeaf for LeafNodeRef<'_> { <[u8; 32]>::try_from(self.value).expect("leaf value is 32 bytes"), ) .expect("leaf value is a valid field element"); - println!("leaf value: {:?}", leaf_value.to_repr()); hash_with_domain(&[leaf_key, leaf_value], LEAF_NODE_DOMAIN).to_repr().into() } } diff --git a/crates/scroll/trie/src/lib.rs b/crates/scroll/trie/src/lib.rs index df147e4e6e2c..ebf3f721948c 100644 --- a/crates/scroll/trie/src/lib.rs +++ b/crates/scroll/trie/src/lib.rs @@ -9,7 +9,7 @@ mod hash_builder; pub use hash_builder::HashBuilder; mod leaf; mod sub_tree; -use scroll_primitives::poseidon::Fr; +use reth_scroll_primitives::poseidon::Fr; /// The hashing domain for leaf nodes. pub const LEAF_NODE_DOMAIN: Fr = Fr::from_raw([4, 0, 0, 0]); diff --git a/crates/scroll/trie/src/sub_tree.rs b/crates/scroll/trie/src/sub_tree.rs index de995b78c2a2..eed916b8ae7b 100644 --- a/crates/scroll/trie/src/sub_tree.rs +++ b/crates/scroll/trie/src/sub_tree.rs @@ -2,9 +2,9 @@ use super::{BRANCH_NODE_LBRT_DOMAIN, BRANCH_NODE_LTRB_DOMAIN}; use alloy_primitives::{hex, B256}; use alloy_trie::Nibbles; use core::fmt; -use scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; +use reth_scroll_primitives::poseidon::{hash_with_domain, Fr, PrimeField}; -/// [SubTreeRef] is a structure that allows for calculation of the root of a sparse binary Merkle +/// [`SubTreeRef`] is a structure that allows for calculation of the root of a sparse binary Merkle /// tree consisting of a single leaf node. pub(crate) struct SubTreeRef<'a> { /// The key to the child node. diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index bcf8f68267b7..4306fb2f262b 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -69,7 +69,8 @@ serde = [ "alloy-trie/serde", "revm/serde", "reth-trie-common/serde", - "reth-primitives-traits/serde" + "reth-primitives-traits/serde", + "smallvec/serde" ] test-utils = [ "triehash", diff --git a/crates/trie/trie/src/key.rs b/crates/trie/trie/src/key.rs index 334764aeaade..2f25a1059d95 100644 --- a/crates/trie/trie/src/key.rs +++ b/crates/trie/trie/src/key.rs @@ -43,11 +43,10 @@ impl BitsCompatibility for Nibbles { } } - Nibbles::from_vec_unchecked(bits) + Self::from_vec_unchecked(bits) } fn pack_bits(&self) -> SmallVec<[u8; 32]> { - println!("bits {:?}", self); let mut result = SmallVec::with_capacity((self.len() + 7) / 8); for bits in self.as_slice().chunks(8) { @@ -58,8 +57,6 @@ impl BitsCompatibility for Nibbles { result.push(byte); } - println!("result: {:?}", result); - result } @@ -82,9 +79,9 @@ impl BitsCompatibility for Nibbles { if *nibble < 1 { *nibble += 1; return Some(incremented); - } else { - *nibble = 0; } + + *nibble = 0; } None diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index 5508ad6befd4..c815b7384947 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -1,5 +1,4 @@ use crate::{ - key::BitsCompatibility, prefix_set::PrefixSet, trie_cursor::{CursorSubNode, TrieCursor}, BranchNodeCompact, Nibbles, @@ -10,6 +9,9 @@ use reth_storage_errors::db::DatabaseError; #[cfg(feature = "metrics")] use crate::metrics::WalkerMetrics; +#[cfg(feature = "scroll")] +use crate::key::BitsCompatibility; + /// `TrieWalker` is a structure that enables traversal of a Merkle trie. /// It allows moving through the trie in a depth-first manner, skipping certain branches /// if they have not changed. @@ -103,7 +105,7 @@ impl TrieWalker { if self.can_skip_current_node { // TODO(frisitano): replace this with key abstraction. #[cfg(not(feature = "scroll"))] - let key = key.increment_bit().map(|inc| inc.pack()); + let key = key.increment().map(|inc| inc.pack()); #[cfg(feature = "scroll")] let key = key.increment_bit().map(|inc| inc.pack_bits()); key From dc7ae2408a63605a365cd216e02013446c8bc254 Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 4 Dec 2024 22:57:54 +0800 Subject: [PATCH 23/38] cleanup Cargo.toml files --- Cargo.lock | 2 -- crates/scroll/state-commitment/Cargo.toml | 35 +++++++++++--------- crates/scroll/state-commitment/tests/trie.rs | 15 +-------- crates/scroll/trie/Cargo.toml | 5 ++- 4 files changed, 22 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bacfb73e3b94..c647ade0a121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9230,7 +9230,6 @@ dependencies = [ "reth-trie-common", "reth-trie-db", "tracing", - "tracing-subscriber", "zktrie", "zktrie_rust", ] @@ -9260,7 +9259,6 @@ dependencies = [ "reth-scroll-primitives", "reth-trie", "tracing", - "tracing-subscriber", ] [[package]] diff --git a/crates/scroll/state-commitment/Cargo.toml b/crates/scroll/state-commitment/Cargo.toml index 2f8f96c029d8..e636b68573dc 100644 --- a/crates/scroll/state-commitment/Cargo.toml +++ b/crates/scroll/state-commitment/Cargo.toml @@ -9,42 +9,45 @@ repository.workspace = true exclude.workspace = true [dependencies] -alloy-consensus = { workspace = true, optional = true} -alloy-primitives.workspace = true -alloy-rlp.workspace = true +reth-db = { workspace = true, features = ["test-utils"] } reth-execution-errors.workspace = true -reth-trie = { workspace = true } -reth-scroll-execution.workspace = true reth-primitives.workspace = true -tracing.workspace = true -reth-scroll-trie.workspace = true +reth-primitives-traits = { workspace = true } +reth-scroll-execution.workspace = true reth-scroll-primitives.workspace = true -poseidon-bn254.workspace = true +reth-scroll-trie.workspace = true +reth-trie = { workspace = true } reth-trie-db.workspace = true -reth-db = { workspace = true, features = ["test-utils"] } -reth-primitives-traits = { workspace = true } + +# alloy +alloy-consensus = { workspace = true, optional = true} +alloy-primitives.workspace = true +alloy-rlp.workspace = true # `metrics` feature reth-metrics = { workspace = true, optional = true } metrics = { workspace = true, optional = true } # zktrie +poseidon-bn254.workspace = true zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie", optional = true } zktrie = { path = "/Users/f/dev/scroll/zktrie", features = ["rs_zktrie"], optional = true } +# misc +tracing.workspace = true + [dev-dependencies] -reth-scroll-state-commitment = { workspace = true, features = ["test-utils"]} -alloy-consensus.workspace = true +reth-db-api.workspace = true reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } +reth-scroll-state-commitment = { workspace = true, features = ["test-utils"]} reth-trie = { workspace = true, features = ["test-utils" ] } reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } reth-provider = { workspace = true, features = ["test-utils" ] } -proptest.workspace = true -proptest-arbitrary-interop.workspace = true +alloy-consensus.workspace = true zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie" } zktrie = { path = "/Users/f/dev/scroll/zktrie", features = ["rs_zktrie"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] } -reth-db-api.workspace = true +proptest.workspace = true +proptest-arbitrary-interop.workspace = true [lints] workspace = true diff --git a/crates/scroll/state-commitment/tests/trie.rs b/crates/scroll/state-commitment/tests/trie.rs index 405bb0ebb067..f5357a2c500b 100644 --- a/crates/scroll/state-commitment/tests/trie.rs +++ b/crates/scroll/state-commitment/tests/trie.rs @@ -20,24 +20,11 @@ use reth_trie::{ KeyHasher, }; use reth_trie_db::{DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory}; -use std::{collections::BTreeMap, sync::Once}; -use tracing_subscriber::{self, fmt::format::FmtSpan}; +use std::collections::BTreeMap; #[cfg(feature = "scroll")] use reth_scroll_state_commitment::test_utils::b256_clear_first_byte; -static INIT: Once = Once::new(); - -pub fn init_test_logger() { - INIT.call_once(|| { - tracing_subscriber::fmt() - .with_test_writer() // Capture logs for test output - .with_span_events(FmtSpan::CLOSE) // Optional: Add span events - .with_env_filter("trace") // Set log level as needed - .init(); - }); -} - proptest! { #![proptest_config(ProptestConfig { cases: 6, ..ProptestConfig::default() diff --git a/crates/scroll/trie/Cargo.toml b/crates/scroll/trie/Cargo.toml index 729357aa9bcd..c9886b436781 100644 --- a/crates/scroll/trie/Cargo.toml +++ b/crates/scroll/trie/Cargo.toml @@ -12,14 +12,13 @@ exclude.workspace = true workspace = true [dependencies] +reth-scroll-primitives.workspace = true +reth-trie.workspace = true alloy-trie = { workspace = true, features = ["serde"] } alloy-primitives.workspace = true -reth-scroll-primitives.workspace = true tracing.workspace = true -reth-trie.workspace = true [dev-dependencies] -tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] } hex-literal = "0.4" proptest-arbitrary-interop.workspace = true From fd062c5461406571edadecfeb7a8a06fe107a286 Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 4 Dec 2024 23:34:22 +0800 Subject: [PATCH 24/38] fix: Cargo.toml dependencies --- Cargo.lock | 2 ++ crates/scroll/state-commitment/Cargo.toml | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c647ade0a121..d4a24c1dbb12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12400,6 +12400,7 @@ dependencies = [ [[package]] name = "zktrie" version = "0.3.0" +source = "git+https://github.com/scroll-tech/zktrie.git?rev=309160464c1cd2b87a578ed6d9b6e98205ae4640#309160464c1cd2b87a578ed6d9b6e98205ae4640" dependencies = [ "gobuild", "zktrie_rust", @@ -12408,6 +12409,7 @@ dependencies = [ [[package]] name = "zktrie_rust" version = "0.3.0" +source = "git+https://github.com/scroll-tech/zktrie.git?rev=309160464c1cd2b87a578ed6d9b6e98205ae4640#309160464c1cd2b87a578ed6d9b6e98205ae4640" dependencies = [ "hex", "lazy_static", diff --git a/crates/scroll/state-commitment/Cargo.toml b/crates/scroll/state-commitment/Cargo.toml index e636b68573dc..2272a2fa0a24 100644 --- a/crates/scroll/state-commitment/Cargo.toml +++ b/crates/scroll/state-commitment/Cargo.toml @@ -30,8 +30,8 @@ metrics = { workspace = true, optional = true } # zktrie poseidon-bn254.workspace = true -zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie", optional = true } -zktrie = { path = "/Users/f/dev/scroll/zktrie", features = ["rs_zktrie"], optional = true } +zktrie_rust = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1cd2b87a578ed6d9b6e98205ae4640", optional = true } +zktrie = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1cd2b87a578ed6d9b6e98205ae4640", features = ["rs_zktrie"], optional = true } # misc tracing.workspace = true @@ -44,8 +44,8 @@ reth-trie = { workspace = true, features = ["test-utils" ] } reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } reth-provider = { workspace = true, features = ["test-utils" ] } alloy-consensus.workspace = true -zktrie_rust = { path = "/Users/f/dev/scroll/zktrie/rs_zktrie" } -zktrie = { path = "/Users/f/dev/scroll/zktrie", features = ["rs_zktrie"] } +zktrie_rust = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1cd2b87a578ed6d9b6e98205ae4640" } +zktrie = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1cd2b87a578ed6d9b6e98205ae4640", features = ["rs_zktrie"] } proptest.workspace = true proptest-arbitrary-interop.workspace = true From fb85220614f296974cc10f943af64cf18511896f Mon Sep 17 00:00:00 2001 From: frisitano Date: Thu, 5 Dec 2024 14:41:14 +0800 Subject: [PATCH 25/38] refactor: refactor Cargo.toml and put tests behind scroll feature --- crates/scroll/state-commitment/Cargo.toml | 12 ++++++------ crates/scroll/state-commitment/src/lib.rs | 3 +++ .../state-commitment/{tests/trie.rs => src/test.rs} | 0 3 files changed, 9 insertions(+), 6 deletions(-) rename crates/scroll/state-commitment/{tests/trie.rs => src/test.rs} (100%) diff --git a/crates/scroll/state-commitment/Cargo.toml b/crates/scroll/state-commitment/Cargo.toml index 2272a2fa0a24..8c1a3cfd3ddc 100644 --- a/crates/scroll/state-commitment/Cargo.toml +++ b/crates/scroll/state-commitment/Cargo.toml @@ -8,15 +8,18 @@ homepage.workspace = true repository.workspace = true exclude.workspace = true +[lints] +workspace = true + [dependencies] -reth-db = { workspace = true, features = ["test-utils"] } +reth-db.workspace = true reth-execution-errors.workspace = true reth-primitives.workspace = true -reth-primitives-traits = { workspace = true } +reth-primitives-traits.workspace = true reth-scroll-execution.workspace = true reth-scroll-primitives.workspace = true reth-scroll-trie.workspace = true -reth-trie = { workspace = true } +reth-trie.workspace = true reth-trie-db.workspace = true # alloy @@ -49,9 +52,6 @@ zktrie = { git = "https://github.com/scroll-tech/zktrie.git", rev = "309160464c1 proptest.workspace = true proptest-arbitrary-interop.workspace = true -[lints] -workspace = true - [features] scroll = [ "reth-trie/scroll", diff --git a/crates/scroll/state-commitment/src/lib.rs b/crates/scroll/state-commitment/src/lib.rs index 5f8577dc582a..a19108a9203e 100644 --- a/crates/scroll/state-commitment/src/lib.rs +++ b/crates/scroll/state-commitment/src/lib.rs @@ -16,6 +16,9 @@ mod value; #[cfg(feature = "test-utils")] pub mod test_utils; +#[cfg(all(test, feature = "scroll"))] +mod test; + // RE-EXPORTS pub use key::PoseidonKeyHasher; pub use value::PosiedonValueHasher; diff --git a/crates/scroll/state-commitment/tests/trie.rs b/crates/scroll/state-commitment/src/test.rs similarity index 100% rename from crates/scroll/state-commitment/tests/trie.rs rename to crates/scroll/state-commitment/src/test.rs From 5bfafcc90a9892ec9a71e2c50b5c9b1ffca0c05d Mon Sep 17 00:00:00 2001 From: frisitano Date: Thu, 5 Dec 2024 14:56:39 +0800 Subject: [PATCH 26/38] lints and replace keccak with poseidon for HashedStorage instantiation --- crates/scroll/state-commitment/src/key.rs | 3 --- crates/scroll/state-commitment/src/root.rs | 3 +-- crates/trie/trie/src/key.rs | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/scroll/state-commitment/src/key.rs b/crates/scroll/state-commitment/src/key.rs index 01dfca0c8d85..844f2c4f70f7 100644 --- a/crates/scroll/state-commitment/src/key.rs +++ b/crates/scroll/state-commitment/src/key.rs @@ -4,9 +4,6 @@ use reth_scroll_primitives::poseidon::{ }; use reth_trie::KeyHasher; -// TODO(frisitano): Implement `KeyHasher` trait from upstream. Also consider introducing a -// `HashingScheme` trait that combines both `KeyHasher` and `ValueHasher` traits via GATs. - /// An implementation of a key hasher that uses Poseidon. #[derive(Clone, Debug, Default)] pub struct PoseidonKeyHasher; diff --git a/crates/scroll/state-commitment/src/root.rs b/crates/scroll/state-commitment/src/root.rs index 65da517cdb2a..f9dff61f271a 100644 --- a/crates/scroll/state-commitment/src/root.rs +++ b/crates/scroll/state-commitment/src/root.rs @@ -615,9 +615,8 @@ impl<'a, TX: DbTx> DatabaseStorageRoot<'a, TX> hashed_storage: HashedStorage, ) -> Result { let prefix_set = hashed_storage.construct_prefix_set().freeze(); - // TODO: replace keccak with KH: KeyHasher when integrating with upstream let state_sorted = HashedPostState::from_hashed_storage( - alloy_primitives::keccak256(address), + PoseidonKeyHasher::hash_key(address), hashed_storage, ) .into_sorted(); diff --git a/crates/trie/trie/src/key.rs b/crates/trie/trie/src/key.rs index 2f25a1059d95..1399053fb892 100644 --- a/crates/trie/trie/src/key.rs +++ b/crates/trie/trie/src/key.rs @@ -3,7 +3,7 @@ use smallvec::SmallVec; use crate::Nibbles; /// The maximum number of bits a key can contain. -const MAX_BITS: usize = 248; +pub const MAX_BITS: usize = 248; /// The maximum number of bytes a key can contain. const MAX_BYTES: usize = 31; From 85cf05008cb4d1300c0004caaef37cb82756071a Mon Sep 17 00:00:00 2001 From: frisitano Date: Thu, 5 Dec 2024 15:32:43 +0800 Subject: [PATCH 27/38] fix deny license and add scroll specific tests to ci --- .github/workflows/unit.yml | 6 +++++- deny.toml | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 50f5fa6e38e8..96b5867c9043 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -41,6 +41,10 @@ jobs: args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" partition: 2 total_partitions: 2 + - type: scroll + args: -p reth-scroll-state-commitment --locked --features "scroll" + partition: 1 + total_partitions: 1 - type: book args: --manifest-path book/sources/Cargo.toml partition: 1 @@ -64,7 +68,7 @@ jobs: cargo nextest run \ ${{ matrix.args }} --workspace \ --exclude ef-tests --no-tests=warn \ - --partition hash:${{ matrix.partition }}/2 \ + --partition hash:${{ matrix.partition }}/{{ matrix.total_partitions }} \ -E "!kind(test)" state: diff --git a/deny.toml b/deny.toml index 6dc51dba0a93..b1582febf669 100644 --- a/deny.toml +++ b/deny.toml @@ -67,10 +67,10 @@ exceptions = [ { allow = ["MPL-2.0"], name = "webpki-roots" }, ] -# Skip the poseidon-bn254 and bn254 crates for license verification. We should at some point publish a license for them. +# Skip the poseidon-bn254, bn254 and zktrie crates for license verification. We should at some point publish a license for them. [licenses.private] ignore = true -ignore-sources = ["https://github.com/scroll-tech/poseidon-bn254", "https://github.com/scroll-tech/bn254"] +ignore-sources = ["https://github.com/scroll-tech/poseidon-bn254", "https://github.com/scroll-tech/bn254", "https://github.com/scroll-tech/zktrie.git"] [[licenses.clarify]] name = "ring" From 85266db573984f9596e13e3a00d57432c86d961d Mon Sep 17 00:00:00 2001 From: frisitano Date: Thu, 5 Dec 2024 15:34:51 +0800 Subject: [PATCH 28/38] fix unit github workflow --- .github/workflows/unit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 96b5867c9043..269d8a8ac88b 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -68,7 +68,7 @@ jobs: cargo nextest run \ ${{ matrix.args }} --workspace \ --exclude ef-tests --no-tests=warn \ - --partition hash:${{ matrix.partition }}/{{ matrix.total_partitions }} \ + --partition hash:${{ matrix.partition }}/${{ matrix.total_partitions }} \ -E "!kind(test)" state: From 7639f64daa23340e8e6fc53401bc86c4152ae618 Mon Sep 17 00:00:00 2001 From: frisitano Date: Thu, 5 Dec 2024 16:53:53 +0800 Subject: [PATCH 29/38] lint and deny --- crates/scroll/state-commitment/src/lib.rs | 7 +++---- deny.toml | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/scroll/state-commitment/src/lib.rs b/crates/scroll/state-commitment/src/lib.rs index a19108a9203e..89321843884d 100644 --- a/crates/scroll/state-commitment/src/lib.rs +++ b/crates/scroll/state-commitment/src/lib.rs @@ -10,7 +10,10 @@ mod root; pub use root::{StateRoot, StorageRoot}; mod key; +pub use key::PoseidonKeyHasher; + mod value; +pub use value::PosiedonValueHasher; /// test utils for the state commitment #[cfg(feature = "test-utils")] @@ -18,7 +21,3 @@ pub mod test_utils; #[cfg(all(test, feature = "scroll"))] mod test; - -// RE-EXPORTS -pub use key::PoseidonKeyHasher; -pub use value::PosiedonValueHasher; diff --git a/deny.toml b/deny.toml index b1582febf669..d6d3bc5c21aa 100644 --- a/deny.toml +++ b/deny.toml @@ -100,5 +100,6 @@ allow-git = [ "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/scroll-tech/bn254", "https://github.com/scroll-tech/sp1-intrinsics", - "https://github.com/scroll-tech/poseidon-bn254" + "https://github.com/scroll-tech/poseidon-bn254", + "https://github.com/scroll-tech/zktrie.git" ] From d99b1b7ab1fce14746225498dbb93fccd97c4e7f Mon Sep 17 00:00:00 2001 From: frisitano Date: Thu, 5 Dec 2024 18:02:35 +0800 Subject: [PATCH 30/38] fix Cargo.toml --- Cargo.lock | 463 ++++++++++++++++++++++++++--------------------------- 1 file changed, 231 insertions(+), 232 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4a24c1dbb12..3fe135ed694a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2364c782a245cf8725ea6dbfca5f530162702b5d685992ea03ce64529136cc" +checksum = "80759b3f57b3b20fa7cd8fef6479930fc95461b58ff8adea6e87e618449c8a1d" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -182,9 +182,9 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6cee6a35793f3db8a5ffe60e86c695f321d081a567211245f503e8c498fce8" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -230,9 +230,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84c506bf264110fa7e90d9924f742f40ef53c6572ea56a0b0bd714a567ed389" +checksum = "ac4b22b3e51cac09fd2adfcc73b55f447b4df669f983c13f7894ec82b607c63f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -309,9 +309,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fce5dbd6a4f118eecc4719eaa9c7ffc31c315e6c5ccde3642db927802312425" +checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" dependencies = [ "alloy-rlp", "arbitrary", @@ -322,9 +322,9 @@ dependencies = [ "derive_more 1.0.0", "foldhash", "getrandom 0.2.15", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "hex-literal", - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "k256", "keccak-asm", @@ -333,7 +333,7 @@ dependencies = [ "proptest-derive", "rand 0.8.5", "ruint", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "sha3", "tiny-keccak", @@ -418,7 +418,7 @@ checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -634,56 +634,56 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343289b4a7461ed8bab8618504c995c049c082b70c7332efd7b32125633dc05" +checksum = "3bfd7853b65a2b4f49629ec975fee274faf6dff15ab8894c620943398ef283c0" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4222d70bec485ceccc5d8fd4f2909edd65b5d5e43d4aca0b5dcee65d519ae98f" +checksum = "82ec42f342d9a9261699f8078e57a7a4fda8aaa73c1a212ed3987080e6a9cd13" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck 0.5.0", - "indexmap 2.6.0", + "indexmap 2.7.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e17f2677369571b976e51ea1430eb41c3690d344fef567b840bfc0b01b6f83a" +checksum = "ed2c50e6a62ee2b4f7ab3c6d0366e5770a21cad426e109c2f40335a1b3aff3df" dependencies = [ "const-hex", "dunce", "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa64d80ae58ffaafdff9d5d84f58d03775f66c84433916dc9a64ed16af5755da" +checksum = "ac17c6e89a50fb4a758012e4b409d9a0ba575228e69b539fe37d7a1bd507ca4a" dependencies = [ "serde", "winnow", @@ -691,9 +691,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6520d427d4a8eb7aa803d852d7a52ceb0c519e784c292f64bb339e636918cf27" +checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -776,9 +776,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b2e366c0debf0af77766c23694a3f863b02633050e71e096e257ffbd395e50" +checksum = "3a5fd8fea044cc9a8c8a50bb6f28e31f0385d820f116c5b98f6f4e55d6e5590b" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -866,9 +866,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "aquamarine" @@ -881,7 +881,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1104,7 +1104,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1115,7 +1115,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1153,7 +1153,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1259,7 +1259,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1367,9 +1367,9 @@ dependencies = [ "bitflags 2.6.0", "boa_interner", "boa_macros", - "indexmap 2.6.0", + "indexmap 2.7.0", "num-bigint", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", ] [[package]] @@ -1393,7 +1393,7 @@ dependencies = [ "fast-float", "hashbrown 0.14.5", "icu_normalizer", - "indexmap 2.6.0", + "indexmap 2.7.0", "intrusive-collections", "itertools 0.13.0", "num-bigint", @@ -1405,7 +1405,7 @@ dependencies = [ "portable-atomic", "rand 0.8.5", "regress", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "ryu-js", "serde", "serde_json", @@ -1439,10 +1439,10 @@ dependencies = [ "boa_gc", "boa_macros", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap 2.7.0", "once_cell", "phf", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "static_assertions", ] @@ -1454,7 +1454,7 @@ checksum = "240f4126219a83519bad05c9a40bfc0303921eeb571fc2d7e44c17ffac99d3f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -1474,7 +1474,7 @@ dependencies = [ "num-bigint", "num-traits", "regress", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", ] [[package]] @@ -1491,7 +1491,7 @@ checksum = "ae85205289bab1f2c7c8a30ddf0541cf89ba2ff7dbd144feef50bbfa664288d4" dependencies = [ "fast-float", "paste", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "sptr", "static_assertions", ] @@ -1576,7 +1576,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1587,9 +1587,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -1620,9 +1620,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -1664,9 +1664,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "jobserver", "libc", @@ -1765,9 +1765,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" dependencies = [ "clap_builder", "clap_derive", @@ -1775,9 +1775,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" dependencies = [ "anstream", "anstyle", @@ -1794,7 +1794,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1928,9 +1928,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487981fa1af147182687064d0a2c336586d337a606595ced9ffb0c685c250c73" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -2138,7 +2138,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "rustix", "signal-hook", @@ -2248,7 +2248,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2272,7 +2272,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2283,7 +2283,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2405,7 +2405,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2416,7 +2416,7 @@ checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2437,7 +2437,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "unicode-xid", ] @@ -2551,7 +2551,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2702,7 +2702,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2713,7 +2713,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2724,12 +2724,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2747,12 +2747,11 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfbba28f4f3f32d92c06a64f5bf6c4537b5d4e21f28c689bd2bbaecfea4e0d3e" +checksum = "036c84bd29bff35e29bbee3c8fc0e2fb95db12b6f2f3cae82a827fbc97256f3a" dependencies = [ "alloy-primitives", - "derivative", "ethereum_serde_utils", "itertools 0.13.0", "serde", @@ -2763,14 +2762,14 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d37845ba7c16bf4be8be4b5786f03a2ba5f2fda0d7f9e7cb2282f69cff420d7" +checksum = "9dc8e67e1f770f5aa4c2c2069aaaf9daee7ac21bed357a71b911b37a58966cfb" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3333,7 +3332,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3384,9 +3383,9 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "generator" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb949699c3e4df3a183b1d2142cb24277057055ed23c68ed58894f76c517223" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" dependencies = [ "cfg-if", "libc", @@ -3530,7 +3529,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -3577,9 +3576,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -3697,9 +3696,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -3731,9 +3730,9 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "http-types" @@ -3873,7 +3872,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4023,7 +4022,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4090,7 +4089,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4131,13 +4130,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "serde", ] @@ -4160,7 +4159,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" dependencies = [ "ahash", - "indexmap 2.6.0", + "indexmap 2.7.0", "is-terminal", "itoa", "log", @@ -4212,7 +4211,7 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4313,9 +4312,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni" @@ -4348,10 +4347,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -4415,7 +4415,7 @@ dependencies = [ "parking_lot", "pin-project", "rand 0.8.5", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "serde_json", "thiserror 1.0.69", @@ -4460,7 +4460,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4626,9 +4626,9 @@ checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -4793,7 +4793,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -4861,9 +4861,9 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae428771d17306715c5091d446327d1cfdedc82185c65ba8423ab404e45bf10" +checksum = "7a7deb012b3b2767169ff203fadb4c6b0b82b947512e5eb9e0b78c2e186ad9e3" dependencies = [ "ahash", "portable-atomic", @@ -4878,7 +4878,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4888,7 +4888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b6f8152da6d7892ff1b7a1c0fa3f435e92b5918ad67035c3bb432111d9a29b" dependencies = [ "base64 0.22.1", - "indexmap 2.6.0", + "indexmap 2.7.0", "metrics", "metrics-util", "quanta", @@ -4897,9 +4897,9 @@ dependencies = [ [[package]] name = "metrics-process" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ca8ecd85575fbb143b2678cb123bb818779391ec0f745b1c4a9dbabadde407" +checksum = "4a82c8add4382f29a122fa64fff1891453ed0f6b2867d971e7d60cb8dfa322ff" dependencies = [ "libc", "libproc", @@ -4919,8 +4919,8 @@ checksum = "15b482df36c13dd1869d73d14d28cd4855fbd6cfc32294bee109908a9f4a4ed7" dependencies = [ "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.15.1", - "indexmap 2.6.0", + "hashbrown 0.15.2", + "indexmap 2.7.0", "metrics", "ordered-float", "quanta", @@ -4992,11 +4992,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", @@ -5026,7 +5025,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5285,7 +5284,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5351,7 +5350,7 @@ dependencies = [ "derive_more 1.0.0", "serde", "serde_with", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -5366,7 +5365,7 @@ dependencies = [ "alloy-sol-types", "serde", "serde_repr", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -5402,7 +5401,7 @@ dependencies = [ "op-alloy-consensus", "op-alloy-genesis", "serde", - "thiserror 2.0.3", + "thiserror 2.0.4", "tracing", "unsigned-varint", ] @@ -5442,7 +5441,7 @@ dependencies = [ "op-alloy-protocol", "serde", "snap", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -5522,9 +5521,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arbitrary", "arrayvec", @@ -5533,20 +5532,19 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec-derive", - "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 1.0.109", ] [[package]] @@ -5661,7 +5659,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5690,7 +5688,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5871,7 +5869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5922,7 +5920,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6020,7 +6018,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6072,10 +6070,10 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "rustls", "socket2", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "tracing", ] @@ -6090,11 +6088,11 @@ dependencies = [ "getrandom 0.2.15", "rand 0.8.5", "ring", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.3", + "thiserror 2.0.4", "tinyvec", "tracing", "web-time", @@ -6832,7 +6830,7 @@ dependencies = [ "proc-macro2", "quote", "similar-asserts", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6936,7 +6934,7 @@ dependencies = [ "reth-storage-errors", "reth-tracing", "reth-trie-common", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "serde_json", "strum 0.26.3", @@ -7519,7 +7517,7 @@ dependencies = [ "once_cell", "proptest", "proptest-derive", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "thiserror-no-std", ] @@ -7811,7 +7809,7 @@ dependencies = [ "criterion", "dashmap 6.1.0", "derive_more 1.0.0", - "indexmap 2.6.0", + "indexmap 2.7.0", "parking_lot", "pprof", "rand 0.8.5", @@ -7911,7 +7909,7 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "reth-transaction-pool", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "schnellru", "secp256k1", "serde", @@ -8778,7 +8776,7 @@ dependencies = [ "reth-testing-utils", "reth-tokio-util", "reth-tracing", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "thiserror 1.0.69", "tokio", "tracing", @@ -9519,7 +9517,7 @@ dependencies = [ "reth-storage-api", "reth-tasks", "reth-tracing", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "schnellru", "serde", "serde_json", @@ -9845,9 +9843,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4b84ba6e838ceb47b41de5194a60244fac43d9fe03b71dbe8c5a201081d6d1" +checksum = "f81dc953b2244ddd5e7860cb0bb2a790494b898ef321d4aff8e260efab60cc88" dependencies = [ "bytemuck", "byteorder", @@ -9895,7 +9893,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.89", + "syn 2.0.90", "unicode-ident", ] @@ -9944,9 +9942,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" dependencies = [ "rand 0.8.5", ] @@ -9990,9 +9988,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", @@ -10297,7 +10295,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10306,7 +10304,7 @@ version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -10332,7 +10330,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10366,7 +10364,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_derive", "serde_json", @@ -10383,7 +10381,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10406,7 +10404,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10505,7 +10503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 1.0.2", + "mio 1.0.3", "signal-hook", ] @@ -10600,9 +10598,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -10610,9 +10608,9 @@ dependencies = [ [[package]] name = "soketto" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" dependencies = [ "base64 0.22.1", "bytes", @@ -10716,7 +10714,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10740,9 +10738,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.12.1" +version = "12.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4d73159efebfb389d819fd479afb2dbd57dcb3e3f4b7fcfa0e675f5a46c1cb" +checksum = "e5ba5365997a4e375660bed52f5b42766475d5bc8ceb1bb13fea09c469ea0f49" dependencies = [ "debugid", "memmap2", @@ -10752,9 +10750,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.12.1" +version = "12.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a767859f6549c665011970874c3f541838b4835d5aaaa493d3ee383918be9f10" +checksum = "beff338b2788519120f38c59ff4bb15174f52a183e547bac3d6072c2c0aa48aa" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -10774,9 +10772,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -10785,14 +10783,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76fe0a3e1476bdaa0775b9aec5b869ed9520c2b2fedfe9c6df3618f8ea6290b" +checksum = "da0523f59468a2696391f2a772edc089342aacd53c3caa2ac3264e598edf119b" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10818,7 +10816,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10895,7 +10893,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10928,11 +10926,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.4", ] [[package]] @@ -10943,18 +10941,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -11029,9 +11027,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -11053,9 +11051,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -11107,14 +11105,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -11131,7 +11129,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -11175,9 +11173,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -11215,7 +11213,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -11302,9 +11300,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -11326,20 +11324,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -11357,9 +11355,9 @@ dependencies = [ [[package]] name = "tracing-journald" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba316a74e8fc3c3896a850dba2375928a9fa171b085ecddfc7c054d39970f3fd" +checksum = "fc0b4143302cf1022dac868d521e36e8b27691f72c84b3311750d5188ebba657" dependencies = [ "libc", "tracing-core", @@ -11391,9 +11389,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -11401,9 +11399,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -11730,7 +11728,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -11781,9 +11779,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -11792,36 +11790,37 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11829,22 +11828,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wasm-streams" @@ -11875,9 +11874,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -12001,7 +12000,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12012,7 +12011,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12023,7 +12022,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12034,7 +12033,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12309,7 +12308,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -12331,7 +12330,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12351,7 +12350,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -12372,7 +12371,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -12394,7 +12393,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] From 6f6b08fb55a6bf5986cecf3a1e1be0344c829e9e Mon Sep 17 00:00:00 2001 From: frisitano Date: Thu, 5 Dec 2024 18:27:43 +0800 Subject: [PATCH 31/38] add go build to allowed sources --- deny.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index d6d3bc5c21aa..498ac05beaf0 100644 --- a/deny.toml +++ b/deny.toml @@ -101,5 +101,6 @@ allow-git = [ "https://github.com/scroll-tech/bn254", "https://github.com/scroll-tech/sp1-intrinsics", "https://github.com/scroll-tech/poseidon-bn254", - "https://github.com/scroll-tech/zktrie.git" + "https://github.com/scroll-tech/zktrie.git", + "https://github.com/scroll-tech/gobuild.git" ] From 0bd6fe7a57f8ac0ba50971402190f9b48e9ba407 Mon Sep 17 00:00:00 2001 From: frisitano Date: Fri, 6 Dec 2024 08:59:36 +0400 Subject: [PATCH 32/38] update Cargo.lock --- Cargo.lock | 45 ++++++++++++--------------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d7e7cd72b8d..e57c4247d174 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.1.47" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c5c520273946ecf715c0010b4e3503d7eba9893cd9ce6b7fff5654c4a3c470" +checksum = "a0161082e0edd9013d23083465cc04b20e44b7a15646d36ba7b0cdb7cd6fe18f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -1774,9 +1774,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -1784,9 +1784,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -1808,9 +1808,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "coins-bip32" @@ -2711,27 +2711,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", -] - -[[package]] -name = "equator" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35da53b5a021d2484a7cc49b2ac7f2d840f8236a286f84202369bd338d761ea" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -7848,7 +7828,7 @@ dependencies = [ "criterion", "dashmap 6.1.0", "derive_more", - "indexmap 2.6.0", + "indexmap 2.7.0", "parking_lot", "pprof", "rand 0.8.5", @@ -11173,12 +11153,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] From 65de4cdf4cbd14fed6539b858207c7847ac4c80f Mon Sep 17 00:00:00 2001 From: frisitano Date: Fri, 6 Dec 2024 09:00:32 +0400 Subject: [PATCH 33/38] update unit ci workflow to exclude --workspace by default --- .github/workflows/unit.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 269d8a8ac88b..f66ec6a74d19 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -26,19 +26,19 @@ jobs: matrix: include: - type: ethereum - args: --features "asm-keccak ethereum" --locked + args: --features "asm-keccak ethereum" --locked --workspace partition: 1 total_partitions: 2 - type: ethereum - args: --features "asm-keccak ethereum" --locked + args: --features "asm-keccak ethereum" --locked --workspace partition: 2 total_partitions: 2 - type: optimism - args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" + args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" --workspace partition: 1 total_partitions: 2 - type: optimism - args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" + args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" --workspace partition: 2 total_partitions: 2 - type: scroll @@ -46,7 +46,7 @@ jobs: partition: 1 total_partitions: 1 - type: book - args: --manifest-path book/sources/Cargo.toml + args: --manifest-path book/sources/Cargo.toml --workspace partition: 1 total_partitions: 1 timeout-minutes: 30 @@ -66,7 +66,7 @@ jobs: - name: Run tests run: | cargo nextest run \ - ${{ matrix.args }} --workspace \ + ${{ matrix.args }} \ --exclude ef-tests --no-tests=warn \ --partition hash:${{ matrix.partition }}/${{ matrix.total_partitions }} \ -E "!kind(test)" From d7c44384ffdd2507312e2ebdac41d1a95a2d008f Mon Sep 17 00:00:00 2001 From: frisitano Date: Sat, 7 Dec 2024 07:44:20 +0000 Subject: [PATCH 34/38] fix ci and address PR feedback --- .github/workflows/unit.yml | 12 ++++++------ Cargo.toml | 2 +- crates/blockchain-tree/Cargo.toml | 2 +- crates/scroll/state-commitment/src/lib.rs | 2 +- crates/scroll/state-commitment/src/root.rs | 6 +++--- crates/scroll/state-commitment/src/value.rs | 4 ++-- crates/stages/stages/src/stages/execution.rs | 4 ++-- crates/tracing/Cargo.toml | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index f66ec6a74d19..fe76e1a4ceb7 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -26,19 +26,19 @@ jobs: matrix: include: - type: ethereum - args: --features "asm-keccak ethereum" --locked --workspace + args: --features "asm-keccak ethereum" --locked --workspace --exclude ef-tests partition: 1 total_partitions: 2 - type: ethereum - args: --features "asm-keccak ethereum" --locked --workspace + args: --features "asm-keccak ethereum" --locked --workspace --exclude ef-tests partition: 2 total_partitions: 2 - type: optimism - args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" --workspace + args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" --workspace --exclude ef-tests partition: 1 total_partitions: 2 - type: optimism - args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" --workspace + args: --features "asm-keccak optimism" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" --workspace --exclude ef-tests partition: 2 total_partitions: 2 - type: scroll @@ -46,7 +46,7 @@ jobs: partition: 1 total_partitions: 1 - type: book - args: --manifest-path book/sources/Cargo.toml --workspace + args: --manifest-path book/sources/Cargo.toml --workspace --exclude ef-tests partition: 1 total_partitions: 1 timeout-minutes: 30 @@ -67,7 +67,7 @@ jobs: run: | cargo nextest run \ ${{ matrix.args }} \ - --exclude ef-tests --no-tests=warn \ + --no-tests=warn \ --partition hash:${{ matrix.partition }}/${{ matrix.total_partitions }} \ -E "!kind(test)" diff --git a/Cargo.toml b/Cargo.toml index d5bb663cb48c..0e26d2e6849d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ members = [ "crates/rpc/rpc-server-types/", "crates/rpc/rpc-testing-util/", "crates/rpc/rpc-types-compat/", - "crates/rpc/rpc/", + "crates/rpc/rpc/", "crates/scroll/execution", "crates/scroll/primitives", "crates/scroll/revm", diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index 3fac728a66ed..099add248009 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -76,10 +76,10 @@ test-utils = [ "reth-primitives/test-utils", "reth-revm/test-utils", "reth-stages-api/test-utils", - "reth-trie-db/test-utils", "reth-db/test-utils", "reth-db-api/test-utils", "reth-provider/test-utils", + "reth-trie-db/test-utils", "reth-trie/test-utils", "revm/test-utils" ] diff --git a/crates/scroll/state-commitment/src/lib.rs b/crates/scroll/state-commitment/src/lib.rs index 89321843884d..a6995479e175 100644 --- a/crates/scroll/state-commitment/src/lib.rs +++ b/crates/scroll/state-commitment/src/lib.rs @@ -13,7 +13,7 @@ mod key; pub use key::PoseidonKeyHasher; mod value; -pub use value::PosiedonValueHasher; +pub use value::PoseidonValueHasher; /// test utils for the state commitment #[cfg(feature = "test-utils")] diff --git a/crates/scroll/state-commitment/src/root.rs b/crates/scroll/state-commitment/src/root.rs index f9dff61f271a..c7087d1b44bb 100644 --- a/crates/scroll/state-commitment/src/root.rs +++ b/crates/scroll/state-commitment/src/root.rs @@ -1,4 +1,4 @@ -use super::{PoseidonKeyHasher, PosiedonValueHasher, ScrollTrieAccount}; +use super::{PoseidonKeyHasher, PoseidonValueHasher, ScrollTrieAccount}; use alloy_primitives::{Address, BlockNumber, B256}; use reth_db::transaction::DbTx; use reth_execution_errors::{StateRootError, StorageRootError}; @@ -232,7 +232,7 @@ where }; let account = ScrollTrieAccount::from((account, storage_root)); - let account_hash = PosiedonValueHasher::hash_account(account); + let account_hash = PoseidonValueHasher::hash_account(account); hash_builder.add_leaf( Nibbles::unpack_and_truncate_bits(hashed_address), account_hash.as_slice(), @@ -431,7 +431,7 @@ where hash_builder.add_branch(node.key, node.value, node.children_are_in_trie); } TrieElement::Leaf(hashed_slot, value) => { - let hashed_value = PosiedonValueHasher::hash_storage(value); + let hashed_value = PoseidonValueHasher::hash_storage(value); tracker.inc_leaf(); hash_builder.add_leaf( Nibbles::unpack_and_truncate_bits(hashed_slot), diff --git a/crates/scroll/state-commitment/src/value.rs b/crates/scroll/state-commitment/src/value.rs index bc625babf852..4c37ad410bf1 100644 --- a/crates/scroll/state-commitment/src/value.rs +++ b/crates/scroll/state-commitment/src/value.rs @@ -9,9 +9,9 @@ use reth_scroll_primitives::poseidon::{ /// /// This hash provides hashing of a [`ScrollTrieAccount`] and a storage entry ([`U256`]). #[derive(Debug)] -pub struct PosiedonValueHasher; +pub struct PoseidonValueHasher; -impl PosiedonValueHasher { +impl PoseidonValueHasher { /// The number of field elements in the account hashing. const ACCOUNT_HASHING_FIELD_ELEMENTS: u64 = 5; diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 44a451e49b03..64f6d5accba1 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -262,8 +262,8 @@ where + StaticFileProviderFactory + StatsReader + BlockHashReader - + StateCommitmentProvider - + StateWriter, + + StateWriter + + StateCommitmentProvider, { /// Return the id of the stage fn id(&self) -> StageId { diff --git a/crates/tracing/Cargo.toml b/crates/tracing/Cargo.toml index 59631365d603..d944b5eeeb61 100644 --- a/crates/tracing/Cargo.toml +++ b/crates/tracing/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] tracing.workspace = true -tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "ansi", "json"] } tracing-appender.workspace = true tracing-journald = "0.3" tracing-logfmt = "0.3.3" From d84a329b67f5f4f4ac8502d6f89acdd28627e68b Mon Sep 17 00:00:00 2001 From: frisitano Date: Sat, 7 Dec 2024 07:57:56 +0000 Subject: [PATCH 35/38] replace TODO(frisitano) with TODO(scroll) --- crates/scroll/state-commitment/src/root.rs | 2 +- crates/scroll/state-commitment/src/test.rs | 4 +--- crates/scroll/state-commitment/src/value.rs | 2 +- crates/scroll/trie/src/hash_builder.rs | 8 ++++---- crates/trie/trie/src/key.rs | 2 +- crates/trie/trie/src/state.rs | 6 +++--- crates/trie/trie/src/walker.rs | 2 +- 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/scroll/state-commitment/src/root.rs b/crates/scroll/state-commitment/src/root.rs index c7087d1b44bb..675a2dbdbbaf 100644 --- a/crates/scroll/state-commitment/src/root.rs +++ b/crates/scroll/state-commitment/src/root.rs @@ -21,7 +21,7 @@ use tracing::{debug, trace}; #[cfg(feature = "metrics")] use reth_trie::metrics::{StateRootMetrics, TrieRootMetrics, TrieType}; -// TODO(frisitano): Instead of introducing this new type we should make StateRoot generic over +// TODO(scroll): Instead of introducing this new type we should make StateRoot generic over // the [`HashBuilder`] and key traversal types /// `StateRoot` is used to compute the root node of a state trie. diff --git a/crates/scroll/state-commitment/src/test.rs b/crates/scroll/state-commitment/src/test.rs index f5357a2c500b..0423d6ed3975 100644 --- a/crates/scroll/state-commitment/src/test.rs +++ b/crates/scroll/state-commitment/src/test.rs @@ -15,6 +15,7 @@ use reth_scroll_state_commitment::{ PoseidonKeyHasher, StateRoot, StorageRoot, }; +use reth_scroll_state_commitment::test_utils::b256_clear_first_byte; use reth_trie::{ trie_cursor::InMemoryTrieCursorFactory, updates::TrieUpdates, HashedPostState, HashedStorage, KeyHasher, @@ -22,9 +23,6 @@ use reth_trie::{ use reth_trie_db::{DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieCursorFactory}; use std::collections::BTreeMap; -#[cfg(feature = "scroll")] -use reth_scroll_state_commitment::test_utils::b256_clear_first_byte; - proptest! { #![proptest_config(ProptestConfig { cases: 6, ..ProptestConfig::default() diff --git a/crates/scroll/state-commitment/src/value.rs b/crates/scroll/state-commitment/src/value.rs index 4c37ad410bf1..387d7fd96408 100644 --- a/crates/scroll/state-commitment/src/value.rs +++ b/crates/scroll/state-commitment/src/value.rs @@ -27,7 +27,7 @@ impl PoseidonValueHasher { pub(crate) fn hash_account(account: ScrollTrieAccount) -> B256 { // combine nonce and code size and parse into field element let nonce_code_size_bytes = field_element_from_be_bytes( - // TODO(frisitano): Replace with native handling of bytes instead of using U256. + // TODO(scroll): Replace with native handling of bytes instead of using U256. U256::from_limbs([account.nonce, account.code_size, 0, 0]).to_be_bytes(), ); diff --git a/crates/scroll/trie/src/hash_builder.rs b/crates/scroll/trie/src/hash_builder.rs index 6eaa737a3f4c..6aea7fc736a1 100644 --- a/crates/scroll/trie/src/hash_builder.rs +++ b/crates/scroll/trie/src/hash_builder.rs @@ -21,7 +21,7 @@ pub struct HashBuilder { pub value: HashBuilderValue, pub stack: Vec, - // TODO(frisitano): Introduce terminator / leaf masks + // TODO(scroll): Introduce terminator / leaf masks pub state_masks: Vec, pub tree_masks: Vec, pub hash_masks: Vec, @@ -216,7 +216,7 @@ impl HashBuilder { if !build_extensions { match self.value.as_ref() { HashBuilderValueRef::Bytes(leaf_value) => { - // TODO(frisitano): Replace with terminator masks + // TODO(scroll): Replace with terminator masks // Set the terminator mask for the leaf node self.state_masks[len] |= TrieMask::new(0b100 << extra_digit); let leaf_node = LeafNodeRef::new(¤t, leaf_value); @@ -370,7 +370,7 @@ impl HashBuilder { } } - // TODO(frisitano): Enable proof retention + // TODO(scroll): Enable proof retention // fn retain_proof_from_stack(&mut self, prefix: &Nibbles) { // if let Some(proof_retainer) = self.proof_retainer.as_mut() { // proof_retainer.retain( @@ -405,7 +405,7 @@ impl HashBuilder { } } -// TODO(frisitano): Introduce generic for the HashBuilder. +// TODO(scroll): Introduce generic for the HashBuilder. impl From for HashBuilder { fn from(hash_builder: reth_trie::HashBuilder) -> Self { Self { diff --git a/crates/trie/trie/src/key.rs b/crates/trie/trie/src/key.rs index 1399053fb892..f90da1e5ada8 100644 --- a/crates/trie/trie/src/key.rs +++ b/crates/trie/trie/src/key.rs @@ -8,7 +8,7 @@ pub const MAX_BITS: usize = 248; /// The maximum number of bytes a key can contain. const MAX_BYTES: usize = 31; -// TODO(frisitano): Refactor this into a trait that is more generic and can be used by any +// TODO(scroll): Refactor this into a trait that is more generic and can be used by any // implementation that requires converting between nibbles and bits. Better yet we should use a // trait that allows for defining the key type via a GAT as opposed to using Nibbles. diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index cb02dec1affa..d0891e783597 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -121,7 +121,7 @@ impl HashedPostState { let mut account_prefix_set = PrefixSetMut::with_capacity(self.accounts.len()); let mut destroyed_accounts = HashSet::default(); for (hashed_address, account) in &self.accounts { - // TODO(frisitano): replace with key abstraction + // TODO(scroll): replace with key abstraction #[cfg(feature = "scroll")] let nibbles = Nibbles::unpack_and_truncate_bits(hashed_address); #[cfg(not(feature = "scroll"))] @@ -137,7 +137,7 @@ impl HashedPostState { let mut storage_prefix_sets = HashMap::with_capacity_and_hasher(self.storages.len(), Default::default()); for (hashed_address, hashed_storage) in &self.storages { - // TODO(frisitano): replace this with abstraction. + // TODO(scroll): replace this with abstraction. #[cfg(feature = "scroll")] let nibbles = Nibbles::unpack_and_truncate_bits(hashed_address); #[cfg(not(feature = "scroll"))] @@ -266,7 +266,7 @@ impl HashedStorage { } else { let mut prefix_set = PrefixSetMut::with_capacity(self.storage.len()); for hashed_slot in self.storage.keys() { - // TODO(frisitano): replace this with key abstraction. + // TODO(scroll): replace this with key abstraction. #[cfg(feature = "scroll")] let nibbles = Nibbles::unpack_and_truncate_bits(hashed_slot); #[cfg(not(feature = "scroll"))] diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index c815b7384947..db76049279fd 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -103,7 +103,7 @@ impl TrieWalker { self.key() .and_then(|key| { if self.can_skip_current_node { - // TODO(frisitano): replace this with key abstraction. + // TODO(scroll): replace this with key abstraction. #[cfg(not(feature = "scroll"))] let key = key.increment().map(|inc| inc.pack()); #[cfg(feature = "scroll")] From 2cd2da422681df191e3406f78ff1b200e7c4fcc9 Mon Sep 17 00:00:00 2001 From: frisitano Date: Sat, 7 Dec 2024 08:32:10 +0000 Subject: [PATCH 36/38] replace use of unwrap(..) in library code with expect(..) --- crates/scroll/trie/src/branch.rs | 2 +- crates/scroll/trie/src/hash_builder.rs | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/scroll/trie/src/branch.rs b/crates/scroll/trie/src/branch.rs index 48cf9acf59c5..4599749c1ef1 100644 --- a/crates/scroll/trie/src/branch.rs +++ b/crates/scroll/trie/src/branch.rs @@ -58,7 +58,7 @@ impl<'a> BranchNodeRef<'a> { self.stack .len() .checked_sub((self.state_mask & CHILD_INDEX_MASK).count_ones() as usize) - .unwrap() + .expect("branch node stack is in inconsistent state") } #[inline] diff --git a/crates/scroll/trie/src/hash_builder.rs b/crates/scroll/trie/src/hash_builder.rs index 6aea7fc736a1..39d2bed347fd 100644 --- a/crates/scroll/trie/src/hash_builder.rs +++ b/crates/scroll/trie/src/hash_builder.rs @@ -235,11 +235,17 @@ impl HashBuilder { self.stack.push(*hash); if self.stored_in_database { - self.tree_masks[current.len() - 1] |= - TrieMask::from_nibble(current.last().unwrap()); + self.tree_masks[current.len() - 1] |= TrieMask::from_nibble( + current + .last() + .expect("must have at least a single bit in the current key"), + ); } - self.hash_masks[current.len() - 1] |= - TrieMask::from_nibble(current.last().unwrap()); + self.hash_masks[current.len() - 1] |= TrieMask::from_nibble( + current + .last() + .expect("must have at least a single bit in the current key"), + ); build_extensions = true; } @@ -365,7 +371,10 @@ impl HashBuilder { (len == 0).then(|| self.current_root()), ); trace!(target: "trie::hash_builder", ?node, "storing updated intermediate node"); - self.updated_branch_nodes.as_mut().unwrap().insert(common_prefix, node); + self.updated_branch_nodes + .as_mut() + .expect("updates_branch_nodes is some") + .insert(common_prefix, node); } } } From c960cb7b482b1ff46567b663b6ab3d2c35ed4aeb Mon Sep 17 00:00:00 2001 From: frisitano Date: Sat, 7 Dec 2024 09:15:37 +0000 Subject: [PATCH 37/38] add zktrie specification to crates/scroll/trie --- crates/scroll/trie/README.md | 4 +- crates/scroll/trie/assets/arch.png | Bin 0 -> 54797 bytes crates/scroll/trie/assets/deletion.png | Bin 0 -> 37275 bytes crates/scroll/trie/assets/insertion.png | Bin 0 -> 33852 bytes crates/scroll/trie/assets/zktrie.md | 186 ++++++++++++++++++++++++ crates/scroll/trie/src/lib.rs | 4 +- 6 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 crates/scroll/trie/assets/arch.png create mode 100644 crates/scroll/trie/assets/deletion.png create mode 100644 crates/scroll/trie/assets/insertion.png create mode 100644 crates/scroll/trie/assets/zktrie.md diff --git a/crates/scroll/trie/README.md b/crates/scroll/trie/README.md index e925174614bf..8645b5efadd5 100644 --- a/crates/scroll/trie/README.md +++ b/crates/scroll/trie/README.md @@ -1,3 +1,5 @@ # scroll-trie -Fast binary Merkle-Patricia Trie (BMPT) state root calculator and proof generator for prefix-sorted bits. \ No newline at end of file +Fast binary Merkle-Patricia Trie (zktrie) state root calculator and proof generator for prefix-sorted bits. + +Please see the specification of zktrie [here](assets/zktrie.md). \ No newline at end of file diff --git a/crates/scroll/trie/assets/arch.png b/crates/scroll/trie/assets/arch.png new file mode 100644 index 0000000000000000000000000000000000000000..aca3f8d0c5ce4eedb5d40b8dedb39d226e57efbe GIT binary patch literal 54797 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

    -9R17$h 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

    62bb*C9PM+BO06 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 literal 0 HcmV?d00001 diff --git a/crates/scroll/trie/assets/insertion.png b/crates/scroll/trie/assets/insertion.png new file mode 100644 index 0000000000000000000000000000000000000000..942338a07f2ea46e33fc899fd222b6ae6671f5e4 GIT binary patch literal 33852 zcmb5WbySsW6F0o+kdO}P4iOOPhD|6T-Hmj2mwbft|}_RmIffEAR9jZ^51Bw?gO`WYAz=#FtPMLa=h*0 z+>r+s+atfZmk&`pszE6IZ|^kti`%>2S~`0fYV%}Ik!tbi8^(mKRu(6PYuii>|B(_5 z!jL2PWB1rdHz25Y4eMQOEw!OlEYM99@+sFlalVG}U&d1V0vEZHIbr#E0iIH6CSEw? zRK&>aQVhLytkieowGA`T(>4tbhjtai*Wfr5CL?KBK(yuj-d+^13tBJ zE1e@Nku2U7Vxr9{VV2mlWP^dG6K!?vjmuc~v3Fj#H!iY>V@kaQ(ZNcUrv>|e1O#{Z zV7WeYKpLG;XUrzjj!PpDe;-qFWJ6-VI;rEepI+FWCpGI?F1#|>DNle%f{S_;ls*Es z{d)nK%Fq_q%xc4S+w-Cnw0Cf>L3MX++005m6FMp(OXB>+Ph|Rz=AW}~3%H3xB-u5i z_GSNFIz*Pt>gn?(h{elv8pe;rdO7xMp-oX>Ha9P$uU}~y9o2jAha&@}e+&gUl#JbP z^8#<1<#n_Br;I{xd&Gh5tITLV$8AMQm=Sx`lm!|L`dU@R$A6dRn*(c$h1zYG)SaEj zq9ChZKaH5WAdq@aFkXEqq`F`!N}-@opEnZn?e&_4kw~~315G7qIwQ&x+u^k=}>h@mPK4FE9Y2$x1gD6 zIl||7g^6sFcDm9sM$I*3RjSxvrO;V<{(qtjOm*w44I9h0olf$SBP)thm0&?RnwWV5 zCxkY%O&|@wxviXdQ0bN-L{d39f8?iVk+v-}v?GV%qCm;CSBqN6tyK zcS);Ru6O&Y2JR;AQ;UW0Vbh>O;?@Y{J(%aKK>S~tl0kD~l3~1m)MN9TeY@`P1uT7- z8=yVUsY>W#_7q)Dd!1|d%6)#345v+!LLpE*n*rbj8^fRu6$U+l z#?ZIFQ`;ex5XGddUK!+$(IcI$PkcO+Jx10M8Z$;V;G@`ID-7-+MT`!n3aUsFI$OpY zLyJuP@zvSnSGe>A9y=4o*Vx#(oE$1yNAtMv6o}DYIQsB2N}oS|Fd(?&muqU31T!p& zAQ$NlExYl$J3*%W*T^T3i)MU^&VF&nsCq9q>K!&A;HYwAFy;_o+(Hf{4Eb6wzPhbS zs3kW@RtKK%_|8^#s9o^XeJD3MAH_WqQlxwu=hP)~7Xg2Xi${S~c|1tMcX5dtMCm{b z%cY+j(S-QzICfOv@@E%MR(|me%iRhf*6!1i;B zoh8utr0>}IH3#Z17OACa3YeJj=&eaV9kM;XIK$h*{|e5rxBPySTynR+N4s$AB11B{ z!7Tit_^`5N31`+k6qkvhe((50amu$#`PN`**NtEO7uJUc=|NX(Y0ymizhPiL!?bkv zPl0%dU*Nt#V9yh$D=rCqp~@I>i#JRFf5QlpwjXkEvBJfo*`gNko1M(?2{&>IgUa;s zcsfdO_ST2;*6Ca|JAq1=fa=%mm{x=7YkEsp*rxZo>~)8gZTtLn?wr#n-BX#YT_Oi` zzB~Nx1}6A)n`10&vs0v9lGURn+(i^CUwx+z2V7J07Btk_Tscj3(-s}?$Tfm({oGy9B@my3{?0 z=ByfULoSYWhYtvo-=yrnj+z?Cfl*1|LW<;bZ}PBaZE$lwTv{6!CWh{^dQ;g@1schhd*X_aUI>@1Wq#y1Gd=m0<#K+Azqoaj$sruWQNQKxMiPhPrE1NQ@`jV0 zkTgH$eagi9)2!DILD)F?d}*R&)P~D@-zwI;cwik}WZ|sk&F!|_xO6U@uB0jtj^(6t zsfvObBq$Og8*^HQGNKEutVzIF6 zt;-oRujWNXECRxf3a@i1*=Zx~o1arlKehvVD$Bom^|F68upj1q=V7gA=%isU?H`tw)~`Wpw|5buRkZ+3l2+J1>P;l znD#W)O(%c(s~7*T8ayrTsY^!Ny%D^blCcVMgPxT^U0AGIUKq?6XUc5Y^1N~S_U3*s zzYlY{5iZ6)E=R?oy&EeE;RW%NqDs^hcCb~IAwIX?Ssy$fJo&&I1|@|-#P=Unnkz>X z2xG8OiCt36151kj6xf|jVt!!xI>^B} zZif9Q6>X%9=D}gpfP9-dn5xWgQ~iy@(6`5Me9CCf?p}<+@vDqYf=FUX3#u*KR>1^6 z3o?R)VWMRZWXe9o%~7TfjKOXbfep96pQtaNqh2SJ{4!zW9Faa2%nI%1@L&r*n=OfQ zO|M7EVUIP?Jg&Qq368_5cSe>P{Fb;>{m@Z{@%^DSb*2dDY1kW8-!fD6l^tx9@C;~| znS(dGWcp=MgZJlT1_%d!uC5afj-*P#4URA7wut6y6t@gLr6;oCK2&K8$iiAF{B zcovyHL4&G0JgwuknC06(7bVBCURz4foupq8ZETqOxkAQIv$`9Zd&^uByogJi;`Y=) z9Z&Dq9I|Hb7dw+oeg#JD{UuoiI_jxWp+c~Jr(krG`N@y&(gra^QX(yW@< zAmhFtI$vS7UdGaqMQtCJtb#bkNx$-}%#38ZVuVV=qNTV*?@oS^3Z7^DZ2VdIr0+U4fu zA8LY1ViuI$pj98xszZ?QiohBhhy@-*^FFTKbpCONCYeKm;)zd!!-xCY=T&*itKuLA zORLQl5ZMbGRV>spD*|Ph4t|jBP~Gfr?=W!A(g0oOD6E`hGSjyDb9el{vahYNC z@>hY$r6y)y&X|*!6rB#3#`@0N={nMHJmMx}0Mv-$c(dcVaVuq%5}VQL5kqXWj($J_ zvaNA&fbkUvHM{^t&dZmTmNO`TX-r16mpa?;9blRwqet6lL(yJ^6_43`AZi{XxnCGk z550bb262fQ+=m=o&4{p5oGtn#6?=WANVgzmRpIbyOm1IMQ%q{7x8FqgaE6C%S4<75 zFdg)l1TWxeUetX>T%C6IQXIY89lK7>nX^>1{jJnp_(B2|{wLdG?&+&#dj+;MosxC2 z&CfC`#IIQC>??>A=SP25$eKNU1H#1rcg2rUiZeUVHw2IXr~(zFqMrW(0R+<72s(a3 z0S$sqm_UcnAR=upG>{^Q?CDEY2E1=1=*K9JK^zi@4U~xu$^xxgf$$MQCZ$H+HZu1h zkY2wDly5oIYVIp>3eBPnVj0{=^ACNChOGMC2*a%LOIn(#lLDs)F{D`irPcjylu)bs zpaMAGLXa&yNX~Oh<6LYkN+bXN#L&R+vi%7JN&$iLxsMQh4?q=ApfBnY4nJsIUDzVu zwIPD$K_^roEf`-e5FM1SKgf5Mbybpcq2{*Jp{xxGMARt@0zt0XK_}^#f%hd(%k$<> z+#uUQ9;a3mP#%;J21pAY#HWZPL4gXYzyy)N`Z9nd;e0;^YHFksjS+uyE79)x)@d(o z#5s$fUsJw){8ZV$J9>5Vf<_``ot2ayN-u+*V;Bmw(TD(AwWI2W(nA1cX0#YEVOfO; z1U&i?t5?b*<>!oeA)8@lFrR}ueOwNf{UFCQly)!e|1(|MVqd=L}kFt6srcY5& zdLd5gsNS9i0%;}Qz9uFHUUEHk(*opuc`=gY7j(Z#5m$|0q5pJ9;XRmP&AXQqWF3p? zaUiRH<|unv6?ur|OH>1cdvDl40B0M$mn+0qk99oIa&*qsdXsjqe|U2J zxOQix@Rism7f7=Hadig-S{}Sdx7Z2Kf+0qGBJM?O3lp54d!KpJ{dCn1l5-K|*M$c` zPIdHgm|sDGo`RsUd0(XFdEJym@e8Zf`zW;Pg09q8s^W)R{t3uIBK5pM}g=lR^tofNWLtgNM=OcGnaFJ{;`6 zM3$~G{Hn~A^-W0gus*aJ8ziLP^b%yNWc3n61oHLK5JZkGx$W1m3RR>w<%A5FdY{m} zz^(-r(c2>mItiG$0!u3=8%v@4-%%FGu zY@trrm!>ZurX6#{Ru3H=@+OcQAwiMTsP zOb}a3Z}HkyT{TjOsoN3*)oDGf*d}iA_^P8D38=-UlyodEGq|U&cDn6n^zzX$6-D5W z?3#KCmc#V-tU>*wZN*(H)l8)GZ)gT$7%@*7x?5s5jFg>>N_0%r4EYf-4Xq0}p(_%GG}XEb1CA(O zWNl*nZl#j43k}uEyZLT@K}UXBv-UnI?;b&**o1%xC1z*M^@V3r<6w*BI`7q7y*ij; z79P|w(fR`9s|lLGrGxdUikw?r5F>iu6+?|3!&aL1HWK&AfLLDKFRHF1@`fA4qQUD% zX)!j9q+39Pd+<7rk0M}mC2f66n~2u8{Z8eb-DPp~cGew1eG1^$lk}XWqKKY1lnB!6#<_$?iZVn(Z-R4*Gc@3pDTkr zo7D`74M|OqPc+g@N-5B1{v@V|rC)V>N_hLExSPi~C|tq%KRlp^D>_bz-X+WU z`lBj4+90CUZbk*!MG(Po|yH%U+5|{Ivpo3kpnL zN^HuTRl13cQbEXEPe1bj*RLs7~j9S~}h;&_#V(^|@MI;d5 znu*-{j^Fu?Q&7!>sMT@5o0htg`^lk?$jK$^@GRg5E_+a+Zhm6mExse7$|Ae%xS8 z5?zmOWd~ahwx4vvcwOuMOtizslK3evy&lYqtmi4$xPj(Z(vzAAlW+FW=}wPvoOM5yW<+h2^xoHAsXJbaN^OfjGRd0V4pcO zBIy2}i^Z`%nUO$pp$S2BA94-+47%K27IB+TU;Po>3cv>X(;|Z zgc7+fSW*jXp08^DHHBS~eLcGm_B+#$L=#;xFH&*4V_=AqI(9G)b}`$g6dr6BG=EAM zQN}b-fh5RI%}D0E%_I&hD*YwChX}qi)lB_hjT^)DHaJ_Z0fsQ2cmglEmj+o{+J7B8 zk^kL}O;m6XdW?ugYqA&>^2fu&lvz2BFf5|?{GZb7ZFJazrV2Y%$o-&UXBF0YZ}hx= z)LUz1GZsfJs(lL;r?5kP_lCJs7KN0kx5tRF0gW(lIpQ>&0@LtrSTk5sdY0(==HmrD zA*wf@fxFh#+leNXD{)6U3e7WSNI?gB=r#r}q@YtBQ_J4Z+t*3xG`I^-;z!Cu zQSQ|mys!kR{5`q^qwwB4QIOZG-ZYtEJJ)m*{4c_!HP^zY!3N66GDbGqpS*NK;lu7I zGbh<&=KNOgYED%uebe5hKLk_0;MxiyMtoDzKY^?j6`+_kC%zD{5==qg)O0OQ!;TS@- zAy&fVVWi-C^Ni?oak1ZmsYew`P$rns9<5nYvKBy59O)@sFA+o!gVHGybfoOh-4d%`}7F>tFH_z_|o_ z9EqLt0Td-H5)z01+&~*3{DQFhN?}w~_hJ}f@6+KY=YNr>$|7A=B7H;OF-9W6l;UES zZQE>D&*uDt?!=l?As`Fnvb=s4uw5utqbLPvqR7wd z{s)|=Fz{a3qV5?V5iEtJv+nj(pu*bDC*oA=*?55^ZX`8 zJ9YdUhFxEP;}=#tNm-+oa>ZKi+$GSz_2MH#Yr%v_+ijlqT~?Wz91hqup<6{{rwFZ1sECX)E*y_< zR)aaV9nK|4tkXNU7>~t16en&fJvNSUA~Pv%AYP4bMIR;a zt(7G(W3DtJm)mimOhUO$f>ra~{MN;C*sOuRvMi(MKxSLtd0B1a9c59Yp2?zG_0Fcp z%q8k&_hCzAo;rAa-t~ej#20va@P4m!KQHf~JJr@uraIX2ceI1n7vBSFc@RgrpL1*= zES?mF0*grEIB1qUJ2*~cWe!^P$jni5pK6|0K?wH&NY1~H$IzY>b1Yp*78=ci4M zyPPa92877C4Ugub-Y%zkW$hkRhg7~gtYwyc$nxzuKi{>Z@LTj}8?Q?|JvuDDy&t>6 zZ|&9b!SH{X{vtFxagKcXsMCmnOCxw#^^mv_H#^;6o80}0+<1$QS^1^yHr*#SvP5;# zLQIU?2We(>ul<@+7`q|y$(;@U{^7D&j+Sq@KIrMS;!!Bi!-&y~A09c8O&b`4zdgu2 zJk-6qFY)jqeZB&CuT?JH`r`K_wo9J!X=@nc4@b;9Q9g3U)8W`xpuHOSI5QZ_L~FwW61ueI3k_<#&azQhV2*9x(0akqXnQsR{k6Bo%@{>F13dr-@MxlmH~9o=y=YjD0!n%th|58-z0lt z0~VV8UdNNfb$DN29{n!VEs;Y<*NJN9+&p~;ul1cZ%=|1;U3}I6MD(M;xSdRK_Z|N} zIu*s%R%z~JToa?ll!$^d1!k+)o}jnZ{EtuG zYi-}XJbUqDdN2mcBhw47Yzc#dP)ZS^p50yB0{!e{_yx2*#8ki`sgy zsZDqCVAPjpn4b*+RM>@-XJ`zn6YB0xdsLO}!EA>>@-&1`-@K=@t@=63Y--1-Rqfr!u#`%o&& z$d@t}HDZ<%?9$jz^(g|tCP$i1B2t8pxr4;X#Mq+N-*#aP_K9zN`B6qGE%H-xop2zw zOVpZ&Gm85i^xiwRufdLl?lZV0UuuJrTXE&%DKNE(XC>X;4(in)=BITx_C zUI&p@hYk@Ej`r3%$wW;NN^U*4LY%#Gf_79~(jE)OAO%OsW~wz6!EAIeq6V+yA+@ zOE{tsV~D|Gtt);g86ves#3E;Dw*K=6tAH#D?W-_vqw(sg3!dw(4$+`UzcsA_8$3Zl zVDkO$1~k0uyTPPqUIJyPO%R9AJdLf2V+-><> zym(;=HO_({}fYw8~x|{cem$!<%aluUAw@vsZS2nL27AU^!QnvB+a_) z6{8S^g8a6er%{|^P83tcG|Q#66tB0B1RKSfN~!FJ4+g-n&AMe}?hV&PRTWaSSG*Nz z?o8xAv$kY)>oW8Z$y`lI?n}jWV0}O2En02nIb^~lHA~(f17q*!DYF4E%2Nub$JUP7 z#%Nsw*@7q7Tb;JvRSN*aSLD{HqQ3|QKj?trD#;` zuJPBHTq_3nS+%Cg%IfFz1Q#c}daV^DhFQw_q~`Oa6PWDt1Da2=sTd*^k(5?rG%(Cc z#0J+)G+hw>EnZRG;>rJBY9pO;BimdmbbNZNK5QerQap-_mnQ8aj7ISAwBvw9f%=46bj zgn=y+T1821v>1hcblEh3ZosTqY!Z*%>;?Gfc+37OlE?8G;Wx2;6^y>&@ z5_G^2AMDs55*Pgs^Ca09`yo~eCcgQ!__`;M^)=_u4)^eFuCnqZw)!2{>VHpfmnDzL z!xi(F5l(yIvOczZ0TE)HGm5qS%fUU(Ggle&Y)s)3-D<64hVS|vyxgP|J<>Tn&O546 zSctlJEeO(n;e44fN_k~58H!3GTT*IT$}IxgpZM4cSH9({4nJZw(`d)+8@g7as6IZbC(sQv!l{smrxIs zWCtuzLl+R5T%%0065B^9SJ>GOQ9F%&N9k?)G#S3uen-nzovUl;JequY{rMNH*uv7t!Iqnx-R;ig-GV~e@@O;>>-qzBGg~7$)L%_5)1S}cQCGZu zNWy={JQF=qqIbt!5D;!1UrJ}S3krXF)ezy(> zghf*TNk{V)#=?rk;m@7K)RN(*n~40Nl<`G4!>Q$$ygs#?i7q#VjWU876UQ-F8XE?{sRJCn0Xz^7K4^-TtQ%FFT z-i%REP_w=Gm!u@52|IbktcDJn3KHjkxx2Q_nkatdV;p~!8AvRQLN7#pdNz!x)&e42G03-egF|DYv3Yrol}{SHKGH>*=cs8*k(%*QJ3>tao|w&7 zDMyZD?df*~)00Zob+rxv`H5lTYk}0MpTQYdnFxXV%dNWz`mn zQ}f5iDSmMq>lh$}3wIUU{wz^RTJIU@!X?`&hsn59(qMc1579`{%&EFwz~oB$h2f>N z?0Dxnc0c!ZkQ2?=h{Pq3_7YOxgBL8U+%x{T@x@EE#`{6_-w$1iL10-FB@KtDt>0xW z8tM&t^IpeoJ!03nba|?a&)m0e;adB>+@*j$@Vl0y_2WNm zwGKGMoTHR$xA7}~n!un7!}yLB=*>ezasR-z&0rEZr}byv^&hHP1L%LrN*jIJ3xb=!2ffS|zv!5AiXHh8aHnNG z{**#$uCg%>X|7@3@=W^WaFL*CPlHkDe4Ksv#{<#69gH69;gu6nw?l5d-%Ce3ieIu- z36$u()z8i-HH*7jo)4D@U-M7Dh(8Y%)l^?q4u)%oWH+fE_SbJpQ%tJFUXc8o5h*In zucx-}5&HJj#OaXiu^qHp$7F{;oW{)gstLcyt9cNpy;+lMVRtsN4&m3r=lyyCGKsp|c|JDi+Ji4_K|!4-J^#lj zWYAR@lFRp9^PMqR?vqEBW-&V?t`N)of_snWzi}%b{Rz5YUujr{tFd1b#TywUDCi*x zh2URdx4%;d7zK~DSe%#q>+%)0Q-D3cfU)Ry;t%!P5L;E4kG2Z3j?POd;bki|Rzss; zN0W{GHysd!VJqOLtjqrZ=6xy~6m^GC58T$?8lpx5(bu4&SYtZaAmgK|v2<8Ep46(C zae}m)u-JJ0sn@Q})mT-m^mJBC-mrcp7T6gcr838VmSYPAJ=Df~PmG1Yx0J?INt%9d zmo_*Fb}^9(PpbNxEe1uA$q$@q&t>}qP>-j8@@YV*s^vx!iRYE}pm2D{i>*lShr{h+ zyS?YUW_VYF6pH=rtLtES;!fwx=ZATIaVuauMu0>6Q|$jZAd?=tJXi3kq}=4B=)3$- zlKFp=V2Uz^teq385sdi`)^J)FN{+ijlP2E`-Q|>gzZ!~7XOcEDTzU^SXILy`{bT7K z>J#(3)U#%fMZAIl;3R2s<+9UOq{jEi)EmrHX_~Rb+1lceG58OgMR(*eD{FQx%6b^s zIch)iJ^yY(fnF}U!@^?&%aad+hZf~tP+Fbh!et_%u5Bn|zdlz}J60Apx3zQR!`HdG zS6^Y#z9+k*~edJC0@bJ_-J*ySirAT_lH;xAI*rxs}C`(`^vOoLt5<_w+@#XkAWGKcf@6N5k zZTP3}U7iM4+(V&!{uFg9m-}z`d1Pk=;alDD$Q>?gqx#3F+dWi)s+q1uHJCtJ!FO+Y zyc9N1+JE%`R<+#z{`rqp3sQs@{NT($BjMj$X%L3UspN%7M(5Q#p)At{X`I@c_U3i& zTgo#&82TkmPyWeegqKsn)|rttNcWrK$6K<1Q3|20wTyfu*0l@BzO?EaMJF-qSQt7qc$QHg5*w)k2}^CZL_wkJ68sECRZjR zPr_;*>`;6M99d>U|C3EAY}x$uucbq|`K+(BaURpHZU>xMg(J0Mf>jB&^B zy-o~QC(_!Z1sX%%rBe=`Cs30JkbCL{3IFq;3J`21ZC8ZD`=}Ngya-k7F0_=j;=zyq zp;6a>S6M(raK2P66fs7ZcE(bM*L7ArGl6$OaJL?C{`JkI_!sFNZ(rN;Aw(EsO>^LX zFDi>;WYsBQC~eymQafSjYp`dqtX3bjtLd z0{lQEuJB(&+sHpH%K=8m=sP-!t!Z@o2=}IRcDOnX{ncN9U z+E=Z)>p+h<&7#EgM+am|TL26FRFuuuQ!Fi`Xv0cHF=N47#QkR%aU;z+=*IK|+KRBScBQMM%$Yd<)#U;1ffo7I~3+LZF83-F!)fgBFk&iwH zaDr&o0){_{9a~c9{Q-vAI|X4h5efz2zRaROBIWR*Oo;ZT2%&SsL+eyNjy?fYxNR9} z)W4|Wz(cDnP$t%SW`&dQ2=^+udu0H;LMuJ))gOIJ0Z2JUpp;c<65GSVY36VRs3<>c zrdj^1_cJ1$9d=ctu;XSF*;MG)Z{Qp*B6_?(ziLoq22grwY`mXZD8-1q0jLku@c-!$ zN8fcrtp;};$VxnNO$bp@q#h3PGD|*hpprq-wJNCRGD>LGYodZSu^J za3aOqP(Y)f74#)*HHk4}7HXMl!~Zz56*jIVDKEF~hiOi$3htg4W(||N zC_$bQ^EKM>^Lb3LBeWw!?4LKGkqYdv&%`krp+SD|9bi6 z`Q@&hl7E!~UIxIEfaFL>%plTx@DOZrDEU9l*t0d3rKd1unM#h8GK(QD@c=EpOIqpu zA7f`>{a6QZAFAY^xJ%lS;2~cnH4MISHk9sFIX-?MPzu69z2!qFdA0jzd@xs+q;sJ#rx!vYU9Z8+*}-a0Jvp}+11nf$1(wRT^^@s~~1R@O`$JB4%>&6=z%`ZRRK9WzN# z(tE@4tfCIS2U?-YywsnE5gwsNi|b6d4%c;UY0}@lwV@wcKX?9GLPv4`s7HXymF_QI zjb#2RTuxaPp1SWveC`Y${{>GybYyi)t1ToY#gZXh2k5ATl%TQOv(|G)4yj&N*$ykF zTU(!^$|M}g6aPxSb4EfvB-%tEZ(IJ+2N2E^;9m51Iw?|W6_*8uZHeDp_r?yTO68Kk zx3+Z{tgW=QY-1b>);Q*Tw7~WeJPTT{XIHv<-dPVLL?2@Q-++``Y}NFjmRPJ@fbhZN zzs%PaPf}} zN@qV{)%D%n?$*QIy9P-UPjWU$+>>=W@w(&^3EW-yjorKUZodgHyZx3Z>*fh0 z8*u9Ek#0r8#z-}A#JqTMf)-zZ?K`x9$(7Ll%-6Co{?5ZgQs-CEg1hVG za5fGfx@IjI6}>mLOOhbA5M>O-xvZ>@K3aEGvBXun-S!*d?5!M}UaHFsk>w4P;JYz8g(8z;08KfNhjul#?iodYi&zji*@HAj_2SxLOE;PKtC<%EcPps85eCdmJyJ3!zD`4^*D#WU@Z~Q& z1{RcG_tlwX&Cdj($gUc-x7T&c=j^%|z`qvCamm`clv zbyE`GiLvjzRD|%1e+M!|&*OhVG6exD{{_iVL*!V$$A7;73&FwCc`NHtp{iCsWT6jZ z(JbQ7^>Igwu&!~(nt8O9H&ku2&RmdUw~Le>xw>P5tp1- zzKYBJDr|DUsS3wYEQ^kd9t#yK^Jb>PPWeYoMeeWl1-Kg;rnV=Y-V4%l&s&+jt+yMY z!xnjrkD=PY%6Yf}_uSB$jPUc5S%OI=%HCpsztJ@IQ>99$ySsBVIJ|39h!?)agkpLL z2>pyR^Xp565ow0Er>XSw!*NA3u43!kl(Zjp)%ayj9}s%Wm2~tp+o&k4Hm27#pV93~ z5VsHUO-X)73Hak<`G^b+8kzYskey{BOMnT5`YhHLLwOPQ32)`axh>mf%l)QT=z(DR zW4~(NU0z=SkboKMo-%Ko+Rg!>%G5u;IU{vunVHW?aGo_8!%dJ&{Qjx#_L_NeiwD=;ZwkOX>s~FwDu$2e~bN}Yh};6>ew>9un+TDt|fC` z`ZU3oj}qbwk0=K*?+9kF7OBU{@DyOp+sTplYGy2Z;&Qy>I}}K7K7b6P|Btf&bLWf` z?!WGwAz9K;(?TZ$iLLm3by1#tT7qZlc~=9k2hvh}tAipT(XBm#+G}I{cb+ICS`El* zX|gX77IRz4H-Z+Wib<6%pM*~t2YRxo03iA0#{XbE%6(aEQ|`sz^Xqke&c5K#o!*; zBEmGd)T|+re)y`ty%#*6I=G03>8v@RfeOdW#;K7Ymoc>98SYk zk9$GAsr9JDgYioy_Lv-}go3^zJfLKz#~-EnpT`Vji{MUuc|GBeP zA=eqfXa~`JHW0nuOVWcWpMrrlxvE`_{f*Gyf^^<##}Hr*3qtcQE^UVEzWL47a0}mJ zEfN6}Ia?KfNtjrg?r-`)h(9rTzvQod8b6OkSU3YO>1-R``sa9c6%lU0GiU6g7& zB(J2R#$d2^HjX!bDLi#u)%QFkIu4wJ_>wj3-&?=bhm(E0)$P33%DwBSHuT#P@2N)O zP%)wd0Bxz%=oMcf)#zU6yLAOLLkAvC)>Rg$=yq$*|4;2GO;)bLlLUtbT-COoA_2Jm ziy#U9X_cJU#ADmGW06%&eaV|Wm1Y*iV*!|$P{0Z4^gyIfMNg5J`Ie5nXWV7&Y)~|9 z*{1}uE~(z@q-eiZ6r?=mBL|eZX9EuiP#fwvk6*zwiw9g?W3p;CR}C?9s{^;9&qWLl zn*^Mxa}q;*!+>~lvO}=7KW~1slFm<_XEMwBcP-9fD46#;I}FTFG561R*zqHWiAdN3kxtNXSWSLa75yKmSzT^cJ#bbL0vhQ}gfXhsj0$#m z{5NcSe)`h&(V+Qa1=mu{*uVP-mE2J|?dSB_JPc7G0}6j*{*hGhj+^(^fPmry-(ri{ zSnJl|^9)PwDqlQJHt}M8xAwq@@f(0X69U#LCO0gqpkI5R(WChqGL%S4w6=5nn_Tn7 zNoY4thL6>BbLIQn;rycKToylSktMvtN>F_s=7}VdVXC`JS-6LwAPpy9Ti0`qZ%e`-90s}WVcW7ngy`EJs z*8~pW!JIr*^9JecRQ6dJaf-jV0=-__)XR_W*HeYRPh*sKpwpySRd~+)LyMsnuaO7E ztBrmxp87xJq%QQ)A71VW;xD1zF*Od8OiDk?%#Z?PMzf=p&T`I_L>+mSzYiVdfSZ+N zPi?ZD*3;wP&?RhNV?s(1Q3o1wN+3$?X;hW`?aZ4Y?hkhoM0zTi(`+SKGhsgB5}HuDFHf=OL6^U)MgWfku|&O%G7;~ac<(ag?sAp0%kRIDl%wco` zyyGCt58>MFr+HcOlIv5DkH0L$Z7`uIeh@;1uKYxtSTQ31VK>n~zJDpLCHl3|qdd~; zAgeh5-U;o@|D~>t>4q7Ic=&VR#%fa`Mw#+#SKwPpmpg;^%41s!A6+R5L){% z<%a9-JD5>TdPg~u!Fj`dZ>TmKr5Ha)>1NqYy%M-F3NVNlKsltAIL7iz0J{BHmx3VB zINPe))Aw|a1yx1*jl?@GOrk5dY#5v2R-KqHbHpr|)a205y)5-C8T39inFOTCGeas{ zWfC4Sl5VKg;qLm9bdn>&9%#*gFerAzQiNfb7Am{xwbxRSYP9S&LL>s1n~k3(;KP+* zF>H~6{GP9uFB0NQ2fG=rn@AD?@D4-UA601@zTu_Kse5kW_a%NI+WG*S`Vr_0ZaFdu zUqVSBaZ!3}JFoVsQ}oO4$Vu^Vw|~|!SfA9DFt@`l%BKKU!Z7(-iJn3a3(BOYeE6y! z2juzs$u>1k)lfL%!FoTp;`)t7B!mSBBmPX+jX8`2?2-inw&aJ7B;YVD0dQonXEWj(a3_p{=6l(hwzi?4w_C@+ zGko|CdqbE^-fY+A!@ao-{DhA0KAZb|daJoDJY}HG?74ADYD)m;ob1|d$3RC; z>%8xNjGWGxd>lp>~8K#^)qzSAG=-klFc*?lzT&l4CqurKK4h`JF3Y3LN zyKE(}5E5)0QTv<9fN*e51&dr4AgxBI^wK$}H0}#x4OpnuF7v1RS6WO!Z~-~mJ3)X6 zG-;P;O)~Kc4J1J{-e*S7e!M74DFs*j+>;TOfPw#Zqn;)+?`GyA27tAR(dC&+Trw>% zi0cJ&l{f)wIsJ+H#%@n0sUXMLQzK(=V=d=rvj2bDd+V^Mx9)FP5Ts*3Ivqlh zk_PD#0SQ4sT53R2L^_6$E{8_aqkwdGjM66^PgkdMwt!}PXK)`xOx+sQpRT~Ute(c zm{1?Jr~>lpiYK7WBBUY{ySEiBZ>nQ`rw9n5TWYpl=AiJuk zg&jQaRd+teG}v<U**RyUBR33ih!xk< z7x{`bwya6}I6J}&aDa$5@#7LdRu#Yj+M;`)*M@`z00$hnQHxb4__vtn%981uOK%cj zD%FIek+@arGLid*so!bI@QcMCwR~FXXsW5+i*1TIdjm)aS?;8L8T*sI>sZKT<%HhpfQ*vIZ}6V{Qr9%Qycgs<2FQY z->%ZZNsKl>*H6cV1!i~BDcQmAxK9~b?`=4zo~sGx2|xqgDYkcT?lj*oBP5_l$bLp* zRTDk=TEH1Zwiw(yv=PbZm0^g{r9W#TiXKqAm7T+aZGW<6*yN#lA2B1c&GBTF5#bU& zBk9}rObTNLbEbJJ@OvB6J4cgmkGS?EaFTK#+Z2@shD>UGE#@jJ5~B1hEBfSrduQ9^ zb1m4~E9~1YaL9`8A%>k!-L2djo(=UKB7*u2O0M>H8&hn8WiB6>-k*8N(`npZdr8Sf zk29_o{ie8qk|&97evV4@p$ku<7xmEvW@Iq*^5CNqKuRRJLEy`oYquYq>HKr2pN;^S z-^#Srr!vPG1|Z$2XdW}y$;-Gm7V86%!GVf;g6?SIM$0w0&=zqy=c8bU32i&ORIhLe z%}hb0(ouPOguV%2z`?G?i>OLFJU)>4T8DCZ?dy=Qz6*I8L%r`8F_Wr^sRjc4zmar! zms71(R)4982=@lr7NX-M5|A92{q5}V&ST+ z{rR<5)_zvQW9#qFMw2i)O1UWMatZLnr`J>U5Lo}rv0`U6Y7u8KUb^cej~mD=9EOhy zs%Y-Q1M>?wZ%3G#o%8}gszu1&bmyASW4AE+^*n&hqu_(%+lQ-TLND?Z-CJsf!Bz0s zM=oghjsJ1`r~_hYq1ybGhul@0VUqx0Phr*QdAh!ymPhySuN`p_^LB5ZK7DR9v#unE zUkBLp(g&>T_qev-M#X!nWNnh~sul3UuC95$UY<>HA+m)TT?pbBbI%gHiqv_77xYr_ zs)kAnbQMbe3lOgVW395Wz9DAUfjb?5w+wp!GaoOG;>(YyLy`fLzWwQ~!RGtto@Z6@ zMM=j(dh-jZ#!o^C9yF6X`{AKt9DS&(1roif3GiG1jp9!RyOo51_2cNr17Y~SD z1!<4Aa8`AneL72ws^aI*J)?l&a32>I+=acqq$(BHI zif`X^^~mDRLo-bf0*{*Qj@kmeg4S+RUn|Z6m3lbDq4bu2F)ORXn`enFa{HXI^Ij#B z<%}LFEoW%&+gHt!th`kPHH;=#2R7JBER$Rpg+59>3;fPw0~~)SskbEwXGqeVV^#JN9m(U13-L%b|WC(e{|YfV*gJ9 z*kIN^DAJcEq;G3%m+wh0$5xUkP^zELC-Ka*#uwjC z4sS~>DcL(q8ry)zR2c_An+fFij2%LfwMRc*r#n}wo#!wSct=eZ=@0gvVD8%~0a3uP z;WBj%YHX!f(0238YI9+pA(RR?zEpF6ylSf9`@;5hfj*F3#y{1!UIqNKSnNIe?psW; z?Gz-0?p~-8)PfCwySp-Ytfa*_rTQm38w_^zbXdTqYFXNT?y;AcH*mfb@ms&d;^s0g z3(WV=YTq-}uvhVe^ZtSOD^;->{SV>~wjxSk^||Ho%UH;S|=#XdyZ1U)?e`9@C(Km^MV(6fa<+8jHn4d9F7Md zyzm8b&hr1!(CjH{dU;^~24hf^*|RR@k+~qciY^;$Pd)!SOI}l$S=prP>USd*6Mt42 zC8i0*cBMSC78Xv|p*~gxPPS16UcSN|r4l>L1{}bB)NGLZbIRYEq4lRPV}D*ke) zj;Q&4{l`P?RmK$9Fvo^db3z&CC4{z4^-^mvxn5Gq>A9r!s-eLwttP6st#LRm*2RR{ z-J17PV@2%t6_wA?#lx?~!mX0z?BAn;d4s+iPb7xdsd~$IlGG>bN~-|3OFT^XMk6JZ z{3v~BqR>)te~_B)Dy1}CkA8AP*n`6S$yn&9D!ctrrBJlioOs%P7@yCN_?EXO&NiF` zP59-%lA$k7Bd%T^r#m=Q>dt-!EKA%yf?8Vbyy#*>RoM5r^3V1x_}A#KvS zJnuPND8H3m=Hc360gdD8d`(?@>;wSo4oi^c0~WIKuZ5IMUR9l=1DqcxJ)hwdgE7KUs(Qe2N9rcA>>>=z)$M z(|AAx^uAR^u6e{Kzyjjyt?OZ5t>Mbs8`@rwRYb@Cs5P38RDQss;v@}t?3bxxadJ_x zZp7@fkig-*SNn}q&_X$rG3N(x148$h)U~IHej4&Du)sYrp&Le`{yx!FF$?=6LIZM+ z{$xY7jCkM#1IH96QQ6*1yjBT*b;-~B9Dw{&z=aJxut z7?8goh+=Z#E%JNsW-ab$p7#Yfl8_mS3mu?+N<#CY+tJ2Z9!{J5AHn+Cfg|t?a?j<} z41&%QZ_U6hs{z1x>5B)7llVQ#_Khl9p46hwB-i)b56ysmmeQ28Hk>TXalg?AkZ~)f z-8v`Dksy}cHJ-X}n>rNVDjx5hP*@8HWs;x6fjdgsf_l_Z`f9T~4G^YBvwz9tZ;kO< z`#JnJEIffq*XUht1AfQhxekQ!EInc?tv0hjUQy<0AQkru`s5H~cfB52(tzAe$Ub6u$gA{#C((;+Klu9iOgt3=MZ{ zg2+l;?X17CthM+m0ldJ)x47FNZpb)T^5;#CP*pzZ zvDRtSb)MAUaR0A}9Wl4B{vRP&U81hd~?jflD=HZ{eAPbmH^1=Ee0V9dkY;XQTE!;fy z(;C3_0>&!tYfISnCUxdQveN?y5}F5rAmP*ANT>v&^7dua^^3}ME1bfDbFNxApDMc&frs<|8mT*m)FiC?A5t*`NK6=_RNvV94M6 zFSUv7rv<%whww}Wg!6Q-;}z54FrAO3T@dh)4Zen-jpe(GFWvLdaS3bWw})St0eegc z$~;nT>SzV<0D$j~o@G3^Yumi=reoEV?%2^@3w@A(7hVBN&I87prB_ds7N{%yf%m_v zU5Lc82#~B8N1C;SSiW}VI^AF#nPV7(NQW&Z`ha9)t8mS zM<`%%gGT}I#QQN@H}$aA*8Q}@cpwa}fiNecGk}jVNrWVG|Aw2bh z(puPvTf*|kdh?=p%jsXSKd`D~j61abad5JesG?`jhq_5=v9*azL+vx5VWN$K8qzVsjTT0?( zlnd)VeYah3sA?K*0r0C9JH$?FSo|sD^u{=_{co!(3Bo_SEmYYOfgrP=)?n=>4^hlc z?}qr2(zXbh)0y8zCdJxQkuP;qgnJ~Kne^;e6g2R=&EZJjVVpUS)IGyJJfhQD^wg7Z z(qEV-Z^dz=zWdkMj%Cx{4ZG6vqS>+#pt13QgzZ4sf*yMx0DOVOm=3XZ$Hs0m(fl>I zGapq!V5L(OI_p4`ahoH4Kf#c&qf}<4a8K``k_^qr5wQlx9%in?nSBHHFx) zJdnS#Ithr0ltM$dGw|w+6cf8N;HMR^56Qt;^8TcnlrWpS)=@E-f&T5-ik5h!nhCPh z@BP(Z%8|ukAkFEY&?Nfeb@MOaVo?cZhgITYey3)&Y$fYQ$9)K1YTh~bi}hYq8=fg` zENr-(ZMnYm_1W8Z94)>W6>~+q7VK;bOil7zH`iCYAbgdGr$iyA8KFDERWl(ZTTbF9 zn-0a?++&FYlNV}+_U8>mCkMUN$n#RJ&6j(V5q`d2)h(9w)5ofiOxEk-!@7#<9F{Vx=W6@mzsMR|Vnm(K)Vh^Q4{{%Sqv? zni?L5fvUl|s^jQ-?$p^>6Ml4~>Vf?|YsfH5;81+NpoS3CYpwnHSe#@L(O~^niv9Q) zC~0#Gp|rrC)C1vnpyMxgP&MQ)KCHF(v~Xk}KUr(+LiDUoOUR7GtKfA*IoVeB(Xy*^ zj|$l0#q3|fTa@0dytSx#=N3INnYx|Tr?1Lxfi!X8rt_+;;ocq{CN~(1uKdGaBI*MrsYBa!U*eS;0pVxszijgfB+J zWn^T~oZ6%TOJpCe+1N3xKWwy-&g?A z45b&s6>kl-!`}fza=lTbmzKaAc5jsJ*u|Hy?k7H4+4t9+^%IXr)PV_U)qu6Efqon+ zW_tl!*gy*T_S}qOT;Yab^*#}9_Wl}_3Gh%d;Dr$Q$1PyU9T|;YmUw0`-MMl=W+$II z2Rd`I03IpB0*s=QHAdXUkOmg%Co1f~txO7YHo^|R^GO3OSZw#z;=d6ZVp-$?FYetC z(w>@>1P#p%tH_|!#$bBzv;copEzOhiPT#P~FhY_;MsvuaZU6UbFW-Z%#Oz)XI*fCt z%IL2b@$A5w1fy%@O&7Vq@$Y6ekxy}0Ui{2uWxjUkHPgw{k^>0F1-NZPj1(=?Ne(G3 z)P>(Rqy`5Lx!S)l!HT{@sQB#}nv?TEn#^Crf(q>|k&ryPv~*w{Jvc9C>}-LF zav4qF8IhmEo-I6lKu>|E3_r=Aj8g(gH1TOiDl!9;CUU<}V2%OvhZkml4NJF$I4>E% z%+?^FuW9q|bp`g@0 zE3SAm&f4in@A$pTiRF{~UQFPM3Jvs&!P1Xz`BAuc*){90dZ3?KIalwi-o=1oJ*<^J zFGY>_pbIErL4$C!7rRL{EEl^!R>_Txk++52-D3Wsqh@b(RCRUBY-9EDc?=O9)8NWm z_*UJ?5u0bRT3eFScl$5*{nUd>Ti4ls^`MvFj)$gRM4FQBVCHLXx~v5!yrXVk2lW6kF{G$<$eKRvE-eTVwD?q^xU|OMZd_LHvKidCEBL^B$$PGT_)=+C zi!U~hlDE1Z(NwpPn8;9Uc)bdXNrb1nG^Pe~21jhFY6iS+lk#4vt8tGN|3L-E@VziP zPk!BHoXC8~*7i1_s4{-_K{NL9%Q!L4OUY${-t~#}=S17%)=PLy3{yLib2D&n z$K~E*x?~}7cnYzd`A4>ESGtGirF7DXZh(F}ZqwKwkG9AkVpCia=WV>4PP7vl__zn- zqa%jyn%wpgSzJYMPkO96Wp0bS>uOo$`RLXPzbJwKJPtSgti(KnNWQ9%hM(2%Yrhu3 ztdQP!VbRPQQUqw_U9Ei_EGZdANZJ}*uFVJ8jeYFs8=u$;g~Dsx4g1u1pGi7u4KTh~ zjp!&H{B~TojoXc$CX_=>pXFE4VIZ^4>Oo&k*8{lr}f(K?&sP&65N3@c)-WGexDz@zOBF->cJg<)mFtzpPs)==~nJoQ7yUM z;wjcw?ZX_-I~8VnMf6ewb+h6$p|GrfarKg1S_Ctiqd6mt-gP|#0rlEy@%A@&fOSr$!AV0pd$IzosB3XWM%&G2t{UFEAqL+);&nj@pLFgXy zrAymGKS$Jy$!PyA4b(KQV9C2$U;{l2*3A}s-(FKOT$N|gjwftqp?FCoS{AAk?ebdO zI(kqF=0&{wk;i+2zKfPnM|rN_Xw)1Tg5UOL9BN3~Y@tU{31-C~5gM$s61%a7rkS1# z7}Z`OrCuEfD&%T=f2~%xaTN83UICCtpfrgieeP7u_4Rx+ z5s=q%5k34!KG~V)_8WZ8Lo;Y~YZBJw;dc$RAt!+?+RF;+Xt5QP zWk?W_tlo-R{k)s{Q+v1JRNLb89A$>Hgum6uFfFcQ`;PY6i@zT04g3frM!%#1?_Z3Y8yi)(NyMhr(}>?6Ke$ z@4j@f)8(q{fO-u|d92;vj(V*(<$ofpuBMJDokv_yc3vj^s25szP5K_C7xR)Eg9)7m z0d^Q$emOIpHL*QDSVputHmTGFon=?gbZow?yIOKFSa5=zEg6oR_%48M8~ZK1>1mZl zCd5nISsSAn=pMcKVC;FKa5>rUG!;sG{i3>M)Ho(f4z-e=Rcv@t9KNkiFiR(9~k))mT1fP9L`s zIB+&zt}+)XvB4JpW0fhZ*ygR!WVM>bL39X%&yn*ep!tp>wQ+)4Q@>M9PkS!XWH~rE zBB|7YmY8BMhcr^-_b6VCO=z>7T~Q4 zhN4Uxe~F7A_h5}ko$Gf6+vpPC3`I+_gr?X;()e3?b-q_?S1w!0r?V{%RxgUJ?gyAw z`)b$M;+~ZO9)dT!7Me4*D8!%STFnKu_kCAs-1v3minIllFI4@M6upehvmZ50e$3RR zs){;(%l@`=y$dr;5Sa<>mWDiz6Gx+tS-Q3t58O_M z;pd6Qo?{#{dy}DCmQ12cZ@kxPm?pMxpmv{6y){N}f1gqtmpr%(=0&8F&ec{*?pct% z6ugd|;bbn6Jeirxo$-MaF`{D%kk23C{kb~!YgYkGCKTZ9SbSI+s*tYzdHS6@s~)(Mu#ywnXtCCq_Z{QXrzKBfqV~3f8++vdxJ-U|7 zounR1`|XXy8jH(Uc>=yZ*6Vp1+)YmVfMRjAH6kLoTu>sfZK_8;*5#@-V{zYvta-5o zWZ5wdo6IHyj@`@(pd2S9%Nx*_2Xj@ESsH#mkkXu-(F>BJL%x)%_PPEK1dgbKl;@2` zp0m#v3$8T{T+zc8`A8D!`it}AWaB#2hBoxa;s8;DEQ%J(BzBt}FeU<6zgeUGoZ~5E zj79d`&O5BH3+jN4Qa^k4o2K_!-;o@CuD#MPeVl!|eU1LvDA=MXOOR>MG~(#FwZ59% zpQ3s7srT{m6VwKlK`h5pnUhrr^}=eWM$Qai``Z!GG8U@tk=-z=S-+JMd7QzzH5Z%? zi0b754x?$kN5@8FGL*B$5IO%N+|(jkbXx;(UG)9`>AL)w7@C@$rFNcoC}fYm?j}vH zt@3pZu8pvkJZj$kJBV6CUO7pCqLIWdWz4_Oe? zj@7L+9WV9mWYxYszEIQS$#iZNigr}3M^M4vDzCQ~{Au$4E0MD9D`Xfl6v; ze)9uH;P1X?DI&cuVj))iso^)i#JywEINa_&CJ!H1Jz@95$l;jAY&11W>NyH^@WLSx z^x;aUlk?i~Hz8`tiA2{hhBW0=ZmO;_3Y9b`8BW=oA^gsIs(rm`XDZ(GQXz_+PVG(w z_X`;eN2dhl8p{`+hb#E??!l~0jLr{wW0G1NnoFJzxicGw%At(#3YrK2N(7Puzmamw z>7xWlJu&fEO?7Zct(={ntY2+m33Z^a1mY$4|9QORxjear(o1sX@^DauOEnvqRv7JE zunA~fIBqxG-TinTURYD3yB~S_NXTLH#?vc2M?`0-Gy*48f1r(WWjQ+;qKND+i}?SW zwc_2M3HUO0-%pvLe#NGC3*kWD@%IGbS#%iCoI*;NV%mqBaJA>$&b-%_44y#b(ci!c zxW1&nv0z~ct!UYyrilR}{r4;23+O}Gv&pdhW-v46UoX>-^7@25(OZdAX!cFe-l$+ zevQQnju$q!E&ZFAKW@9fzk(&<9a**iySTqk;{OEavS|Pu+XS*7ZGef!74VjP9rXDm zLPic(!M7|q)q(qm#B4P#3cIr$Uv% zRtZibmBa#c<`BDso$K^eNjpd+{>Y)BK;!p*M~+3B21P6t5U%f@ihy@V|3xZtr3tge z=f~+&LK2@}1dy8Y&|YKT#<-f~gBg_#4!7OM0+(M?B^N*QkN$jzb;4IE203wujOk>% zHxFz<`|X=jn`z@`$;IyjATY=Y6ylbg3R->3HmRrn?DS{zbBsQ3-xs>sF;KzEwt=lf zp#r!~wK5efXuv)^b=w+Jn&@AfoO(6be|Abo{6Q&ApGn}-CH(PAB2-Z7l@pC@zNXNp zurC@pndC`Cic6XTh8cSIFwApPDfp+$WlwZz3~+)ppGRIWA1d>K*86aL?d<}8)`~bTY z5fE?f_cQs!ce(u?)68X6>Hx7|SnS2Y=7+GNxD9=Xn~j1me6F7dUw5J?t~(0F{*^B8 zm6hBH_qANv{39mZ_fwvb6NOhFsFB^@9$ZiL&zeE5&Bqud;1&=jK7kE(0#QW2pA`iD zsFFNLht?`TNT-#l-`>1T(SNON!ucB<3rRYu`%IywtKYHL?hKlWWRqo&yXbyu!cYZE zq~?0bw(!zYm^4RXS6ATDTflpwpAmm2kGJR-uF17ML_<%E{b3Rh=5wZLAyQa!Dos4? ziN~kZD7L}$t@K?I0Ap<1{bGCxTFfe*0+q0aokb^2$ z>{+P8@9CuMYC8mRPbNeNo}lP2qfJa>+TShkI+K_^%i7_qbP1{fNh9zCTW>?$YEoeX zN+fq9GWr=lxxKOu@o2PW?#-9>O7wE%b7&1r5+loy1xK^e;;~)m+h_Ky>ew4`R;+!o z1_zKd<;#|)eg(aKmX1Zm?#!++gd{w2#2#wKx>J*pMWDdInOvHN6d%~)lx5$w4q#Ta zG^4iUE#a$-#m0!pe)IN?wBap#>s{CJ`5%lK4Zk+V>lR!a?k*0XX&KW|d{B%CSa zdFsl>DLgPJF@lA zG6oumx#&=`dSgm~;hOr5J? z`CZui(GHI0Jmpanl+6)H0s?VLMN=X&UREBZE>is=%+%=CNV(IXqP>B_t>yRRAsw|e4M+wP%_xms#ls$8Q z>!Cj3!&SBk-AEIx?0Sgsqm=e@x+h`ekCe%xJUIN@I-Uo$$#^ni#3$p`+y%j?@0rI( z_On>Cuw#ASS!Q;T|Iu33T_9zmpb+A)qHifx|A;jZ+kAH}$ct}mkI?d+Y81ajECm@= zh^ovz|B7SAJ6`sI$qC)>6DE5fOm)Ar&?XPNc=Z6Rzfb<)CAUSIC3bnWxm0(1#GH9w zx@tQm9=|15#JM23m~2qL6b&vr)F+I~@PQ1*b&gCG%$VyP1sSa44wtC{<)H(LL>hA6 zAH8Sq6P2y%4;JrrHT)Lpi3bXI3USdd-?m@LmP4?$1>(jy&fR+`*3uSuiY-7#X$eYY zc@-RHdC!2Dx043O6yc01O<-}6Us#V@c8m4-cgzoUVa6akKL$$@kkU9d9=#h)5=HkO zMI{xb73%|1(|)S3rxmaX*Bn22d!Bc&TliFBtQH5co{D`-kx%)HK1C1XP{yOADeIfu z1E6uA6!nA7vvCDROKncW`pn9S~EGPx9{};Q1%$3G;uFO<2og$N@T=At zXJ&;@OM<7nWj>5Ss%3QJ`w6GqsR@X6V!~JrpZ;RK^(K=~f}BP;D>>?seTff9*`KJk zRt}{6;hsYl6qG=k+n|trH{dHt2so}M(O!Zoo+Xg1k$X7^H(yS!G({tx<69;u_Uy4f zM#Z7!lkU4uc=>(K{g}`*ihmwLil@J4m?Hb(ohi%UL5D@}e%`R|wVGEPApNy^dj}X_ zfNC8!!UYs1%~$F4nZs9+#uaY6R z#x_;Lv#|6~CV$UEVA&ZFY4K=FreeV4-%RfW zm`<3ub*GS+KkADg=;eHSw*q~{Q~ydyNuF3@eL27;#%S(6GFw&*iKBqt11X1K&3Dld z^LrYJGE7OHgUMDc3}ZO&!oGY&&Mw+xPt91`d9bgaXr(@FzmJriEl|LE8w`$f;KJnN z%-)M`@Bz_ClG8>!C5%ZBw-%Sn%S9A5{bA^7MVILKjN)IXAOmVCS56A-i7ZYqmkP^r zPDkkxJ(WEisCMbs4xq9_~1f!pJCSbEJq_=B+@?`HV%yZ@rH9nOD z{3=T^Q4yIF(7ZxKI$lzu=(=0f!)dU^R7n4=)ULIT_TH=XHuJvdV$nhT4^|3CfSRxu z=fku3IxSCXaol#;3xd&ySP|gew=!ReVq5kU{w^$?{lg@Xl(+MPGoGFT*0cxf^w*I4G0{_|wFoE?q9nh`Q1IP#z#tgZ!Iaix)9f_gxC zr7{j)&8t4^$HMG=^CBljW6_%H*VlM~(6dL@Q6Q0Y@ENyEB{ z72qo$;2Flx6c#ijVxzEEg^{*$-62XUDM6&YSj>|opMA1oFC438u*&2RIdfbp^H0aH z*zHAFe&9VpL2Jf6DJ^c>DX>nNF7JU412q+=1@huDz(GA?s>TC~{sQ~y^F#=RgVz%F zA6c!U0(=}7iCSb_D5Ggs|b zs5HOEz6nm`8jZ&e-xpdR;hEd#lwX*2 zw9#3I^`Ro|F$~S9>En49Vlb?S*u?!CR$Dz@c34PIvrSC`hJ?14QR!7MrQiLyx98+i zQv=VYEd|6_dIZi$m?c`eVE#T?){eNoA`QVtPZdI3@C`4y>{Hw7_FOUX zkm`ZN-!mg$$s49P{n8@lrTjHA;B(uBpZOe_muasE)wqM<*6{MyJoyB>QrX!<47U#A z+b7Wj@2xXE=LN3q7(2FtOB3E3mf=1cloL>5{_%3}#e&FBCZSg_-8C22GvfW&U<#TM zlsotjzUGt^3$(eP84f6meKzhX*b(q2&L;yDS)v}KLGANKTmT(O*4IK1dE9Z}I{H4b=F-jc4VH}@79-2r z);8{8*X+hw2l<6D1gAFbdB^4Wkiwq)>Ls}o?DMfF#U;iE>3`TWPxCA@n)fG&`^x@T zCI-mI$TR@Sm|VN2QN#ZvWA@~LuY@Ofq67X +zkTrie Structure +

    Figure 1. zkTrie Structure
    + + +In essence, zkTrie is a sparse binary Merkle Patricia Trie, depicted in the above figure. +Before diving into the Sparse Binary Merkle Patricia Trie, let's briefly touch on Merkle Trees and Patricia Tries. +* **Merkle Tree**: A Merkle Tree is a tree where each leaf node represents a hash of a data block, and each non-leaf node represents the hash of its child nodes. +* **Patricia Trie**: A Patricia Trie is a type of radix tree or compressed trie used to store key-value pairs efficiently. It encodes the nodes with same prefix of the key to share the common path, where the path is determined by the value of the node key. + +As illustrated in the Figure 1, there are three types of nodes in the zkTrie. +- Parent Node (type: 0): Given the zkTrie is a binary tree, a parent node has two children. +- Leaf Node (type: 1): A leaf node holds the data of a key-value pair. +- Empty Node (type: 2): An empty node is a special type of node, indicating the sub-trie that shares the same prefix is empty. + +In zkTrie, we use Poseidon hash to compute the node hash because it's more friendly and efficient to prove it in the zk circuit. + +## 2. Tree Construction + +Given a key-value pair, we first compute a *secure key* for the corresponding leaf node by hashing the original key (i.e., account address and storage key) using the Poseidon hash function. This can make the key uniformly distributed over the key space. The node key hashing method is described in the [Node Hashing](#3-node-hashing) section below. + +We then encode the path of a new leaf node by traversing the secure key from Least Significant Bit (LSB) to the Most Significant Bit (MSB). At each step, if the bit is 0, we will traverse to the left child; otherwise, traverse to the right child. + +We limit the maximum depth of zkTrie to 248, meaning that the tree will only traverse the lower 248 bits of the key. This is because the secure key space is a finite field used by Poseidon hash that doesn't occupy the full range of power of 2. This leads to an ambiguous bit representation of the key in a finite field and thus causes a soundness issue in the zk circuit. But if we truncate the key to lower 248 bits, the key space can fully occupy the range of $2^{248}$ and won't have the ambiguity in the bit representation. + +We also apply an optimization to reduce the tree depth by contracting a subtree that has only one leaf node to a single leaf node. For example, in the Figure 1, the tree has three nodes in total, with keys `0100`, `0010`, and `1010`. Because there is only one node that has key with suffix `00`, the leaf node for key `0100` only traverses the suffix `00` and doesn't fully expand its key which would have resulted in depth of 4. + +## 3. Node Hashing + +In this section, we will describe how leaf secure key and node merkle hash are computed. We use Poseidon hash in both hashing computation, denoted as `h` in the doc below. + + + +### 3.1 Empty Node + +The node hash of an empty node is 0. + +### 3.2 Parent Node + +The parent node hash is computed as follows + +```go +parentNodeHash = h(leftChildHash, rightChildHash) +``` + +### 3.3 Leaf Node + +The node hash of a leaf node is computed as follows + +```go +leafNodeHash = h(h(1, nodeKey), valueHash) +``` + +The leaf node can hold two types of values: Ethereum accounts and storage key-value pairs. Next, we will describe how the node key and value hash are computed for each leaf node type. + +#### Ethereum Account Leaf Node +For an Ethereum Account Leaf Node, it consists of an Ethereum address and a state account struct. The secure key is derived from the Ethereum address. +``` +address[0:20] (20 bytes in big-endian) +valHi = address[0:16] +valLo = address[16:20] * 2^96 (padding 12 bytes of 0 at the end) +nodeKey = h(valHi, valLo) +``` + +A state account struct in the Scroll consists of the following fields (`Fr` indicates the finite field used in Poseidon hash and is a 254-bit value) + +- `Nonce`: u64 +- `Balance`: u256, but treated as Fr +- `StorageRoot`: Fr +- `KeccakCodeHash`: u256 +- `PoseidonCodeHash`: Fr +- `CodeSize`: u64 + +Before computing the value hash, the state account is first marshaled into a list of `u256` values. The marshaling scheme is + +``` +(The following scheme assumes the big-endian encoding) +[0:32] (bytes in big-endian) + [0:16] Reserved with all 0 + [16:24] CodeSize, uint64 in big-endian + [24:32] Nonce, uint64 in big-endian +[32:64] Balance +[64:96] StorageRoot +[96:128] KeccakCodeHash +[128:160] PoseidonCodehash +(total 160 bytes) +``` + +The marshal function also returns a `flag` value along with a vector of `u256` values. The `flag` is a bitmap that indicates whether a `u256` value CANNOT be treated as a field element (Fr). The `flag` value for state account is 8, shown below. + +``` ++--------------------+---------+------+----------+----------+ +| 0 | 1 | 2 | 3 | 4 | (index) ++--------------------+---------+------+----------+----------+ +| nonce||codesize||0 | balance | root | keccak | poseidon | (u256) ++--------------------+---------+------+----------+----------+ +| 0 | 0 | 0 | 1 | 0 | (flag bits) ++--------------------+---------+------+----------+----------+ +(LSB) (MSB) +``` + +The value hash is computed in two steps: +1. Convert the value that cannot be represented as a field element of the Poseidon hash to the field element. +2. Combine field elements in a binary tree structure till the tree root is treated as the value hash. + +In the first step, when the bit in the `flag` is 1 indicating the `u256` value that cannot be treated as a field element, we split the value into a high-128bit value and a low-128bit value, and then pass them to a Poseidon hash to derive a field element value, `h(valueHi, valueLo)`. + +Based on the definition, the value hash of the state account is computed as follows. + +``` +valueHash = +h( + h( + h(nonce||codesize||0, balance), + h( + storageRoot, + h(keccakCodeHash[0:16], keccakCodeHash[16:32]), // convert Keccak codehash to a field element + ), + ), + poseidonCodeHash, +) +``` + +#### Storage Leaf Node + +For a Storage Leaf Node, it is a key-value pair, which both are a `u256` value. The secure key of this leaf node is derived from the storage key. + +``` +storageKey[0:32] (32 bytes in big-endian) +valHi = storageKey[0:16] +valLo = storageKey[16:32] +nodeKey = h(valHi, valLo) +``` + +The storage value is a `u256` value. The `flag` for the storage value is 1, showed below. + +``` ++--------------+ +| 0 | (index) ++--------------+ +| storageValue | (u256) ++--------------+ +| 1 | (flag bits) ++--------------+ +``` + +The value hash is computed as follows + +```go +valueHash = h(storageValue[0:16], storageValue[16:32]) +``` + +## 4. Tree Operations + +### 4.1 Insertion + +
    +zkTrie Structure +
    Figure 2. Insert a new leaf node to zkTrie
    +
    + +When we insert a new leaf node to the existing zkTrie, there could be two cases illustrated in the Figure 2. + +1. When traversing the path of the node key, it reaches an empty node (Figure 2(b)). In this case, we just need to replace this empty node by this leaf node and backtrace the path to update the merkle hash of parent nodes till the root. +2. When traversing the path of the node key, it reaches another leaf node `b` (Figure 2(c)). In this case, we need to push down the existing leaf node `b` until the next bit in the node keys of two leaf nodes differs. At each push-down step, we need to insert an empty sibling node when necessary. When we reach the level where the bits differ, we then place two leaf nodes `b` and `c` as the left child and the right child depending on their bits. At last, we backtrace the path and update the merkle hash of all parent nodes. + +### 4.2 Deletion + +
    +zkTrie Structure +
    Figure 3. Delete a leaf node from the zkTrie
    +
    + + +The deletion of a leaf node is similar to the insertion. There are two cases illustrated in the Figure 3. + +1. The sibling node of to-be-deleted leaf node is a parent node (Figure 3(b)). In this case, we can just replace the node `a` by an empty node and update the node hash of its ancestors till the root node. +2. The node of to-be-deleted leaf node is a leaf node (Figure 3(c)). Similarly, we first replace the leaf node by an empty node and start to contract its sibling node upwards until its sibling node is not an empty node. For example, in Figure 3(c), we first replace the leaf node `b` by an empty node. During the contraction, since the sibling of node `c` now becomes an empty node, we move node `c` one level upward to replace its parent node. The new sibling of node `c`, node `e`, is still an empty node. So again we move node `c` upward. Now that the sibling of node `c` is node `a`, the deletion process is finished. + +Note that the sibling of a leaf node in a valid zkTrie cannot be an empty node. Otherwise, we should always prune the subtree and move the leaf node upwards. diff --git a/crates/scroll/trie/src/lib.rs b/crates/scroll/trie/src/lib.rs index ebf3f721948c..52109073d973 100644 --- a/crates/scroll/trie/src/lib.rs +++ b/crates/scroll/trie/src/lib.rs @@ -6,9 +6,11 @@ extern crate alloc; mod branch; mod hash_builder; -pub use hash_builder::HashBuilder; mod leaf; mod sub_tree; + +pub use hash_builder::HashBuilder; + use reth_scroll_primitives::poseidon::Fr; /// The hashing domain for leaf nodes. From f4280810d274694d0e294b175a86b664b21e9b6b Mon Sep 17 00:00:00 2001 From: frisitano Date: Sun, 8 Dec 2024 13:48:38 +0000 Subject: [PATCH 38/38] chore: fix clsoing bracket in Cargo.toml --- crates/scroll/state-commitment/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/scroll/state-commitment/Cargo.toml b/crates/scroll/state-commitment/Cargo.toml index 8c1a3cfd3ddc..cb90692ccaaa 100644 --- a/crates/scroll/state-commitment/Cargo.toml +++ b/crates/scroll/state-commitment/Cargo.toml @@ -73,7 +73,7 @@ test-utils = [ "reth-trie/test-utils", "reth-trie-common/test-utils", "reth-trie-db/test-utils" - ] +] metrics = ["reth-metrics", "dep:metrics"]