diff --git a/Cargo.lock b/Cargo.lock index c3ce1e44..4cc298e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ dependencies = [ "alloy-transport", "futures", "futures-util", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -249,7 +249,7 @@ dependencies = [ "alloy-sol-types", "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", "tracing", ] @@ -271,7 +271,7 @@ dependencies = [ "async-trait", "auto_impl", "futures-utils-wasm", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -346,7 +346,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", "tokio", "tracing", "url", @@ -373,9 +373,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -384,9 +384,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0f2d905ebd295e7effec65e5f6868d153936130ae718352771de3e7d03c75c" +checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", @@ -486,7 +486,7 @@ dependencies = [ "auto_impl", "elliptic-curve", "k256", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -502,7 +502,7 @@ dependencies = [ "async-trait", "k256", "rand", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -590,7 +590,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", "tokio", "tower 0.5.1", "tracing", @@ -658,7 +658,23 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "derive_more 1.0.0", - "nybbles", + "nybbles 0.2.1", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-trie" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6917c79e837aa7b77b7a6dae9f89cbe15313ac161c4d3cfaf8909ef21f3d22d8" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more 1.0.0", + "nybbles 0.3.4", "serde", "smallvec", "tracing", @@ -991,6 +1007,9 @@ name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +dependencies = [ + "serde", +] [[package]] name = "async-stream" @@ -1212,7 +1231,7 @@ dependencies = [ "maybe-async", "reqwest", "serde", - "thiserror", + "thiserror 1.0.64", "tokio", ] @@ -1354,7 +1373,7 @@ dependencies = [ "semver 1.0.22", "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -1887,7 +1906,7 @@ dependencies = [ "futures", "rand", "reqwest", - "thiserror", + "thiserror 1.0.64", "tokio", ] @@ -2720,6 +2739,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -3224,6 +3252,19 @@ dependencies = [ "smallvec", ] +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "objc" version = "0.2.7" @@ -3446,7 +3487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.64", "ucd-trie", ] @@ -3735,7 +3776,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 1.0.64", "tokio", "tracing", ] @@ -3752,7 +3793,7 @@ dependencies = [ "rustc-hash", "rustls", "slab", - "thiserror", + "thiserror 1.0.64", "tinyvec", "tracing", ] @@ -3883,7 +3924,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3987,7 +4028,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-primitives", - "alloy-trie", + "alloy-trie 0.6.0", "auto_impl", "derive_more 1.0.0", "once_cell", @@ -4008,7 +4049,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-primitives", - "alloy-trie", + "alloy-trie 0.6.0", "bytes", "modular-bitfield", "op-alloy-consensus", @@ -4165,7 +4206,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "derive_more 1.0.0", - "nybbles", + "nybbles 0.2.1", "reth-consensus", "reth-prune-types", "reth-storage-errors", @@ -4192,7 +4233,7 @@ source = "git+https://github.com/risc0/reth?branch=p1.1.0_zstd#760183fd601f61a3f dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -4213,7 +4254,7 @@ dependencies = [ "alloy-rlp", "enr", "serde_with", - "thiserror", + "thiserror 1.0.64", "url", ] @@ -4272,7 +4313,7 @@ dependencies = [ "reth-revm", "revm", "revm-primitives", - "thiserror", + "thiserror 1.0.64", "tracing", ] @@ -4348,7 +4389,7 @@ dependencies = [ "modular-bitfield", "reth-codecs", "serde", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -4452,11 +4493,11 @@ dependencies = [ "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-trie", + "alloy-trie 0.6.0", "bytes", "derive_more 1.0.0", "itertools 0.13.0", - "nybbles", + "nybbles 0.2.1", "reth-codecs", "reth-primitives-traits", "revm-primitives", @@ -4747,6 +4788,22 @@ dependencies = [ "rand_core", ] +[[package]] +name = "risc0-ethereum-trie" +version = "0.1.0" +source = "git+https://github.com/risc0/risc0-ethereum?branch=feat/trie#c39c05720ebfccff520ae380ee906395d8858669" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.7.8", + "arrayvec", + "bincode", + "itertools 0.14.0", + "rkyv", + "serde", + "thiserror 2.0.11", +] + [[package]] name = "risc0-groth16" version = "1.2.1" @@ -5612,7 +5669,16 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.64", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -5626,6 +5692,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "thiserror-impl-no-std" version = "2.0.2" @@ -5934,7 +6011,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 1.0.64", "utf-8", ] @@ -6464,7 +6541,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper", - "thiserror", + "thiserror 1.0.64", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6576,10 +6653,9 @@ dependencies = [ "reth-primitives", "reth-revm", "reth-storage-errors", + "risc0-ethereum-trie", "rkyv", "serde", - "thiserror", - "tiny-keccak", ] [[package]] @@ -6662,6 +6738,7 @@ dependencies = [ "anyhow", "async-trait", "flate2", + "itertools 0.14.0", "k256", "log", "pot", @@ -6716,11 +6793,11 @@ name = "zeth-testeth" version = "0.1.0" dependencies = [ "alloy", - "alloy-trie", + "alloy-trie 0.6.0", "anyhow", "env_logger", "log", - "nybbles", + "nybbles 0.2.1", "reth-chainspec", "reth-primitives", "reth-revm", @@ -6748,7 +6825,7 @@ dependencies = [ "flate2", "indexmap 2.6.0", "memchr", - "thiserror", + "thiserror 1.0.64", "zopfli", ] diff --git a/Cargo.toml b/Cargo.toml index 9297e047..cca486bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,11 @@ features = ["unstable"] version = "1.2.1" features = ["unstable"] +[workspace.dependencies.risc0-ethereum-trie] +git = "https://github.com/risc0/risc0-ethereum" +branch = "feat/trie" +features = ["orphan", "rkyv", "rlp_serialize", "serde"] + # External [workspace.dependencies] # Alloy @@ -81,6 +86,7 @@ zeth-preflight = { path = "crates/preflight" } zeth-preflight-ethereum = { path = "crates/preflight-ethereum" } zeth-preflight-optimism = { path = "crates/preflight-optimism" } + # Others anyhow = "1.0.89" async-trait = "0.1.83" @@ -89,6 +95,7 @@ bytemuck = "1.19.0" clap = { version = "4.0", features = ["derive"] } env_logger = "0.11.5" hashbrown = { version = "0.15.2", features = ["rayon"] } +itertools = "0.14" k256 = { version = "0.13.3", features = ["serde", "pem"] } log = "0.4.22" flate2 = "1.0.34" @@ -98,7 +105,5 @@ rkyv = { version = "0.8.9", features = ["hashbrown-0_15"] } serde = { version = "1.0.210", features = ["derive"] } serde_json = { version = "1.0.128", features = ["alloc"] } serde_with = "3.11.0" -thiserror = "1.0.64" -tiny-keccak = "2.0.2" tokio = { version = "1.41.0", features = ["full"] } tracing = { version = "0.1.40", features = ["log"] } diff --git a/bin/benchmark/src/main.rs b/bin/benchmark/src/main.rs index 104375e0..4f9763c0 100644 --- a/bin/benchmark/src/main.rs +++ b/bin/benchmark/src/main.rs @@ -12,13 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use alloy::primitives::U256; +use alloy::primitives::{keccak256, U256}; use alloy_chains::NamedChain; use clap::Parser; use std::process::Command; use tracing::{error, info}; use zeth::cli::ProveArgs; -use zeth_core::keccak::keccak; #[derive(clap::Parser, Debug, Clone)] #[command(name = "zeth-benchmark")] @@ -57,7 +56,7 @@ fn main() { let build_args = &cli.prove_args.run_args.build_args; let chain_id = build_args.chain.or(cli.chain_id).unwrap(); // generate sequence of starting block numbers to benchmark - let seed = keccak( + let seed = keccak256( [ (chain_id as u64).to_be_bytes(), build_args.block_number.to_be_bytes(), @@ -70,9 +69,9 @@ fn main() { let block_numbers = (0..cli.sample_count) .map(|i| { build_args.block_number - + U256::from_be_bytes(keccak( - [seed.as_slice(), i.to_be_bytes().as_slice()].concat(), - )) + + U256::from_be_bytes( + keccak256([seed.as_slice(), i.to_be_bytes().as_slice()].concat()).0, + ) .reduce_mod(U256::from(cli.sample_range)) .to::() }) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 89ac1141..36bc5ef5 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -13,8 +13,7 @@ k256.workspace = true pot.workspace = true rkyv.workspace = true serde.workspace = true -thiserror.workspace = true -tiny-keccak.workspace = true +risc0-ethereum-trie.workspace = true reth-chainspec.workspace = true reth-primitives.workspace = true diff --git a/crates/core/src/db/trie.rs b/crates/core/src/db/trie.rs index 6206be44..dd0bea1b 100644 --- a/crates/core/src/db/trie.rs +++ b/crates/core/src/db/trie.rs @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::keccak::keccak; use crate::map::NoMapHasher; use crate::mpt::MptNode; use crate::rescue::Recoverable; use crate::stateless::data::StorageEntry; use alloy_consensus::Account; use alloy_primitives::map::{AddressHashMap, B256HashMap, HashMap}; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{keccak256, Address, B256, U256}; use reth_primitives::revm_primitives::db::Database; use reth_primitives::revm_primitives::{AccountInfo, Bytecode}; use reth_revm::DatabaseRef; @@ -27,7 +26,7 @@ use reth_storage_errors::provider::ProviderError; #[derive(Default)] pub struct TrieDB { - pub accounts: MptNode, + pub accounts: MptNode, pub storage: AddressHashMap, pub contracts: B256HashMap, pub block_hashes: HashMap, @@ -45,8 +44,7 @@ impl DatabaseRef for TrieDB { fn basic_ref(&self, address: Address) -> Result, Self::Error> { Ok(self .accounts - .get_rlp::(&keccak(address)) - .unwrap() + .get_rlp(keccak256(address))? .map(|acc| AccountInfo { balance: acc.balance, nonce: acc.nonce, @@ -63,8 +61,7 @@ impl DatabaseRef for TrieDB { let entry = self.storage.get(&address).unwrap(); Ok(entry .storage_trie - .get_rlp(&keccak(index.to_be_bytes::<32>())) - .unwrap() + .get_rlp(keccak256(index.to_be_bytes::<32>()))? .unwrap_or_default()) } diff --git a/crates/core/src/keccak.rs b/crates/core/src/keccak.rs deleted file mode 100644 index 482f86c3..00000000 --- a/crates/core/src/keccak.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023, 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use tiny_keccak::{Hasher, Keccak}; - -/// Computes the Keccak-256 hash of the provided data. -/// -/// This function is a thin wrapper around the Keccak256 hashing algorithm -/// and is optimized for performance. -/// -/// # TODO -/// - Consider switching the return type to `B256` for consistency with other parts of the -/// codebase. -#[inline] -pub fn keccak(data: impl AsRef<[u8]>) -> [u8; 32] { - let mut hasher = Keccak::v256(); - hasher.update(data.as_ref()); - let mut output = [0; 32]; - hasher.finalize(&mut output); - output -} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index b9a8fd4f..111855aa 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -14,7 +14,6 @@ pub mod db; pub mod driver; -pub mod keccak; pub mod map; pub mod mpt; pub mod rescue; diff --git a/crates/core/src/mpt.rs b/crates/core/src/mpt.rs index 144024ba..6d72cc41 100644 --- a/crates/core/src/mpt.rs +++ b/crates/core/src/mpt.rs @@ -1,1379 +1,112 @@ -// Copyright 2023, 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -extern crate alloc; - -use alloc::boxed::Box; -use core::{ - cell::RefCell, - cmp, - fmt::{Debug, Write}, - iter, mem, -}; - -use alloy_primitives::bytes::Buf; -use alloy_primitives::map::HashMap; -use alloy_primitives::{b256, B256}; +use alloy_primitives::map::B256Set; +use alloy_primitives::B256; use alloy_rlp::{Decodable, Encodable}; -use anyhow::{bail, Context}; +use risc0_ethereum_trie::{orphan, CachedTrie}; use serde::{Deserialize, Serialize}; -use thiserror::Error as ThisError; - -use crate::keccak::keccak; - -/// Root hash of an empty trie. -pub const EMPTY_ROOT: B256 = - b256!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); +use std::borrow::Borrow; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; -/// Represents the root node of a sparse Merkle Patricia Trie. -/// -/// The "sparse" nature of this trie allows for truncation of certain unneeded parts, -/// representing them by their node hash. This design choice is particularly useful for -/// optimizing storage. However, operations targeting a truncated part will fail and -/// return an error. Another distinction of this implementation is that branches cannot -/// store values, aligning with the construction of MPTs in Ethereum. #[derive( - Clone, Debug, - Default, - PartialEq, - Eq, - Ord, - PartialOrd, - Serialize, - Deserialize, - rkyv::Archive, - rkyv::Serialize, - rkyv::Deserialize, -)] -#[rkyv(bytecheck( - bounds( - __C: rkyv::validation::ArchiveContext, - __C::Error: rkyv::rancor::Source, - ) -))] -#[rkyv(serialize_bounds( - __S: rkyv::ser::Writer + rkyv::ser::Allocator, - __S::Error: rkyv::rancor::Source, -))] -#[rkyv(deserialize_bounds( - __D::Error: rkyv::rancor::Source -))] -pub struct MptNode { - /// The type and data of the node. - #[rkyv(omit_bounds)] - pub data: MptNodeData, - /// Cache for a previously computed reference of this node. This is skipped during - /// serialization. - #[serde(skip)] - #[rkyv(with = rkyv::with::Skip)] - cached_reference: RefCell>, -} - -/// Represents custom error types for the sparse Merkle Patricia Trie (MPT). -/// -/// These errors cover various scenarios that can occur during trie operations, such as -/// encountering unresolved nodes, finding values in branches where they shouldn't be, and -/// issues related to RLP (Recursive Length Prefix) encoding and decoding. -#[derive(Debug, ThisError)] -pub enum Error { - /// Triggered when an operation reaches an unresolved node. The associated `B256` - /// value provides details about the unresolved node. - #[error("reached an unresolved node: {0:?}")] - NodeNotResolved(B256), - /// Occurs when a value is unexpectedly found in a branch node. - #[error("branch node with value")] - ValueInBranch, - /// Represents errors related to the RLP encoding and decoding using the `alloy_rlp` - /// library. - #[error("RLP error")] - Rlp(#[from] alloy_rlp::Error), -} - -impl From for MptNode { - fn from(digest: B256) -> Self { - match digest { - EMPTY_ROOT | B256::ZERO => MptNode::default(), - _ => MptNodeData::Digest(digest).into(), - } - } -} - -/// Represents the various types of data that can be stored within a node in the sparse -/// Merkle Patricia Trie (MPT). -/// -/// Each node in the trie can be of one of several types, each with its own specific data -/// structure. This enum provides a clear and type-safe way to represent the data -/// associated with each node type. -#[derive( Clone, - Debug, - Default, - PartialEq, Eq, - Ord, - PartialOrd, - Serialize, + PartialEq, Deserialize, + Serialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, )] -#[rkyv(bytecheck( - bounds( - __C: rkyv::validation::ArchiveContext, - ) -))] -#[rkyv(serialize_bounds( - __S: rkyv::ser::Writer + rkyv::ser::Allocator, - __S::Error: rkyv::rancor::Source, -))] -#[rkyv(deserialize_bounds( - __D::Error: rkyv::rancor::Source -))] -pub enum MptNodeData { - /// Represents an empty trie node. - #[default] - Null, - /// A node that can have up to 16 children. Each child is an optional boxed [MptNode]. - Branch( - // #[rkyv(with = rkyv::with::Map>>)] - #[rkyv(omit_bounds)] [Option>; 16], - ), - /// A leaf node that contains a key and a value, both represented as byte vectors. - Leaf(Vec, Vec), - /// A node that has exactly one child and is used to represent a shared prefix of - /// several keys. - Extension( - Vec, - // #[rkyv(with = Box)] - #[rkyv(omit_bounds)] Box, - ), - /// Represents a sub-trie by its hash, allowing for efficient storage of large - /// sub-tries without storing their entire content. - Digest(#[rkyv(with = B256Def)] B256), -} - -#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -#[rkyv(remote = B256)] -#[rkyv(archived = ArchivedB256)] -pub struct B256Def(pub [u8; 32]); - -impl From for B256 { - fn from(value: B256Def) -> Self { - B256::new(value.0) - } -} - -/// Represents the ways in which one node can reference another node inside the sparse -/// Merkle Patricia Trie (MPT). -/// -/// Nodes in the MPT can reference other nodes either directly through their byte -/// representation or indirectly through a hash of their encoding. This enum provides a -/// clear and type-safe way to represent these references. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)] -pub enum MptNodeReference { - /// Represents a direct reference to another node using its byte encoding. Typically - /// used for short encodings that are less than 32 bytes in length. - Bytes(Vec), - /// Represents an indirect reference to another node using the Keccak hash of its long - /// encoding. Used for encodings that are not less than 32 bytes in length. - Digest(B256), +pub struct MptNode { + inner: CachedTrie, + phantom_data: PhantomData, } -impl MptNodeReference { - pub fn as_digest(&self) -> Self { - match self { - MptNodeReference::Digest(d) => MptNodeReference::Digest(*d), - MptNodeReference::Bytes(b) => MptNodeReference::Digest(keccak(b).into()), - } - } - - pub fn digest(&self) -> B256 { - let MptNodeReference::Digest(d) = self.as_digest() else { - unreachable!() - }; - d - } -} - -/// Provides a conversion from [MptNodeData] to [MptNode]. -/// -/// This implementation allows for conversion from [MptNodeData] to [MptNode], -/// initializing the `data` field with the provided value and setting the -/// `cached_reference` field to `None`. -impl From for MptNode { - fn from(value: MptNodeData) -> Self { +impl Default for MptNode { + fn default() -> Self { Self { - data: value, - cached_reference: RefCell::new(None), - } - } -} - -/// Provides encoding functionalities for the `MptNode` type. -/// -/// This implementation allows for the serialization of an [MptNode] into its RLP-encoded -/// form. The encoding is done based on the type of node data ([MptNodeData]) it holds. -impl Encodable for MptNode { - /// Encodes the node into the provided `out` buffer. - /// - /// The encoding is done using the Recursive Length Prefix (RLP) encoding scheme. The - /// method handles different node data types and encodes them accordingly. - #[inline] - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - match &self.data { - MptNodeData::Null => { - out.put_u8(alloy_rlp::EMPTY_STRING_CODE); - } - MptNodeData::Branch(nodes) => { - alloy_rlp::Header { - list: true, - payload_length: self.payload_length(), - } - .encode(out); - nodes.iter().for_each(|child| match child { - Some(node) => node.reference_encode(out), - None => out.put_u8(alloy_rlp::EMPTY_STRING_CODE), - }); - // in the MPT reference, branches have values so always add empty value - out.put_u8(alloy_rlp::EMPTY_STRING_CODE); - } - MptNodeData::Leaf(prefix, value) => { - alloy_rlp::Header { - list: true, - payload_length: self.payload_length(), - } - .encode(out); - prefix.as_slice().encode(out); - value.as_slice().encode(out); - } - MptNodeData::Extension(prefix, node) => { - alloy_rlp::Header { - list: true, - payload_length: self.payload_length(), - } - .encode(out); - prefix.as_slice().encode(out); - node.reference_encode(out); - } - MptNodeData::Digest(digest) => { - digest.encode(out); - } + inner: CachedTrie::default(), + phantom_data: PhantomData, } } - - /// Returns the length of the encoded node in bytes. - /// - /// This method calculates the length of the RLP-encoded node. It's useful for - /// determining the size requirements for storage or transmission. - #[inline] - fn length(&self) -> usize { - let payload_length = self.payload_length(); - payload_length + alloy_rlp::length_of_length(payload_length) - } } -/// Provides decoding functionalities for the [MptNode] type. -/// -/// This implementation allows for the deserialization of an RLP-encoded [MptNode] back -/// into its original form. The decoding is done based on the prototype of the RLP data, -/// ensuring that the node is reconstructed accurately. -/// -impl Decodable for MptNode { - /// Decodes an RLP-encoded node from the provided `rlp` buffer. - /// - /// The method handles different RLP prototypes and reconstructs the `MptNode` based - /// on the encoded data. If the RLP data does not match any known prototype or if - /// there's an error during decoding, an error is returned. - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - if buf.is_empty() { - return Ok(MptNodeData::Null.into()); - } - match rlp_parse_head(buf)? { - (0, _) => Ok(MptNodeData::Null.into()), - (2, true) => { - let path = Vec::from(alloy_rlp::Header::decode_bytes(buf, false)?); - let prefix = path[0]; - if (prefix & (2 << 4)) == 0 { - let node = MptNode::decode(buf)?; - Ok(MptNodeData::Extension(path, Box::new(node)).into()) - } else { - let header = alloy_rlp::Header::decode(buf)?; - let value = Vec::from(&buf[..header.payload_length]); - buf.advance(header.payload_length); - Ok(MptNodeData::Leaf(path, value).into()) - } - } - (17, true) => { - let mut node_list = Vec::with_capacity(16); - for _ in 0..16 { - match *buf.first().ok_or(alloy_rlp::Error::InputTooShort)? { - alloy_rlp::EMPTY_STRING_CODE => { - buf.advance(1); - node_list.push(None); - } - _ => node_list.push(Some(Box::new(MptNode::decode(buf)?))), - } - } - let value: Vec = Vec::from(alloy_rlp::Header::decode_bytes(buf, false)?); - if value.is_empty() { - Ok(MptNodeData::Branch(node_list.try_into().unwrap()).into()) - } else { - Err(alloy_rlp::Error::Custom("branch node with value")) - } - } - (32, false) => { - if buf.length() < 32 { - Err(alloy_rlp::Error::InputTooShort) - } else { - let bytes: [u8; 32] = (&buf[0..32]).try_into().unwrap(); - buf.advance(32); - Ok(MptNodeData::Digest(B256::from(bytes)).into()) - } - } - _ => Err(alloy_rlp::Error::Custom("bad node encoding")), - } - } -} - -fn rlp_parse_head(buf: &mut &[u8]) -> alloy_rlp::Result<(usize, bool)> { - let head = alloy_rlp::Header::decode(buf)?; - let mut result = 0; - if head.list { - let mut buf = &buf[0..head.payload_length]; - while !buf.is_empty() { - let item_head = alloy_rlp::Header::decode(&mut buf)?; - buf.advance(item_head.payload_length); - result += 1; - } - } else { - result = head.payload_length; - } - Ok((result, head.list)) -} - -/// Represents a node in the sparse Merkle Patricia Trie (MPT). -/// -/// The [MptNode] type encapsulates the data and functionalities associated with a node in -/// the MPT. It provides methods for manipulating the trie, such as inserting, deleting, -/// and retrieving values, as well as utility methods for encoding, decoding, and -/// debugging. -impl MptNode { - /// Clears the trie, replacing its data with an empty node, [MptNodeData::Null]. - /// - /// This method effectively removes all key-value pairs from the trie. - #[inline] - pub fn clear(&mut self) { - self.data = MptNodeData::Null; - self.invalidate_ref_cache(); - } - - /// Retrieves the underlying data of the node. - /// - /// This method provides a reference to the node's data, allowing for inspection and - /// manipulation. - #[inline] - pub fn as_data(&self) -> &MptNodeData { - &self.data - } - - /// Retrieves the underlying data of the node. - /// - /// This method provides a reference to the node's data, allowing for inspection and - /// manipulation. - #[inline] - pub fn as_data_mut(&mut self) -> &mut MptNodeData { - &mut self.data - } - - #[inline] - pub fn is_reference_cached(&self) -> bool { - self.cached_reference.borrow().is_some() - } - - /// Retrieves the [MptNodeReference] reference of the node when it's referenced inside - /// another node. - /// - /// This method provides a way to obtain a compact representation of the node for - /// storage or transmission purposes. - #[inline] - pub fn reference(&self) -> MptNodeReference { - self.cached_reference - .borrow_mut() - .get_or_insert_with(|| self.calc_reference()) - .clone() - } - - /// Computes and returns the 256-bit hash of the node. - /// - /// This method provides a unique identifier for the node based on its content. - #[inline] - pub fn hash(&self) -> B256 { - match self.data { - MptNodeData::Null => EMPTY_ROOT, - _ => match self - .cached_reference - .borrow_mut() - .get_or_insert_with(|| self.calc_reference()) - { - MptNodeReference::Digest(digest) => *digest, - MptNodeReference::Bytes(bytes) => keccak(bytes).into(), - }, - } - } - - /// Encodes the [MptNodeReference] of this node into the `out` buffer. - fn reference_encode(&self, out: &mut dyn alloy_rlp::BufMut) { - match self - .cached_reference - .borrow_mut() - .get_or_insert_with(|| self.calc_reference()) - { - // if the reference is an RLP-encoded byte slice, copy it directly - MptNodeReference::Bytes(bytes) => out.put_slice(bytes), - // if the reference is a digest, RLP-encode it with its fixed known length - MptNodeReference::Digest(digest) => { - out.put_u8(alloy_rlp::EMPTY_STRING_CODE + 32); - out.put_slice(digest.as_slice()); - } - } - } - - /// Returns the length of the encoded [MptNodeReference] of this node. - fn reference_length(&self) -> usize { - match self - .cached_reference - .borrow_mut() - .get_or_insert_with(|| self.calc_reference()) - { - MptNodeReference::Bytes(bytes) => bytes.len(), - MptNodeReference::Digest(_) => 1 + 32, - } - } - - fn calc_reference(&self) -> MptNodeReference { - match &self.data { - MptNodeData::Null => MptNodeReference::Bytes(vec![alloy_rlp::EMPTY_STRING_CODE]), - MptNodeData::Digest(digest) => MptNodeReference::Digest(*digest), - _ => { - let encoded = alloy_rlp::encode(self); - if encoded.len() < 32 { - MptNodeReference::Bytes(encoded) - } else { - MptNodeReference::Digest(keccak(encoded).into()) - } - } - } - } - - /// Determines if the trie is empty. - /// - /// This method checks if the node represents an empty trie, i.e., it doesn't contain - /// any key-value pairs. - #[inline] - pub fn is_empty(&self) -> bool { - matches!(&self.data, MptNodeData::Null) - } - - /// Determines if the node represents a digest. - /// - /// A digest is a compact representation of a sub-trie, represented by its hash. - #[inline] - pub fn is_digest(&self) -> bool { - matches!(&self.data, MptNodeData::Digest(_)) - } - - /// Retrieves the nibbles corresponding to the node's prefix. - /// - /// Nibbles are half-bytes, and in the context of the MPT, they represent parts of - /// keys. - #[inline] - pub fn nibs(&self) -> Vec { - match &self.data { - MptNodeData::Null | MptNodeData::Branch(_) | MptNodeData::Digest(_) => vec![], - MptNodeData::Leaf(prefix, _) | MptNodeData::Extension(prefix, _) => prefix_nibs(prefix), - } - } - - /// Retrieves the value associated with a given key in the trie. - /// - /// If the key is not present in the trie, this method returns `None`. Otherwise, it - /// returns a reference to the associated value. If [None] is returned, the key is - /// provably not in the trie. - #[inline] - pub fn get(&self, key: &[u8]) -> Result, Error> { - self.get_internal(&to_nibs(key)) - } - - /// Retrieves the RLP-decoded value corresponding to the key. - /// - /// If the key is not present in the trie, this method returns `None`. Otherwise, it - /// returns the RLP-decoded value. - #[inline] - pub fn get_rlp(&self, key: &[u8]) -> Result, Error> { - match self.get(key)? { - Some(mut bytes) => Ok(Some(T::decode(&mut bytes)?)), +impl MptNode { + pub fn get_rlp(&self, key: impl AsRef<[u8]>) -> alloy_rlp::Result> { + match self.inner.get(key) { None => Ok(None), + Some(mut bytes) => Ok(Some(T::decode(&mut bytes)?)), } } - fn get_internal(&self, key_nibs: &[u8]) -> Result, Error> { - match &self.data { - MptNodeData::Null => Ok(None), - MptNodeData::Branch(nodes) => { - if let Some((i, tail)) = key_nibs.split_first() { - match nodes[*i as usize] { - Some(ref node) => node.get_internal(tail), - None => Ok(None), - } - } else { - Ok(None) - } - } - MptNodeData::Leaf(prefix, value) => { - if prefix_nibs(prefix) == key_nibs { - Ok(Some(value)) - } else { - Ok(None) - } - } - MptNodeData::Extension(prefix, node) => { - if let Some(tail) = key_nibs.strip_prefix(prefix_nibs(prefix).as_slice()) { - node.get_internal(tail) - } else { - Ok(None) - } - } - MptNodeData::Digest(digest) => Err(Error::NodeNotResolved(*digest)), - } - } - - /// Removes a key from the trie. - /// - /// This method attempts to remove a key-value pair from the trie. If the key is - /// present, it returns `true`. Otherwise, it returns `false`. - #[inline] - pub fn delete(&mut self, key: &[u8]) -> Result { - self.delete_internal(&to_nibs(key)) - } - - fn delete_internal(&mut self, key_nibs: &[u8]) -> Result { - match &mut self.data { - MptNodeData::Null => return Ok(false), - MptNodeData::Branch(children) => { - if let Some((i, tail)) = key_nibs.split_first() { - let child = &mut children[*i as usize]; - match child { - Some(node) => { - if !node.delete_internal(tail)? { - return Ok(false); - } - // if the node is now empty, remove it - if node.is_empty() { - *child = None; - } - } - None => return Ok(false), - } - } else { - return Err(Error::ValueInBranch); - } - - let mut remaining = children.iter_mut().enumerate().filter(|(_, n)| n.is_some()); - // there will always be at least one remaining node - let (index, node) = remaining.next().unwrap(); - // if there is only exactly one node left, we need to convert the branch - if remaining.next().is_none() { - let mut orphan = node.take().unwrap(); - match &mut orphan.data { - // if the orphan is a leaf, prepend the corresponding nib to it - MptNodeData::Leaf(prefix, orphan_value) => { - let new_nibs: Vec<_> = - iter::once(index as u8).chain(prefix_nibs(prefix)).collect(); - self.data = MptNodeData::Leaf( - to_encoded_path(&new_nibs, true), - mem::take(orphan_value), - ); - } - // if the orphan is an extension, prepend the corresponding nib to it - MptNodeData::Extension(prefix, orphan_child) => { - let new_nibs: Vec<_> = - iter::once(index as u8).chain(prefix_nibs(prefix)).collect(); - self.data = MptNodeData::Extension( - to_encoded_path(&new_nibs, false), - mem::take(orphan_child), - ); - } - // if the orphan is a branch, convert to an extension - MptNodeData::Branch(_) => { - self.data = MptNodeData::Extension( - to_encoded_path(&[index as u8], false), - orphan, - ); - } - MptNodeData::Digest(digest) => { - return Err(Error::NodeNotResolved(*digest)); - } - MptNodeData::Null => unreachable!(), - } - } - } - MptNodeData::Leaf(prefix, _) => { - if prefix_nibs(prefix) != key_nibs { - return Ok(false); - } - self.data = MptNodeData::Null; - } - MptNodeData::Extension(prefix, child) => { - let mut self_nibs = prefix_nibs(prefix); - if let Some(tail) = key_nibs.strip_prefix(self_nibs.as_slice()) { - if !child.delete_internal(tail)? { - return Ok(false); - } - } else { - return Ok(false); - } - - // an extension can only point to a branch or a digest; since it's sub trie was - // modified, we need to make sure that this property still holds - match &mut child.data { - // if the child is empty, remove the extension - MptNodeData::Null => { - self.data = MptNodeData::Null; - } - // for a leaf, replace the extension with the extended leaf - MptNodeData::Leaf(prefix, value) => { - self_nibs.extend(prefix_nibs(prefix)); - self.data = - MptNodeData::Leaf(to_encoded_path(&self_nibs, true), mem::take(value)); - } - // for an extension, replace the extension with the extended extension - MptNodeData::Extension(prefix, node) => { - self_nibs.extend(prefix_nibs(prefix)); - self.data = MptNodeData::Extension( - to_encoded_path(&self_nibs, false), - mem::take(node), - ); - } - // for a branch, the extension is still correct - MptNodeData::Branch(_) => {} - // if the child were a digest an early return should have been hit - MptNodeData::Digest(_) => unreachable!(), - } - } - MptNodeData::Digest(digest) => return Err(Error::NodeNotResolved(*digest)), + pub fn insert_rlp(&mut self, key: K, value: V) + where + K: AsRef<[u8]>, + V: Borrow, + { + self.inner.insert(key, alloy_rlp::encode(value.borrow())); + } + + /// Tries to resolve the potential removal orphan corresponding to `key` from the given + /// post-removal proof. If the orphan cannot be resolved from the proof alone, the `key` + /// corresponding to the unresolved path is added to `unresolvable`. + pub fn resolve_orphan( + &mut self, + key: K, + post_state_proof: impl IntoIterator, + unresolvable: &mut B256Set, + ) -> alloy_rlp::Result<()> + where + K: AsRef<[u8]>, + N: AsRef<[u8]>, + { + match self.inner.resolve_orphan(key, post_state_proof) { + Ok(_) => {} + Err(orphan::Error::Unresolvable(prefix)) => { + // convert the unresolvable prefix nibbles into a B256 key with zero padding + let key = B256::right_padding_from(&prefix.pack()); + unresolvable.insert(key); + } + Err(orphan::Error::RlpError(err)) => return Err(err), }; - self.invalidate_ref_cache(); - Ok(true) - } - - /// Inserts a key-value pair into the trie. - /// - /// This method attempts to insert a new key-value pair into the trie. If the - /// insertion is successful, it returns `true`. If the key already exists, it updates - /// the value and returns `false`. - #[inline] - pub fn insert(&mut self, key: &[u8], value: Vec) -> Result { - if value.is_empty() { - panic!("value must not be empty"); - } - self.insert_internal(&to_nibs(key), value) + Ok(()) } - /// Inserts an RLP-encoded value into the trie. - /// - /// This method inserts a value that's been encoded using RLP into the trie. #[inline] - pub fn insert_rlp(&mut self, key: &[u8], value: impl Encodable) -> Result { - self.insert_internal(&to_nibs(key), alloy_rlp::encode(value)) - } - - fn insert_internal(&mut self, key_nibs: &[u8], value: Vec) -> Result { - match &mut self.data { - MptNodeData::Null => { - self.data = MptNodeData::Leaf(to_encoded_path(key_nibs, true), value); - } - MptNodeData::Branch(children) => { - if let Some((i, tail)) = key_nibs.split_first() { - let child = &mut children[*i as usize]; - match child { - Some(node) => { - if !node.insert_internal(tail, value)? { - return Ok(false); - } - } - // if the corresponding child is empty, insert a new leaf - None => { - *child = Some(Box::new( - MptNodeData::Leaf(to_encoded_path(tail, true), value).into(), - )); - } - } - } else { - return Err(Error::ValueInBranch); - } + pub fn from_digest(digest: B256) -> Self { + if digest == B256::ZERO { + Self::default() + } else { + Self { + inner: CachedTrie::from_digest(digest), + phantom_data: PhantomData, } - MptNodeData::Leaf(prefix, old_value) => { - let self_nibs = prefix_nibs(prefix); - let common_len = lcp(&self_nibs, key_nibs); - if common_len == self_nibs.len() && common_len == key_nibs.len() { - // if self_nibs == key_nibs, update the value if it is different - if old_value == &value { - return Ok(false); - } - *old_value = value; - } else if common_len == self_nibs.len() || common_len == key_nibs.len() { - return Err(Error::ValueInBranch); - } else { - let split_point = common_len + 1; - // otherwise, create a branch with two children - let mut children: [Option>; 16] = Default::default(); - - children[self_nibs[common_len] as usize] = Some(Box::new( - MptNodeData::Leaf( - to_encoded_path(&self_nibs[split_point..], true), - mem::take(old_value), - ) - .into(), - )); - children[key_nibs[common_len] as usize] = Some(Box::new( - MptNodeData::Leaf(to_encoded_path(&key_nibs[split_point..], true), value) - .into(), - )); - - let branch = MptNodeData::Branch(children); - if common_len > 0 { - // create parent extension for new branch - self.data = MptNodeData::Extension( - to_encoded_path(&self_nibs[..common_len], false), - Box::new(branch.into()), - ); - } else { - self.data = branch; - } - } - } - MptNodeData::Extension(prefix, existing_child) => { - let self_nibs = prefix_nibs(prefix); - let common_len = lcp(&self_nibs, key_nibs); - if common_len == self_nibs.len() { - // traverse down for update - if !existing_child.insert_internal(&key_nibs[common_len..], value)? { - return Ok(false); - } - } else if common_len == key_nibs.len() { - return Err(Error::ValueInBranch); - } else { - let split_point = common_len + 1; - // otherwise, create a branch with two children - let mut children: [Option>; 16] = Default::default(); - - children[self_nibs[common_len] as usize] = if split_point < self_nibs.len() { - Some(Box::new( - MptNodeData::Extension( - to_encoded_path(&self_nibs[split_point..], false), - mem::take(existing_child), - ) - .into(), - )) - } else { - Some(mem::take(existing_child)) - }; - children[key_nibs[common_len] as usize] = Some(Box::new( - MptNodeData::Leaf(to_encoded_path(&key_nibs[split_point..], true), value) - .into(), - )); - - let branch = MptNodeData::Branch(children); - if common_len > 0 { - // Create parent extension for new branch - self.data = MptNodeData::Extension( - to_encoded_path(&self_nibs[..common_len], false), - Box::new(branch.into()), - ); - } else { - self.data = branch; - } - } - } - MptNodeData::Digest(digest) => return Err(Error::NodeNotResolved(*digest)), - }; - - self.invalidate_ref_cache(); - Ok(true) - } - - fn invalidate_ref_cache(&mut self) { - self.cached_reference.borrow_mut().take(); - } - - /// Returns the number of traversable nodes in the trie. - /// - /// This method provides a count of all the nodes that can be traversed within the - /// trie. - pub fn size(&self) -> usize { - match self.as_data() { - MptNodeData::Null => 0, - MptNodeData::Branch(children) => { - children.iter().flatten().map(|n| n.size()).sum::() + 1 - } - MptNodeData::Leaf(_, _) => 1, - MptNodeData::Extension(_, child) => child.size() + 1, - MptNodeData::Digest(_) => 0, - } - } - - /// Formats the trie as a string list, where each line corresponds to a trie leaf. - /// - /// This method is primarily used for debugging purposes, providing a visual - /// representation of the trie's structure. - pub fn debug_rlp(&self) -> Vec { - // convert the nibs to hex - let nibs: String = self.nibs().iter().fold(String::new(), |mut output, n| { - let _ = write!(output, "{:x}", n); - output - }); - - match self.as_data() { - MptNodeData::Null => vec![format!("{:?}", MptNodeData::Null)], - MptNodeData::Branch(children) => children - .iter() - .enumerate() - .flat_map(|(i, child)| { - match child { - Some(node) => node.debug_rlp::(), - None => vec!["None".to_string()], - } - .into_iter() - .map(move |s| format!("{:x} {}", i, s)) - }) - .collect(), - MptNodeData::Leaf(_, data) => { - vec![format!( - "{} -> {:?}", - nibs, - T::decode(&mut &data[..]).unwrap() - )] - } - MptNodeData::Extension(_, node) => node - .debug_rlp::() - .into_iter() - .map(|s| format!("{} {}", nibs, s)) - .collect(), - MptNodeData::Digest(digest) => vec![format!("#{:#}", digest)], - } - } - - /// Returns the length of the RLP payload of the node. - fn payload_length(&self) -> usize { - match &self.data { - MptNodeData::Null => 0, - MptNodeData::Branch(nodes) => { - 1 + nodes - .iter() - .map(|child| child.as_ref().map_or(1, |node| node.reference_length())) - .sum::() - } - MptNodeData::Leaf(prefix, value) => { - prefix.as_slice().length() + value.as_slice().length() - } - MptNodeData::Extension(prefix, node) => { - prefix.as_slice().length() + node.reference_length() - } - MptNodeData::Digest(_) => 32, } } -} - -/// Converts a byte slice into a vector of nibbles. -/// -/// A nibble is 4 bits or half of an 8-bit byte. This function takes each byte from the -/// input slice, splits it into two nibbles, and appends them to the resulting vector. -pub fn to_nibs(slice: &[u8]) -> Vec { - let mut result = Vec::with_capacity(2 * slice.len()); - for byte in slice { - result.push(byte >> 4); - result.push(byte & 0xf); - } - result -} - -/// Encodes a slice of nibbles into a vector of bytes, with an additional prefix to -/// indicate the type of node (leaf or extension). -/// -/// The function starts by determining the type of node based on the `is_leaf` parameter. -/// If the node is a leaf, the prefix is set to `0x20`. If the length of the nibbles is -/// odd, the prefix is adjusted and the first nibble is incorporated into it. -/// -/// The remaining nibbles are then combined into bytes, with each pair of nibbles forming -/// a single byte. The resulting vector starts with the prefix, followed by the encoded -/// bytes. -pub fn to_encoded_path(mut nibs: &[u8], is_leaf: bool) -> Vec { - let mut prefix = (is_leaf as u8) * 0x20; - if nibs.len() % 2 != 0 { - prefix += 0x10 + nibs[0]; - nibs = &nibs[1..]; - } - iter::once(prefix) - .chain(nibs.chunks_exact(2).map(|byte| (byte[0] << 4) + byte[1])) - .collect() -} - -/// Returns the length of the common prefix. -fn lcp(a: &[u8], b: &[u8]) -> usize { - for (i, (a, b)) in iter::zip(a, b).enumerate() { - if a != b { - return i; - } - } - cmp::min(a.len(), b.len()) -} - -pub fn prefix_nibs(prefix: &[u8]) -> Vec { - let (extension, tail) = prefix.split_first().unwrap(); - // the first bit of the first nibble denotes the parity - let is_odd = extension & (1 << 4) != 0; - let mut result = Vec::with_capacity(2 * tail.len() + is_odd as usize); - // for odd lengths, the second nibble contains the first element - if is_odd { - result.push(extension & 0xf); - } - for nib in tail { - result.push(nib >> 4); - result.push(nib & 0xf); - } - result -} - -/// Parses proof bytes into a vector of MPT nodes. -pub fn parse_proof(proof: &[impl AsRef<[u8]>]) -> anyhow::Result> { - proof - .iter() - // .filter(|proof| !proof.as_ref().is_empty()) // this is a sign of a malformed proof - .map(|buf| MptNode::decode(&mut buf.as_ref())) - .collect::, _>>() - .context("parse_proof") -} - -/// Creates a Merkle Patricia trie from an EIP-1186 proof. -pub fn mpt_from_proof(proof_nodes: &[MptNode]) -> anyhow::Result { - let mut next: Option = None; - for (i, node) in proof_nodes.iter().enumerate().rev() { - // there is nothing to replace for the last node - let Some(replacement) = next else { - next = Some(node.clone()); - continue; - }; - - // find the child that references the next node - let replacement_digest = replacement.reference().digest(); - let resolved: MptNode = match node.as_data().clone() { - MptNodeData::Branch(mut children) => { - children.iter_mut().flatten().for_each(|c| { - if c.reference().digest() == replacement_digest { - *c = Box::new(replacement.clone()); - } - }); - if children.iter_mut().all(|child| match child { - None => !replacement.is_empty(), - Some(node) => node.reference().digest() != replacement_digest, - }) { - bail!("branch node {} does not reference the successor", i); - } - MptNodeData::Branch(children).into() - } - MptNodeData::Extension(prefix, child) => { - if child.reference().digest() != replacement_digest { - bail!("extension node {} does not reference the successor", i); - } - MptNodeData::Extension(prefix, Box::new(replacement)).into() - } - MptNodeData::Null | MptNodeData::Leaf(_, _) | MptNodeData::Digest(_) => { - bail!("node {} has no children to replace", i); - } - }; - - next = Some(resolved); + #[inline] + pub fn from_rlp>(nodes: impl IntoIterator) -> alloy_rlp::Result { + Ok(Self { + inner: CachedTrie::from_rlp(nodes)?, + phantom_data: PhantomData, + }) } - - // the last node in the proof should be the root - Ok(next.unwrap_or_default()) } -/// Verifies that the given proof is a valid proof of exclusion for the given key. -pub fn is_not_included(key: &[u8], proof_nodes: &[MptNode]) -> anyhow::Result { - let proof_trie = mpt_from_proof(proof_nodes).context("invalid trie")?; - // for valid proofs, the get must not fail - let value = proof_trie.get(key).context("invalid trie")?; - - Ok(value.is_none()) -} +impl Deref for MptNode { + type Target = CachedTrie; -/// Creates a new MPT trie where all the digests contained in `node_store` are resolved. -pub fn resolve_nodes(root: &MptNode, node_store: &HashMap) -> MptNode { - let trie = match root.as_data() { - MptNodeData::Null | MptNodeData::Leaf(_, _) => root.clone(), - MptNodeData::Branch(children) => { - let children: Vec<_> = children - .iter() - .map(|child| { - child - .as_ref() - .map(|node| Box::new(resolve_nodes(node, node_store))) - }) - .collect(); - MptNodeData::Branch(children.try_into().unwrap()).into() - } - MptNodeData::Extension(prefix, target) => { - MptNodeData::Extension(prefix.clone(), Box::new(resolve_nodes(target, node_store))) - .into() - } - MptNodeData::Digest(digest) => { - if let Some(node) = node_store.get(&MptNodeReference::Digest(*digest)) { - resolve_nodes(node, node_store) - } else { - root.clone() - } - } - }; - // the root hash must not change - debug_assert_eq!(root.hash(), trie.hash()); - - trie -} - -/// Creates a new MPT trie where all the digests contained in `node_store` are resolved. -pub fn resolve_nodes_in_place(root: &mut MptNode, node_store: &HashMap) { - let starting_hash = root.hash(); - let replacement = match root.as_data_mut() { - MptNodeData::Null | MptNodeData::Leaf(_, _) => None, - MptNodeData::Branch(children) => { - for child in children.iter_mut().flatten() { - resolve_nodes_in_place(child, node_store); - } - None - } - MptNodeData::Extension(_, target) => { - resolve_nodes_in_place(target, node_store); - None - } - MptNodeData::Digest(digest) => node_store.get(&MptNodeReference::Digest(*digest)), - }; - if let Some(data) = replacement { - root.data = data.data.clone(); - root.invalidate_ref_cache(); - resolve_nodes_in_place(root, node_store); + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner } - // the root hash must not change - debug_assert_eq!(root.hash(), starting_hash); -} - -/// Returns a list of all possible nodes that can be created by shortening the path of the -/// given node. -/// When nodes in an MPT are deleted, leaves or extensions may be extended. To still be -/// able to identify the original nodes, we create all shortened versions of the node. -pub fn shorten_node_path(node: &MptNode) -> Vec { - let mut res = Vec::new(); - let nibs = node.nibs(); - match node.as_data() { - MptNodeData::Null | MptNodeData::Branch(_) => {} - MptNodeData::Leaf(_, value) => { - for i in 0..=nibs.len() { - res.push(MptNodeData::Leaf(to_encoded_path(&nibs[i..], true), value.clone()).into()) - } - } - MptNodeData::Extension(_, child) => { - for i in 0..=nibs.len() { - res.push( - MptNodeData::Extension(to_encoded_path(&nibs[i..], false), child.clone()) - .into(), - ) - } - } - MptNodeData::Digest(_) => unreachable!(), - }; - res } -#[cfg(test)] -mod tests { - use alloy_primitives::hex; - - use super::*; - - #[test] - pub fn test_trie_pointer_no_keccak() { - let cases = [ - ("do", "verb"), - ("dog", "puppy"), - ("doge", "coin"), - ("horse", "stallion"), - ]; - for (k, v) in cases { - let node: MptNode = - MptNodeData::Leaf(k.as_bytes().to_vec(), v.as_bytes().to_vec()).into(); - assert!( - matches!(node.reference(),MptNodeReference::Bytes(bytes) if bytes == alloy_rlp::encode(&node)) - ); - } - } - - #[test] - pub fn test_to_encoded_path() { - // extension node with an even path length - let nibbles = vec![0x0a, 0x0b, 0x0c, 0x0d]; - assert_eq!(to_encoded_path(&nibbles, false), vec![0x00, 0xab, 0xcd]); - // extension node with an odd path length - let nibbles = vec![0x0a, 0x0b, 0x0c]; - assert_eq!(to_encoded_path(&nibbles, false), vec![0x1a, 0xbc]); - // leaf node with an even path length - let nibbles = vec![0x0a, 0x0b, 0x0c, 0x0d]; - assert_eq!(to_encoded_path(&nibbles, true), vec![0x20, 0xab, 0xcd]); - // leaf node with an odd path length - let nibbles = vec![0x0a, 0x0b, 0x0c]; - assert_eq!(to_encoded_path(&nibbles, true), vec![0x3a, 0xbc]); - } - - #[test] - pub fn test_lcp() { - let cases = [ - (vec![], vec![], 0), - (vec![0xa], vec![0xa], 1), - (vec![0xa, 0xb], vec![0xa, 0xc], 1), - (vec![0xa, 0xb], vec![0xa, 0xb], 2), - (vec![0xa, 0xb], vec![0xa, 0xb, 0xc], 2), - (vec![0xa, 0xb, 0xc], vec![0xa, 0xb, 0xc], 3), - (vec![0xa, 0xb, 0xc], vec![0xa, 0xb, 0xc, 0xd], 3), - (vec![0xa, 0xb, 0xc, 0xd], vec![0xa, 0xb, 0xc, 0xd], 4), - ]; - for (a, b, cpl) in cases { - assert_eq!(lcp(&a, &b), cpl) - } - } - - #[test] - pub fn test_empty() { - let trie = MptNode::default(); - - assert!(trie.is_empty()); - assert_eq!(trie.reference(), MptNodeReference::Bytes(vec![0x80])); - let expected = hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); - assert_eq!(expected, trie.hash().0); - - // test RLP encoding - let mut out = Vec::new(); - trie.encode(&mut out); - assert_eq!(out, vec![0x80]); - assert_eq!(trie.length(), out.len()); - let decoded = MptNode::decode(&mut out.as_slice()).unwrap(); - assert_eq!(trie.hash(), decoded.hash()); - } - - #[test] - pub fn test_empty_key() { - let mut trie = MptNode::default(); - - trie.insert(&[], b"empty".to_vec()).unwrap(); - assert_eq!(trie.get(&[]).unwrap(), Some(b"empty".as_ref())); - assert!(trie.delete(&[]).unwrap()); - } - - #[test] - pub fn test_clear() { - let mut trie = MptNode::default(); - trie.insert(b"dog", b"puppy".to_vec()).unwrap(); - assert!(!trie.is_empty()); - assert_ne!(trie.hash(), EMPTY_ROOT); - - trie.clear(); - assert!(trie.is_empty()); - assert_eq!(trie.hash(), EMPTY_ROOT); - } - - #[test] - pub fn test_tiny() { - // trie consisting of an extension, a branch and two leafs - let mut trie = MptNode::default(); - trie.insert_rlp(b"a", 0u8).unwrap(); - trie.insert_rlp(b"b", 1u8).unwrap(); - - assert!(!trie.is_empty()); - let exp_rlp = hex!("d816d680c3208180c220018080808080808080808080808080"); - assert_eq!(trie.reference(), MptNodeReference::Bytes(exp_rlp.to_vec())); - let exp_hash = hex!("6fbf23d6ec055dd143ff50d558559770005ff44ae1d41276f1bd83affab6dd3b"); - assert_eq!(trie.hash().0, exp_hash); - - // test RLP encoding - let mut out = Vec::new(); - trie.encode(&mut out); - assert_eq!(out, exp_rlp.to_vec()); - assert_eq!(trie.length(), out.len()); - let decoded = MptNode::decode(&mut out.as_slice()).unwrap(); - assert_eq!(trie.hash(), decoded.hash()); - } - - #[test] - pub fn test_partial() { - let mut trie = MptNode::default(); - trie.insert_rlp(b"aa", 0u8).unwrap(); - trie.insert_rlp(b"ab", 1u8).unwrap(); - trie.insert_rlp(b"ba", 2u8).unwrap(); - - let exp_hash = trie.hash(); - - // replace one node with its digest - let MptNodeData::Extension(_, node) = &mut trie.data else { - panic!("extension expected") - }; - **node = MptNodeData::Digest(node.hash()).into(); - assert!(node.is_digest()); - - let out = alloy_rlp::encode(&trie); - let trie = MptNode::decode(&mut out.as_slice()).unwrap(); - assert_eq!(trie.hash(), exp_hash); - - // lookups should fail - trie.get(b"aa").unwrap_err(); - trie.get(b"a0").unwrap_err(); - } - - #[test] - pub fn test_branch_value() { - let mut trie = MptNode::default(); - trie.insert(b"do", b"verb".to_vec()).unwrap(); - // leads to a branch with value which is not supported - trie.insert(b"dog", b"puppy".to_vec()).unwrap_err(); - } - - #[test] - pub fn test_insert() { - let mut trie = MptNode::default(); - let vals = vec![ - ("painting", "place"), - ("guest", "ship"), - ("mud", "leave"), - ("paper", "call"), - ("gate", "boast"), - ("tongue", "gain"), - ("baseball", "wait"), - ("tale", "lie"), - ("mood", "cope"), - ("menu", "fear"), - ]; - for (key, val) in &vals { - assert!(trie - .insert(key.as_bytes(), val.as_bytes().to_vec()) - .unwrap()); - } - - let expected = hex!("2bab6cdf91a23ebf3af683728ea02403a98346f99ed668eec572d55c70a4b08f"); - assert_eq!(expected, trie.hash().0); - - for (key, value) in &vals { - assert_eq!(trie.get(key.as_bytes()).unwrap(), Some(value.as_bytes())); - } - - // check inserting duplicate keys - assert!(trie.insert(vals[0].0.as_bytes(), b"new".to_vec()).unwrap()); - assert!(!trie.insert(vals[0].0.as_bytes(), b"new".to_vec()).unwrap()); - - // try RLP roundtrip - let out = alloy_rlp::encode(&trie); - let decoded = MptNode::decode(&mut out.as_slice()).unwrap(); - assert_eq!(trie.hash(), decoded.hash()); - } - - #[test] - pub fn test_keccak_trie() { - const N: usize = 512; - - // insert - let mut trie = MptNode::default(); - for i in 0..N { - assert!(trie.insert_rlp(&keccak(i.to_be_bytes()), i).unwrap()); - - // check hash against trie build in reverse - let mut reference = MptNode::default(); - for j in (0..=i).rev() { - reference.insert_rlp(&keccak(j.to_be_bytes()), j).unwrap(); - } - assert_eq!(trie.hash(), reference.hash()); - } - - let expected = hex!("7310027edebdd1f7c950a7fb3413d551e85dff150d45aca4198c2f6315f9b4a7"); - assert_eq!(trie.hash().0, expected); - - // get - for i in 0..N { - assert_eq!(trie.get_rlp(&keccak(i.to_be_bytes())).unwrap(), Some(i)); - assert!(trie.get(&keccak((i + N).to_be_bytes())).unwrap().is_none()); - } - - // delete - for i in 0..N { - assert!(trie.delete(&keccak(i.to_be_bytes())).unwrap()); - - let mut reference = MptNode::default(); - for j in ((i + 1)..N).rev() { - reference.insert_rlp(&keccak(j.to_be_bytes()), j).unwrap(); - } - assert_eq!(trie.hash(), reference.hash()); - } - assert!(trie.is_empty()); - } - - #[test] - pub fn test_index_trie() { - const N: usize = 512; - - // insert - let mut trie = MptNode::default(); - for i in 0..N { - assert!(trie.insert_rlp(&alloy_rlp::encode(i), i).unwrap()); - - // check hash against trie build in reverse - let mut reference = MptNode::default(); - for j in (0..=i).rev() { - reference.insert_rlp(&alloy_rlp::encode(j), j).unwrap(); - } - assert_eq!(trie.hash(), reference.hash()); - - // try RLP roundtrip - let out = alloy_rlp::encode(&trie); - let decoded = MptNode::decode(&mut out.as_slice()).unwrap(); - assert_eq!(trie.hash(), decoded.hash()); - } - - // get - for i in 0..N { - assert_eq!(trie.get_rlp(&alloy_rlp::encode(i)).unwrap(), Some(i)); - assert!(trie.get(&alloy_rlp::encode(i + N)).unwrap().is_none()); - } - - // delete - for i in 0..N { - assert!(trie.delete(&alloy_rlp::encode(i)).unwrap()); - - let mut reference = MptNode::default(); - for j in ((i + 1)..N).rev() { - reference.insert_rlp(&alloy_rlp::encode(j), j).unwrap(); - } - assert_eq!(trie.hash(), reference.hash()); - } - assert!(trie.is_empty()); +impl DerefMut for MptNode { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner } } diff --git a/crates/core/src/stateless/data.rs b/crates/core/src/stateless/data.rs index ed178126..b8d8e27a 100644 --- a/crates/core/src/stateless/data.rs +++ b/crates/core/src/stateless/data.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::mpt::MptNode; +use alloy_consensus::Account; use alloy_primitives::map::AddressHashMap; use alloy_primitives::{Address, Bytes, U256}; use k256::ecdsa::VerifyingKey; @@ -40,7 +41,7 @@ use serde::{Deserialize, Serialize}; rkyv::Deserialize, )] pub struct StorageEntry { - pub storage_trie: MptNode, + pub storage_trie: MptNode, #[rkyv(with = rkyv::with::Map)] pub slots: Vec, } @@ -90,7 +91,7 @@ pub struct _StatelessClientData { /// List of public keys for transaction signatures pub signers: Vec>, /// State trie of the parent block. - pub state_trie: MptNode, + pub state_trie: MptNode, /// Maps each address with its storage trie and the used storage slots. pub storage_tries: AddressHashMap, /// The code for each account @@ -126,7 +127,7 @@ pub struct StatelessClientData { #[rkyv(with = rkyv::with::Map>)] pub signers: Vec>, /// State trie of the parent block. - pub state_trie: MptNode, + pub state_trie: MptNode, /// Maps each address with its storage trie and the used storage slots. #[rkyv(with = rkyv::with::MapKV)] pub storage_tries: AddressHashMap, @@ -163,7 +164,7 @@ pub struct RkyvStatelessClientData { #[rkyv(with = rkyv::with::Map>)] pub signers: Vec>, /// State trie of the parent block. - pub state_trie: MptNode, + pub state_trie: MptNode, /// Maps each address with its storage trie and the used storage slots. #[rkyv(with = rkyv::with::MapKV)] pub storage_tries: AddressHashMap, diff --git a/crates/core/src/stateless/finalize.rs b/crates/core/src/stateless/finalize.rs index 5b662601..a8009959 100644 --- a/crates/core/src/stateless/finalize.rs +++ b/crates/core/src/stateless/finalize.rs @@ -16,12 +16,11 @@ use crate::db::memory::MemoryDB; use crate::db::trie::TrieDB; use crate::db::update::{into_plain_state, Update}; use crate::driver::CoreDriver; -use crate::keccak::keccak; use crate::mpt::MptNode; use crate::stateless::data::StorageEntry; use alloy_consensus::Account; use alloy_primitives::map::AddressHashMap; -use alloy_primitives::U256; +use alloy_primitives::{keccak256, U256}; use anyhow::{bail, Context}; use reth_revm::db::states::StateChangeset; use reth_revm::db::BundleState; @@ -29,7 +28,7 @@ use reth_revm::db::BundleState; pub trait FinalizationStrategy { fn finalize_state( block: &mut Driver::Block, - state_trie: &mut MptNode, + state_trie: &mut MptNode, storage_tries: &mut AddressHashMap, parent_header: &mut Driver::Header, db: Option<&mut Database>, @@ -43,7 +42,7 @@ pub struct TrieDbFinalizationStrategy; impl FinalizationStrategy for TrieDbFinalizationStrategy { fn finalize_state( block: &mut Driver::Block, - _state_trie: &mut MptNode, + _state_trie: &mut MptNode, _storage_tries: &mut AddressHashMap, parent_header: &mut Driver::Header, db: Option<&mut TrieDB>, @@ -88,7 +87,7 @@ pub struct MemoryDbFinalizationStrategy; impl FinalizationStrategy for MemoryDbFinalizationStrategy { fn finalize_state( block: &mut Driver::Block, - state_trie: &mut MptNode, + state_trie: &mut MptNode, storage_tries: &mut AddressHashMap, parent_header: &mut Driver::Header, db: Option<&mut MemoryDB>, @@ -123,32 +122,28 @@ impl FinalizationStrategy for MemoryDbFina // apply all new storage entries for the current account (address) let mut deletions = Vec::with_capacity(storage_change.storage.len()); for (key, value) in &storage_change.storage { - let storage_trie_index = keccak(key.to_be_bytes::<32>()); + let storage_trie_index = keccak256(key.to_be_bytes::<32>()); if value.is_zero() { deletions.push(storage_trie_index); } else { - storage_trie - .insert_rlp(&storage_trie_index, value) - .context("storage_trie.insert_rlp")?; + storage_trie.insert_rlp(storage_trie_index, value); } } // Apply deferred storage trie deletions for storage_trie_index in deletions { - storage_trie - .delete(&storage_trie_index) - .context("storage_trie.delete")?; + storage_trie.remove(storage_trie_index); } } // Apply account info + storage changes let mut deletions = Vec::with_capacity(accounts.len()); for (address, account_info) in accounts { - let state_trie_index = keccak(address); + let state_trie_index = keccak256(address); if account_info.is_none() { deletions.push(state_trie_index); continue; } let storage_root = { - let StorageEntry { storage_trie, .. } = storage_tries.get(address).unwrap(); + let StorageEntry { storage_trie, .. } = storage_tries.get_mut(address).unwrap(); storage_trie.hash() }; @@ -159,32 +154,26 @@ impl FinalizationStrategy for MemoryDbFina storage_root, code_hash: info.code_hash, }; - state_trie - .insert_rlp(&state_trie_index, state_account) - .context("state_trie.insert_rlp")?; + state_trie.insert_rlp(state_trie_index, state_account); } // Apply deferred state trie deletions for state_trie_index in deletions { - state_trie - .delete(&state_trie_index) - .context("state_trie.delete")?; + state_trie.remove(state_trie_index); } // Apply account storage only changes for (address, StorageEntry { storage_trie, .. }) in storage_tries { - if storage_trie.is_reference_cached() { + if storage_trie.is_cached() { continue; } - let state_trie_index = keccak(address); + let state_trie_index = keccak256(address); let mut state_account = state_trie - .get_rlp::(&state_trie_index) + .get_rlp(state_trie_index) .context("state_trie.get_rlp")? .unwrap_or_default(); let new_storage_root = storage_trie.hash(); if state_account.storage_root != new_storage_root { state_account.storage_root = storage_trie.hash(); - state_trie - .insert_rlp(&state_trie_index, state_account) - .context("state_trie.insert_rlp (2)")?; + state_trie.insert_rlp(state_trie_index, state_account); } } diff --git a/crates/core/src/stateless/initialize.rs b/crates/core/src/stateless/initialize.rs index 6f9dbdee..0dd2da34 100644 --- a/crates/core/src/stateless/initialize.rs +++ b/crates/core/src/stateless/initialize.rs @@ -15,14 +15,13 @@ use crate::db::memory::MemoryDB; use crate::db::trie::TrieDB; use crate::driver::CoreDriver; -use crate::keccak::keccak; use crate::map::NoMapHasher; use crate::mpt::MptNode; use crate::stateless::data::StorageEntry; use alloy_consensus::constants::EMPTY_ROOT_HASH; use alloy_consensus::Account; use alloy_primitives::map::{AddressHashMap, HashMap}; -use alloy_primitives::{Bytes, B256, U256}; +use alloy_primitives::{keccak256, Bytes, B256, U256}; use anyhow::{bail, ensure}; use core::mem::take; use reth_primitives::revm_primitives::Bytecode; @@ -32,7 +31,7 @@ use std::default::Default; pub trait InitializationStrategy { fn initialize_database( - state_trie: &mut MptNode, + state_trie: &mut MptNode, storage_tries: &mut AddressHashMap, contracts: &mut Vec, parent_header: &mut Driver::Header, @@ -44,7 +43,7 @@ pub struct TrieDbInitializationStrategy; impl InitializationStrategy for TrieDbInitializationStrategy { fn initialize_database( - state_trie: &mut MptNode, + state_trie: &mut MptNode, storage_tries: &mut AddressHashMap, contracts: &mut Vec, parent_header: &mut Driver::Header, @@ -62,13 +61,13 @@ impl InitializationStrategy for TrieDbInitia // hash all the contract code let contracts = take(contracts) .into_iter() - .map(|bytes| (keccak(&bytes).into(), Bytecode::new_raw(bytes))) + .map(|bytes| (keccak256(&bytes), Bytecode::new_raw(bytes))) .collect(); // Verify account data in db - for (address, StorageEntry { storage_trie, .. }) in storage_tries.iter() { + for (address, StorageEntry { storage_trie, .. }) in storage_tries.iter_mut() { // load the account from the state trie - let state_account = state_trie.get_rlp::(&keccak(address))?; + let state_account = state_trie.get_rlp(keccak256(address))?; // check that the account storage root matches the storage trie root of the input let storage_root = state_account.map_or(EMPTY_ROOT_HASH, |a| a.storage_root); @@ -127,7 +126,7 @@ impl InitializationStrategy for MemoryDbInitializationStrategy { fn initialize_database( - state_trie: &mut MptNode, + state_trie: &mut MptNode, storage_tries: &mut AddressHashMap, contracts: &mut Vec, parent_header: &mut Driver::Header, @@ -145,7 +144,7 @@ impl InitializationStrategy // hash all the contract code let contracts = take(contracts) .into_iter() - .map(|bytes| (keccak(&bytes).into(), Bytecode::new_raw(bytes))) + .map(|bytes| (keccak256(&bytes), Bytecode::new_raw(bytes))) .collect(); // Load account data into db @@ -163,7 +162,7 @@ impl InitializationStrategy let slots = take(slots); // load the account from the state trie - let state_account = state_trie.get_rlp::(&keccak(address))?; + let state_account = state_trie.get_rlp(keccak256(address))?; // check that the account storage root matches the storage trie root of the input let storage_root = state_account.map_or(EMPTY_ROOT_HASH, |a| a.storage_root); @@ -184,7 +183,7 @@ impl InitializationStrategy HashMap::with_capacity_and_hasher(slots.len(), Default::default()); for slot in slots { let value: U256 = storage_trie - .get_rlp(&keccak(slot.to_be_bytes::<32>()))? + .get_rlp(keccak256(slot.to_be_bytes::<32>()))? .unwrap_or_default(); storage.insert(slot, value); } diff --git a/crates/preflight/Cargo.toml b/crates/preflight/Cargo.toml index f621f604..503f2089 100644 --- a/crates/preflight/Cargo.toml +++ b/crates/preflight/Cargo.toml @@ -11,6 +11,7 @@ alloy.workspace = true anyhow.workspace = true async-trait.workspace = true flate2.workspace = true +itertools.workspace = true k256.workspace = true log.workspace = true pot.workspace = true diff --git a/crates/preflight/src/client.rs b/crates/preflight/src/client.rs index 7d6e0314..599c9f25 100644 --- a/crates/preflight/src/client.rs +++ b/crates/preflight/src/client.rs @@ -17,11 +17,12 @@ use crate::driver::PreflightDriver; use crate::provider::db::ProviderDB; use crate::provider::query::{BlockQuery, UncleQuery}; use crate::provider::{new_provider, Provider}; -use crate::trie::extend_proof_tries; use alloy::network::Network; -use alloy::primitives::map::{AddressHashMap, HashMap}; -use alloy::primitives::Bytes; +use alloy::primitives::map::{AddressHashMap, B256Set, HashSet}; +use alloy::primitives::{keccak256, Bytes, U256}; +use alloy::rpc::types::EIP1186StorageProof; use anyhow::Context; +use itertools::Itertools; use log::{debug, info, warn}; use std::cell::RefCell; use std::iter::zip; @@ -29,9 +30,7 @@ use std::path::PathBuf; use std::rc::Rc; use zeth_core::db::update::into_plain_state; use zeth_core::driver::CoreDriver; -use zeth_core::mpt::{ - parse_proof, resolve_nodes_in_place, shorten_node_path, MptNode, MptNodeReference, -}; +use zeth_core::mpt::MptNode; use zeth_core::rescue::Wrapper; use zeth_core::stateless::data::{StatelessClientData, StorageEntry}; use zeth_core::stateless::engine::StatelessClientEngine; @@ -164,12 +163,12 @@ where Some(preflight_db), ); - let block_count = data.blocks.len(); + let block_count = data.blocks.len() as u64; let core_parent_header = P::derive_header(data.parent_header.clone()); - let mut state_trie = MptNode::from(R::state_root(&core_parent_header)); + let mut state_trie = MptNode::from_digest(R::state_root(&core_parent_header)); let mut storage_tries = AddressHashMap::::default(); - let mut contracts: Vec = Default::default(); + let mut contracts: HashSet = HashSet::default(); let mut ancestor_headers: Vec = Default::default(); for num_blocks in 1..=block_count { @@ -190,10 +189,6 @@ where info!("Saving provider cache ..."); preflight_db.save_provider()?; - // info!("Sanity check ..."); - // preflight_db.sanity_check(state_changeset)?; - // preflight_db.save_provider()?; - // Gather inclusion proofs for the initial and final state info!("Gathering initial proofs ..."); let initial_proofs = preflight_db.get_initial_proofs()?; @@ -223,74 +218,104 @@ where // collect the code of the used contracts let initial_db = preflight_db.inner.db.db.borrow(); for code in initial_db.contracts.values() { - contracts.push(code.bytes().clone()); + contracts.insert(code.bytes().clone()); } drop(initial_db); info!("Collected contracts: {}", contracts.len()); - // construct the sparse MPTs from the inclusion proofs - info!( - "Extending tries from {} initialization and {} finalization proofs ...", - initial_proofs.len(), - latest_proofs.len() - ); - let (state_orphans, storage_orphans) = extend_proof_tries( - &mut state_trie, - &mut storage_tries, - initial_proofs, - latest_proofs, - ) - .context("failed to extend proof tries")?; - // resolve potential orphans - let orphan_resolves = - preflight_db.resolve_orphans(block_count as u64, &state_orphans, &storage_orphans); - info!( - "Using {} proofs to resolve {} state and {} storage orphans.", - orphan_resolves.len(), - state_orphans.len(), - storage_orphans.len() - ); - for account_proof in orphan_resolves { - let node_store = parse_proof(&account_proof.account_proof)? + info!("Constructing tries from state proofs..."); + + // build the state trie from the initial account proofs + let account_proofs = initial_proofs + .values() + .flat_map(|proof| &proof.account_proof); + state_trie + .hydrate_from_rlp(account_proofs) + .context("invalid account proof")?; + + // build the storage entries from the initial storage proofs + for (address, proof) in initial_proofs { + let mut storage_trie = MptNode::from_digest(proof.storage_hash); + storage_trie + .hydrate_from_rlp(proof.storage_proof.iter().flat_map(|p| &p.proof)) + .with_context(|| format!("invalid storage proof for {}", address))?; + // collect all the unique storage slots + let slots = proof + .storage_proof .iter() - .flat_map(|n| { - vec![vec![n.clone()], shorten_node_path(n)] - .into_iter() - .flatten() - }) - .map(|n| (n.reference().as_digest(), n)) - .collect(); - resolve_nodes_in_place(&mut state_trie, &node_store); - // resolve storage orphans - if let Some(StorageEntry { storage_trie, .. }) = - storage_tries.get_mut(&account_proof.address) - { - for storage_proof in account_proof.storage_proof { - let node_store: HashMap = - parse_proof(&storage_proof.proof)? - .iter() - .flat_map(|n| { - vec![vec![n.clone()], shorten_node_path(n)] - .into_iter() - .flatten() - }) - .map(|n| (n.reference().as_digest(), n)) - .collect(); - for k in node_store.keys() { - let digest = k.digest(); - if storage_orphans - .iter() - .any(|(a, (_, d))| a == &account_proof.address && &digest == d) - { - info!( - "Resolved storage node {digest} for account {}", - account_proof.address - ); - } - } - resolve_nodes_in_place(storage_trie, &node_store); + .map(|p| p.key.0.into()) + .unique() + .collect::>(); + + storage_tries.insert( + address, + StorageEntry { + storage_trie, + slots, + }, + ); + } + + info!("Extending tries from post-state proofs..."); + + let mut unresolvable_state_keys = B256Set::default(); + + for (address, account_proof) in latest_proofs { + let db_key = keccak256(address); + + // if the key was inserted, extend with the inclusion proof + if state_trie.get(db_key).is_none() { + state_trie + .hydrate_from_rlp(account_proof.account_proof) + .with_context(|| format!("invalid account proof for {}", address))?; + continue; + } + + // otherwise, prepare trie for the removal of that key + state_trie + .resolve_orphan( + db_key, + account_proof.account_proof, + &mut unresolvable_state_keys, + ) + .with_context(|| format!("failed to resolve orphan for {}", address))?; + + let mut unresolvable_storage_keys = B256Set::default(); + + let storage_trie = &mut storage_tries.get_mut(&address).unwrap().storage_trie; + for EIP1186StorageProof { key, proof, .. } in account_proof.storage_proof { + let db_key = keccak256(key.0); + // if the key was inserted, extend with the inclusion proof + if storage_trie.get(db_key).is_none() { + storage_trie.hydrate_from_rlp(proof)?; + } else { + // otherwise, prepare trie for the removal of that key + storage_trie + .resolve_orphan(db_key, proof, &mut unresolvable_storage_keys) + .with_context(|| { + format!("failed to resolve orphan for {}@{}", key.0, address) + })?; } } + + // if orphans could not be resolved, use a range query to get that missing info + if !unresolvable_storage_keys.is_empty() { + let proof = preflight_db + .get_next_slot_proofs(block_count, address, unresolvable_storage_keys) + .with_context(|| format!("failed to get next slot for {}", address))?; + storage_trie + .hydrate_from_rlp(proof.storage_proof.iter().flat_map(|p| &p.proof)) + .with_context(|| format!("invalid storage proof for {}", address))?; + } + } + + for state_key in unresolvable_state_keys { + let proof = preflight_db + .get_next_account_proof(block_count, state_key) + .context("failed to get next account")?; + state_trie + .hydrate_from_rlp(proof.account_proof) + .with_context(|| format!("invalid account proof for {}", proof.address))?; } info!("Saving provider cache ..."); @@ -311,10 +336,10 @@ where // Report stats info!("State trie: {} nodes", state_trie.size()); - let storage_nodes: u64 = storage_tries + let storage_nodes = storage_tries .values() - .map(|e| e.storage_trie.size() as u64) - .sum(); + .map(|e| e.storage_trie.size()) + .sum::(); info!( "Storage tries: {storage_nodes} total nodes over {} accounts", storage_tries.len() @@ -338,7 +363,7 @@ where signers, state_trie, storage_tries, - contracts, + contracts: contracts.into_iter().collect(), parent_header: P::derive_header(data.parent_header), ancestor_headers, total_difficulty: data.total_difficulty, diff --git a/crates/preflight/src/db.rs b/crates/preflight/src/db.rs index 56e18e68..7690ae96 100644 --- a/crates/preflight/src/db.rs +++ b/crates/preflight/src/db.rs @@ -16,17 +16,18 @@ use crate::driver::PreflightDriver; use crate::provider::db::ProviderDB; use crate::provider::get_proofs; use crate::provider::query::{AccountRangeQuery, BlockQuery, ProofQuery, StorageRangeQuery}; -use crate::trie::TrieOrphan; use alloy::network::Network; use alloy::primitives::map::HashMap; use alloy::primitives::{Address, B256, U256}; use alloy::rpc::types::EIP1186AccountProofResponse; -use log::{error, warn}; +use anyhow::Context; +use log::{debug, error}; use reth_primitives::revm_primitives::{Account, AccountInfo, Bytecode}; use reth_revm::db::states::StateChangeset; use reth_revm::db::CacheDB; use reth_revm::{Database, DatabaseCommit, DatabaseRef}; use std::cell::{Ref, RefCell}; +use std::collections::BTreeSet; use std::marker::PhantomData; use std::ops::DerefMut; use zeth_core::db::update::Update; @@ -251,87 +252,71 @@ impl> PreflightDB { Ok(headers) } - pub fn resolve_orphans( + /// Fetches the EIP-1186 proof for the next account after a given key. + /// + /// This method retrieves an [EIP1186AccountProofResponse] for the account whose address, when + /// hashed, lexicographically follows the provided `start` key. The proof is generated for the + /// block `block_count` after the currently configured block in the provider. + pub fn get_next_account_proof( &mut self, block_count: u64, - state_orphans: &[TrieOrphan], - storage_orphans: &[(Address, TrieOrphan)], - ) -> Vec { - let state_resolves = self.resolve_state_orphans(state_orphans, block_count); - let storage_resolves = self.resolve_storage_orphans(storage_orphans, block_count); - state_resolves.into_iter().chain(storage_resolves).collect() - } - - pub fn resolve_state_orphans( - &mut self, - state_orphans: &[TrieOrphan], - block_count: u64, - ) -> Vec { + start: B256, + ) -> anyhow::Result { let initial_db = self.inner.db.db.borrow_mut(); let provider_db = initial_db.db.borrow_db(); let mut provider = provider_db.provider.borrow_mut(); - let mut result = Vec::new(); let block_no = initial_db.db.borrow_db().block_no + block_count - 1; - for (start, digest) in state_orphans { - if let Ok(next_account) = provider.get_next_account(&AccountRangeQuery { + + debug!("getting next account: start={}", start); + let address = provider + .get_next_account(&AccountRangeQuery::new(block_no, start)) + .context("debug_accountRange call failed")?; + + provider + .get_proof(&ProofQuery { block_no, - start: *start, - max_results: 1, - no_code: true, - no_storage: true, - incompletes: false, - }) { - if let Ok(proof) = provider.get_proof(&ProofQuery { - block_no, - address: next_account, - indices: Default::default(), - }) { - result.push(proof); - continue; - } - continue; - } - warn!("state orphan {digest} not found"); - } - result + address, + indices: BTreeSet::default(), + }) + .context("eth_getProof call failed") } - pub fn resolve_storage_orphans( + /// Fetches EIP-1186 proofs for the next storage slots of a given account. + /// + /// This method retrieves an [EIP1186AccountProofResponse] for multiple storage slots of a given + /// account. For each `B256` key provided in the `starts` iterator, the method finds the next + /// storage slot whose hashed index lexicographically follows the given key. The proofs are + /// generated for the block `block_count` after the currently configured block in the provider. + pub fn get_next_slot_proofs( &mut self, - storage_orphans: &[(Address, TrieOrphan)], block_count: u64, - ) -> Vec { + address: Address, + starts: impl IntoIterator, + ) -> anyhow::Result { let initial_db = self.inner.db.db.borrow_mut(); let provider_db = initial_db.db.borrow_db(); let mut provider = provider_db.provider.borrow_mut(); - let mut result = Vec::new(); let block_no = initial_db.db.borrow_db().block_no + block_count - 1; - for (address, (start, digest)) in storage_orphans { - // if let Ok(val) = provider.get_preimage(&PreimageQuery { digest: *digest }) { - // continue; - // } - if let Ok(next_slot) = provider.get_next_slot(&StorageRangeQuery { - block_no, - tx_index: 0, - address: *address, - start: *start, - max_results: 1, - }) { - if let Ok(proof) = provider.get_proof(&ProofQuery { - block_no, - address: *address, - indices: vec![next_slot] - .into_iter() - .map(|x| x.to_be_bytes().into()) - .collect(), - }) { - result.push(proof); - continue; - } - } - warn!("storage orphan {address}/{digest} not found"); + + let mut indices = BTreeSet::new(); + for start in starts { + debug!( + "getting next storage key: address={},start={}", + address, start + ); + let slot = provider + .get_next_slot(&StorageRangeQuery::new(block_no, address, start)) + .context("debug_storageRangeAt call failed")?; + indices.insert(B256::from(slot)); } - result + + provider + .get_proof(&ProofQuery { + block_no, + address, + indices, + }) + .context("eth_getProof call failed") } } diff --git a/crates/preflight/src/lib.rs b/crates/preflight/src/lib.rs index bcf82a41..15995606 100644 --- a/crates/preflight/src/lib.rs +++ b/crates/preflight/src/lib.rs @@ -34,7 +34,6 @@ pub mod client; pub mod db; pub mod driver; pub mod provider; -pub mod trie; #[derive(Debug, Default, Clone)] pub struct Witness { @@ -90,7 +89,7 @@ where block_number: u64, block_count: u64, ) -> anyhow::Result { - // Fetch all of the initial data + // Fetch all the initial data let preflight_data: StatelessClientData = spawn_blocking(move || { ::preflight( chain_id, diff --git a/crates/preflight/src/provider/query.rs b/crates/preflight/src/provider/query.rs index eeaf8152..0307fafd 100644 --- a/crates/preflight/src/provider/query.rs +++ b/crates/preflight/src/provider/query.rs @@ -62,6 +62,19 @@ pub struct AccountRangeQuery { pub incompletes: bool, } +impl AccountRangeQuery { + pub fn new(block_no: u64, start: B256) -> Self { + Self { + block_no, + start, + max_results: 1, + no_code: true, + no_storage: true, + incompletes: true, + } + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AccountRangeQueryResponse { @@ -91,6 +104,18 @@ pub struct StorageRangeQuery { pub max_results: u64, } +impl StorageRangeQuery { + pub fn new(block_no: u64, address: Address, start: B256) -> Self { + Self { + block_no, + tx_index: 0, + address, + start, + max_results: 1, + } + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct StorageRangeQueryResponse { diff --git a/crates/preflight/src/trie.rs b/crates/preflight/src/trie.rs deleted file mode 100644 index 1ba475f0..00000000 --- a/crates/preflight/src/trie.rs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2024, 2025 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use alloy::primitives::map::{AddressHashMap, HashMap}; -use alloy::primitives::{Address, B256, U256}; -use alloy::rpc::types::EIP1186AccountProofResponse; -use anyhow::Context; -use std::collections::VecDeque; -use std::iter; -use zeth_core::keccak::keccak; -use zeth_core::mpt::{ - is_not_included, mpt_from_proof, parse_proof, prefix_nibs, resolve_nodes, - resolve_nodes_in_place, shorten_node_path, MptNode, MptNodeData, MptNodeReference, -}; -use zeth_core::stateless::data::StorageEntry; - -pub type TrieOrphan = (B256, B256); -pub type OrphanPair = (Vec, Vec<(Address, TrieOrphan)>); -pub fn extend_proof_tries( - state_trie: &mut MptNode, - storage_tries: &mut AddressHashMap, - initialization_proofs: HashMap, - finalization_proofs: HashMap, -) -> anyhow::Result { - // collected orphan data - let mut state_orphans = Vec::new(); - let mut storage_orphans = Vec::new(); - // storage for encountered trie data - let mut state_nodes = HashMap::default(); - for (address, initialization_proof) in initialization_proofs { - // Create individual nodes from proof - let proof_nodes = parse_proof(&initialization_proof.account_proof) - .context("invalid account_proof encoding")?; - // Ensure the trie is consistent - mpt_from_proof(&proof_nodes).context("invalid account_proof")?; - // Insert each node into the trie data store - proof_nodes.into_iter().for_each(|node| { - assert_eq!(node.size(), 1); - state_nodes.insert(node.reference(), node); - }); - // insert inaccessible storage trie - if let alloy::primitives::map::Entry::Vacant(e) = storage_tries.entry(address) { - e.insert(StorageEntry { - storage_trie: initialization_proof.storage_hash.into(), - slots: vec![], - }); - } - // storage for encountered storage trie data - let mut storage_nodes = HashMap::default(); - for storage_proof in &initialization_proof.storage_proof { - let proof_nodes = parse_proof(&storage_proof.proof) - .context("extend_proof_tries/parse storage proof")?; - mpt_from_proof(&proof_nodes).with_context(|| { - format!("extend_proof_tries/ mpt from storage proof: {initialization_proof:?}") - })?; - // Load storage entry - let storage_entry = storage_tries.get_mut(&address).unwrap(); - let storage_key = U256::from_be_bytes(storage_proof.key.0 .0); - // Push the storage key if new - if !storage_entry.slots.contains(&storage_key) { - storage_entry.slots.push(storage_key); - } - // Load storage trie nodes into store - proof_nodes.into_iter().for_each(|node| { - storage_nodes.insert(node.reference(), node); - }); - } - - // ensure that trie orphans are loaded - let finalization_proof = finalization_proofs - .get(&address) - .with_context(|| format!("missing finalization proof for address {}", &address))?; - if let Some(state_orphan) = - add_orphaned_nodes(address, &finalization_proof.account_proof, &mut state_nodes) - .with_context(|| format!("failed to add orphaned nodes for address {}", &address))? - { - state_orphans.push(state_orphan); - } - - let mut potential_storage_orphans = Vec::new(); - for storage_proof in &finalization_proof.storage_proof { - if let Some(storage_orphan) = add_orphaned_nodes( - storage_proof.key.0, - &storage_proof.proof, - &mut storage_nodes, - ) - .context("failed to add orphaned nodes")? - { - potential_storage_orphans.push(storage_orphan); - } - } - - let storage_entry = storage_tries.get_mut(&address).unwrap(); - // Load up newly found storage nodes - resolve_nodes_in_place(&mut storage_entry.storage_trie, &storage_nodes); - // validate storage orphans - for (prefix, digest) in potential_storage_orphans { - if let Some(node) = storage_nodes.get(&MptNodeReference::Digest(digest)) { - if !node.is_digest() { - // this orphan node has been resolved - continue; - } - } - // defer node resolution - storage_orphans.push((address, (prefix, digest))); - } - } - // Load up newly found state nodes - resolve_nodes_in_place(state_trie, &state_nodes); - let state_orphans = state_orphans - .into_iter() - .filter(|o| { - state_nodes - .get(&MptNodeReference::Digest(o.1)) - .map(|n| !n.is_digest()) - .unwrap_or_default() - }) - .collect(); - - Ok((state_orphans, storage_orphans)) -} - -pub fn proofs_to_tries( - state_root: B256, - initialization_proofs: HashMap, - finalization_proofs: HashMap, -) -> anyhow::Result<(MptNode, HashMap)> { - // if no addresses are provided, return the trie only consisting of the state root - if initialization_proofs.is_empty() { - return Ok((state_root.into(), HashMap::default())); - } - - let mut storage: HashMap = - HashMap::with_capacity_and_hasher(initialization_proofs.len(), Default::default()); - - let mut state_nodes = HashMap::default(); - let mut state_root_node = MptNode::default(); - for (address, initialization_proof) in initialization_proofs { - let proof_nodes = parse_proof(&initialization_proof.account_proof) - .context("invalid account_proof encoding")?; - mpt_from_proof(&proof_nodes).context("invalid account_proof")?; - - // the first node in the proof is the root - if let Some(node) = proof_nodes.first() { - state_root_node = node.clone(); - } - - proof_nodes.into_iter().for_each(|node| { - state_nodes.insert(node.reference(), node); - }); - - let finalization_proof = finalization_proofs - .get(&address) - .with_context(|| format!("missing finalization proof for address {:#}", &address))?; - - // assure that addresses can be deleted from the state trie - add_orphaned_nodes(address, &finalization_proof.account_proof, &mut state_nodes)?; - - // if no slots are provided, return the trie only consisting of the storage root - if initialization_proof.storage_proof.is_empty() { - storage.insert( - address, - StorageEntry { - storage_trie: initialization_proof.storage_hash.into(), - slots: vec![], - }, - ); - continue; - } - - let mut storage_nodes = HashMap::default(); - let mut storage_root_node = MptNode::default(); - for storage_proof in &initialization_proof.storage_proof { - let proof_nodes = parse_proof(&storage_proof.proof).context("proofs_to_tries")?; - mpt_from_proof(&proof_nodes).context("invalid storage_proof")?; - - // the first node in the proof is the root - if let Some(node) = proof_nodes.first() { - storage_root_node = node.clone(); - } - - proof_nodes.into_iter().for_each(|node| { - storage_nodes.insert(node.reference(), node); - }); - } - - // assure that slots can be deleted from the storage trie - for storage_proof in &finalization_proof.storage_proof { - add_orphaned_nodes( - storage_proof.key.0, - &storage_proof.proof, - &mut storage_nodes, - )?; - } - // create the storage trie, from all the relevant nodes - let storage_trie = resolve_nodes(&storage_root_node, &storage_nodes); - assert_eq!(storage_trie.hash(), initialization_proof.storage_hash); - - // convert the slots to a vector of U256 - let slots = initialization_proof - .storage_proof - .iter() - .map(|p| U256::from_be_bytes(p.key.0 .0)) - .collect(); - storage.insert( - address, - StorageEntry { - storage_trie, - slots, - }, - ); - } - let state_trie = resolve_nodes(&state_root_node, &state_nodes); - assert_eq!(state_trie.hash(), state_root); - - Ok((state_trie, storage)) -} - -/// Adds all the nodes of non-inclusion proofs to the nodes. -pub fn add_orphaned_nodes( - key: impl AsRef<[u8]>, - proof: &[impl AsRef<[u8]>], - nodes_by_reference: &mut HashMap, -) -> anyhow::Result> { - if !proof.is_empty() { - let proof_nodes = parse_proof(proof).context("invalid proof encoding")?; - let offset = keccak(key); - if is_not_included(&offset, &proof_nodes)? { - // extract inferrable orphans - let node = proof_nodes.last().unwrap(); - shorten_node_path(node).into_iter().for_each(|node| { - nodes_by_reference.insert(node.reference().as_digest(), node); - }); - if let MptNodeData::Extension(_, target) = node.as_data() { - return Ok(Some(( - nibbles_to_digest(&proof_nodes_nibbles(&proof_nodes)), - target.hash(), - ))); - } - } - } - Ok(None) -} - -pub fn proof_nodes_nibbles(proof_nodes: &[MptNode]) -> Vec { - let mut nibbles = VecDeque::new(); - let mut last_child = proof_nodes.last().unwrap().reference().as_digest(); - for node in proof_nodes.iter().rev() { - match node.as_data() { - MptNodeData::Branch(children) => { - for (i, child) in children.iter().enumerate() { - if let Some(child) = child { - if child.reference().as_digest() == last_child { - nibbles.push_front(i as u8); - break; - } - } - } - } - MptNodeData::Leaf(prefix, _) | MptNodeData::Extension(prefix, _) => { - prefix_nibs(prefix) - .into_iter() - .rev() - .for_each(|n| nibbles.push_front(n)); - } - MptNodeData::Null | MptNodeData::Digest(_) => unreachable!(), - } - last_child = node.reference(); - } - nibbles.into() -} - -pub fn nibbles_to_digest(nibbles: &[u8]) -> B256 { - let padding = 64 - nibbles.len(); - let padded: Vec<_> = nibbles - .iter() - .copied() - .chain(iter::repeat(0u8).take(padding)) - .collect(); - let bytes: Vec<_> = padded - .chunks_exact(2) - .map(|byte| (byte[0] << 4) + byte[1]) - .collect(); - B256::from_slice(&bytes) -} diff --git a/crates/zeth/src/lib.rs b/crates/zeth/src/lib.rs index 9a61968c..7c485579 100644 --- a/crates/zeth/src/lib.rs +++ b/crates/zeth/src/lib.rs @@ -15,7 +15,7 @@ use crate::cli::Cli; use crate::executor::build_executor_env; use alloy::network::Network; -use alloy::primitives::B256; +use alloy::primitives::{keccak256, B256}; use clap::Parser; use log::{error, info, warn}; use reth_chainspec::NamedChain; @@ -25,7 +25,6 @@ use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use tokio::task::spawn_blocking; use zeth_core::driver::CoreDriver; -use zeth_core::keccak::keccak; use zeth_core::rescue::Recoverable; use zeth_preflight::driver::PreflightDriver; use zeth_preflight::BlockBuilder; @@ -214,7 +213,7 @@ pub fn proof_file_name( prover_opts.as_slice(), ] .concat(); - let file_name = B256::from(keccak(data)); + let file_name = keccak256(data); format!("risc0-{version}-{file_name}.{suffix}") } diff --git a/guests/reth-ethereum/Cargo.lock b/guests/reth-ethereum/Cargo.lock index 2103f8f9..347fbc00 100644 --- a/guests/reth-ethereum/Cargo.lock +++ b/guests/reth-ethereum/Cargo.lock @@ -180,9 +180,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" +checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", @@ -321,7 +321,23 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "derive_more", - "nybbles", + "nybbles 0.2.1", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-trie" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6917c79e837aa7b77b7a6dae9f89cbe15313ac161c4d3cfaf8909ef21f3d22d8" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles 0.3.4", "serde", "smallvec", "tracing", @@ -587,6 +603,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "aurora-engine-modexp" @@ -1808,6 +1827,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2196,6 +2224,19 @@ dependencies = [ "smallvec", ] +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "objc" version = "0.2.7" @@ -2762,7 +2803,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-primitives", - "alloy-trie", + "alloy-trie 0.6.0", "auto_impl", "derive_more", "once_cell", @@ -2782,7 +2823,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-primitives", - "alloy-trie", + "alloy-trie 0.6.0", "bytes", "modular-bitfield", "reth-codecs-derive", @@ -2938,7 +2979,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "derive_more", - "nybbles", + "nybbles 0.2.1", "reth-consensus", "reth-prune-types", "reth-storage-errors", @@ -3153,11 +3194,11 @@ dependencies = [ "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-trie", + "alloy-trie 0.6.0", "bytes", "derive_more", "itertools 0.13.0", - "nybbles", + "nybbles 0.2.1", "reth-codecs", "reth-primitives-traits", "revm-primitives", @@ -3363,6 +3404,22 @@ dependencies = [ "rand_core", ] +[[package]] +name = "risc0-ethereum-trie" +version = "0.1.0" +source = "git+https://github.com/risc0/risc0-ethereum?branch=feat/trie#c39c05720ebfccff520ae380ee906395d8858669" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.7.8", + "arrayvec", + "bincode", + "itertools 0.14.0", + "rkyv", + "serde", + "thiserror 2.0.11", +] + [[package]] name = "risc0-groth16" version = "1.2.1" @@ -4797,10 +4854,9 @@ dependencies = [ "reth-primitives", "reth-revm", "reth-storage-errors", + "risc0-ethereum-trie", "rkyv", "serde", - "thiserror 1.0.65", - "tiny-keccak", ] [[package]] diff --git a/guests/reth-optimism/Cargo.lock b/guests/reth-optimism/Cargo.lock index 236ff10c..174cc070 100644 --- a/guests/reth-optimism/Cargo.lock +++ b/guests/reth-optimism/Cargo.lock @@ -168,9 +168,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -179,9 +179,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" +checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", @@ -297,7 +297,23 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "derive_more", - "nybbles", + "nybbles 0.2.1", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-trie" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6917c79e837aa7b77b7a6dae9f89cbe15313ac161c4d3cfaf8909ef21f3d22d8" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles 0.3.4", "serde", "smallvec", "tracing", @@ -563,6 +579,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "aurora-engine-modexp" @@ -1784,6 +1803,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2172,6 +2200,19 @@ dependencies = [ "smallvec", ] +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "objc" version = "0.2.7" @@ -2759,7 +2800,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-primitives", - "alloy-trie", + "alloy-trie 0.6.0", "auto_impl", "derive_more", "once_cell", @@ -2780,7 +2821,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-primitives", - "alloy-trie", + "alloy-trie 0.6.0", "bytes", "modular-bitfield", "op-alloy-consensus", @@ -2905,7 +2946,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "derive_more", - "nybbles", + "nybbles 0.2.1", "reth-consensus", "reth-prune-types", "reth-storage-errors", @@ -3192,11 +3233,11 @@ dependencies = [ "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-trie", + "alloy-trie 0.6.0", "bytes", "derive_more", "itertools 0.13.0", - "nybbles", + "nybbles 0.2.1", "reth-codecs", "reth-primitives-traits", "revm-primitives", @@ -3403,6 +3444,22 @@ dependencies = [ "rand_core", ] +[[package]] +name = "risc0-ethereum-trie" +version = "0.1.0" +source = "git+https://github.com/risc0/risc0-ethereum?branch=feat/trie#7fa3505fef029200251e8110f87656c0e9ddbd39" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.7.8", + "arrayvec", + "bincode", + "itertools 0.14.0", + "rkyv", + "serde", + "thiserror 2.0.11", +] + [[package]] name = "risc0-groth16" version = "1.2.1" @@ -4837,10 +4894,9 @@ dependencies = [ "reth-primitives", "reth-revm", "reth-storage-errors", + "risc0-ethereum-trie", "rkyv", "serde", - "thiserror 1.0.65", - "tiny-keccak", ] [[package]]