diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d70088d..da75b9b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -55,7 +55,7 @@ jobs: - name: "Set up test fixture" run: | - git clone https://github.com/succinctlabs/rsp-tests --branch 2024-08-31 --depth 1 ../rsp-tests + git clone https://github.com/succinctlabs/rsp-tests --branch 2024-09-09 --depth 1 ../rsp-tests cd ../rsp-tests/ docker compose up -d diff --git a/Cargo.lock b/Cargo.lock index 8095c41..396f05b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5371,15 +5371,20 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-trie", + "anyhow", "eyre", "hex-literal", "itertools 0.13.0", "reth-execution-types", "reth-primitives", "reth-trie", + "revm", "revm-primitives", + "rlp", "rsp-mpt", "rsp-primitives", + "serde", + "thiserror", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index 9e413bb..7c3c9f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ url = "2.3" thiserror = "1.0.61" hex-literal = "0.4.1" rayon = "1.10.0" +rlp = "0.5.2" # workspace rsp-rpc-db = { path = "./crates/storage/rpc-db" } diff --git a/bin/client-eth/Cargo.lock b/bin/client-eth/Cargo.lock index a7e0d5b..008119b 100644 --- a/bin/client-eth/Cargo.lock +++ b/bin/client-eth/Cargo.lock @@ -2629,13 +2629,18 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types", + "anyhow", "eyre", "itertools 0.13.0", "reth-execution-types", "reth-primitives", "reth-trie", + "revm", "revm-primitives", + "rlp", "rsp-primitives", + "serde", + "thiserror", ] [[package]] diff --git a/bin/client-linea/Cargo.lock b/bin/client-linea/Cargo.lock index 6add423..2b27fd3 100644 --- a/bin/client-linea/Cargo.lock +++ b/bin/client-linea/Cargo.lock @@ -2629,13 +2629,18 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types", + "anyhow", "eyre", "itertools 0.13.0", "reth-execution-types", "reth-primitives", "reth-trie", + "revm", "revm-primitives", + "rlp", "rsp-primitives", + "serde", + "thiserror", ] [[package]] diff --git a/bin/client-op/Cargo.lock b/bin/client-op/Cargo.lock index c739d01..a8b231e 100644 --- a/bin/client-op/Cargo.lock +++ b/bin/client-op/Cargo.lock @@ -2629,13 +2629,18 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types", + "anyhow", "eyre", "itertools 0.13.0", "reth-execution-types", "reth-primitives", "reth-trie", + "revm", "revm-primitives", + "rlp", "rsp-primitives", + "serde", + "thiserror", ] [[package]] diff --git a/crates/executor/client/src/io.rs b/crates/executor/client/src/io.rs index 7652862..2354e90 100644 --- a/crates/executor/client/src/io.rs +++ b/crates/executor/client/src/io.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; use eyre::Result; -use reth_primitives::{revm_primitives::AccountInfo, Address, Block, Bytes, Header, B256, U256}; -use reth_trie::AccountProof; -use revm_primitives::keccak256; -use rsp_primitives::account_proof::AccountProofWithBytecode; +use reth_primitives::{revm_primitives::AccountInfo, Address, Block, Header, B256, U256}; +use reth_trie::TrieAccount; +use revm_primitives::{keccak256, Bytecode}; +use rsp_mpt::EthereumState; use rsp_witness_db::WitnessDb; use serde::{Deserialize, Serialize}; @@ -19,14 +19,14 @@ pub struct ClientExecutorInput { pub current_block: Block, /// The previous block header. pub previous_block: Header, - /// The dirty storage proofs for the storage slots that were modified. - pub dirty_storage_proofs: Vec, - /// The storage proofs for the storage slots that were accessed. - pub used_storage_proofs: HashMap, + /// Network state as of the parent block. + pub parent_state: EthereumState, + /// Requests to account state and storage slots. + pub state_requests: HashMap>, + /// Account bytecodes. + pub bytecodes: Vec, /// The block hashes. pub block_hashes: HashMap, - /// The trie node preimages. - pub trie_nodes: Vec, } impl ClientExecutorInput { @@ -37,48 +37,62 @@ impl ClientExecutorInput { /// to avoid unnecessary cloning. pub fn witness_db(&mut self) -> Result { let state_root: B256 = self.previous_block.state_root; + if state_root != self.parent_state.state_root() { + eyre::bail!("parent state root mismatch"); + } + + let bytecodes_by_hash = + self.bytecodes.iter().map(|code| (code.hash_slow(), code)).collect::>(); let mut accounts = HashMap::new(); let mut storage = HashMap::new(); - let used_storage_proofs = std::mem::take(&mut self.used_storage_proofs); - for (address, proof) in used_storage_proofs { - // Verify the storage proof. - proof.verify(state_root)?; + let state_requests = std::mem::take(&mut self.state_requests); + for (address, slots) in state_requests { + let hashed_address = keccak256(address); + let hashed_address = hashed_address.as_slice(); + + let account_in_trie = + self.parent_state.state_trie.get_rlp::(hashed_address)?; - // Update the accounts. - let account_info = match proof.proof.info { - Some(account_info) => AccountInfo { - nonce: account_info.nonce, - balance: account_info.balance, - code_hash: account_info.bytecode_hash.unwrap(), - code: Some(proof.code), + accounts.insert( + address, + match account_in_trie { + Some(account_in_trie) => AccountInfo { + balance: account_in_trie.balance, + nonce: account_in_trie.nonce, + code_hash: account_in_trie.code_hash, + code: Some( + (*bytecodes_by_hash + .get(&account_in_trie.code_hash) + .ok_or_else(|| eyre::eyre!("missing bytecode"))?) + // Cloning here is fine as `Bytes` is cheap to clone. + .to_owned(), + ), + }, + None => Default::default(), }, - None => AccountInfo::default(), - }; - accounts.insert(address, account_info); + ); - // Update the storage. - let storage_values: HashMap = proof - .proof - .storage_proofs - .into_iter() - .map(|storage_proof| (storage_proof.key.into(), storage_proof.value)) - .collect(); - storage.insert(address, storage_values); - } + if !slots.is_empty() { + let mut address_storage = HashMap::new(); + + let storage_trie = self + .parent_state + .storage_tries + .get(hashed_address) + .ok_or_else(|| eyre::eyre!("parent state does not contain storage trie"))?; + + for slot in slots { + let slot_value = storage_trie + .get_rlp::(keccak256(slot.to_be_bytes::<32>()).as_slice())? + .unwrap_or_default(); + address_storage.insert(slot, slot_value); + } - let mut trie_nodes = HashMap::new(); - for preimage in self.trie_nodes.iter() { - // TODO: refactor witness db building to avoid cloning and `mem::take`. - trie_nodes.insert(keccak256(preimage), preimage.to_owned()); + storage.insert(address, address_storage); + } } - Ok(WitnessDb { - accounts, - storage, - block_hashes: std::mem::take(&mut self.block_hashes), - state_root: self.current_block.state_root, - trie_nodes, - }) + Ok(WitnessDb { accounts, storage, block_hashes: std::mem::take(&mut self.block_hashes) }) } } diff --git a/crates/executor/client/src/lib.rs b/crates/executor/client/src/lib.rs index 9006eb2..e54c5f8 100644 --- a/crates/executor/client/src/lib.rs +++ b/crates/executor/client/src/lib.rs @@ -146,8 +146,10 @@ impl ClientExecutor { // Verify the state root. let state_root = profile!("compute state root", { - rsp_mpt::compute_state_root(&executor_outcome, &input.dirty_storage_proofs, &witness_db) - })?; + input.parent_state.update(&executor_outcome.hash_state_slow()); + input.parent_state.state_root() + }); + if state_root != input.current_block.state_root { eyre::bail!("mismatched state root"); } diff --git a/crates/executor/host/src/lib.rs b/crates/executor/host/src/lib.rs index df9b493..faf0b46 100644 --- a/crates/executor/host/src/lib.rs +++ b/crates/executor/host/src/lib.rs @@ -1,15 +1,15 @@ -use std::marker::PhantomData; +use std::{collections::BTreeSet, marker::PhantomData}; use alloy_provider::{network::AnyNetwork, Provider}; use alloy_transport::Transport; use eyre::{eyre, Ok}; -use itertools::Itertools; use reth_execution_types::ExecutionOutcome; use reth_primitives::{proofs, Block, Bloom, Receipts, B256}; use revm::db::CacheDB; use rsp_client_executor::{ io::ClientExecutorInput, ChainVariant, EthereumVariant, LineaVariant, OptimismVariant, Variant, }; +use rsp_mpt::EthereumState; use rsp_primitives::account_proof::eip1186_proof_to_account_proof; use rsp_rpc_db::RpcDb; @@ -112,27 +112,61 @@ impl + Clone> HostExecutor>() + }) + .unwrap_or_default() + .into_iter() + .collect::>(); + + let keys = used_keys + .iter() + .map(|key| B256::from(*key)) + .chain(modified_keys.clone().into_iter()) + .collect::>() + .into_iter() + .collect::>(); + let storage_proof = self .provider - .get_proof(address, storage_keys) + .get_proof(*address, keys.clone()) .block_id((block_number - 1).into()) .await?; - dirty_storage_proofs.push(eip1186_proof_to_account_proof(storage_proof)); + before_storage_proofs.push(eip1186_proof_to_account_proof(storage_proof)); + + let storage_proof = self + .provider + .get_proof(*address, modified_keys) + .block_id((block_number).into()) + .await?; + after_storage_proofs.push(eip1186_proof_to_account_proof(storage_proof)); } + let state = EthereumState::from_proofs( + previous_block.state_root, + &before_storage_proofs.iter().map(|item| (item.address, item.clone())).collect(), + &after_storage_proofs.iter().map(|item| (item.address, item.clone())).collect(), + )?; + // Verify the state root. tracing::info!("verifying the state root"); - let state_root = - rsp_mpt::compute_state_root(&executor_outcome, &dirty_storage_proofs, &rpc_db)?; + let state_root = { + let mut mutated_state = state.clone(); + mutated_state.update(&executor_outcome.hash_state_slow()); + mutated_state.state_root() + }; if state_root != current_block.state_root { eyre::bail!("mismatched state root"); } @@ -167,12 +201,12 @@ impl + Clone> HostExecutor; - -/// No additional context is needed since the `preimage_context` feature is disabled. -#[cfg(not(feature = "preimage_context"))] -type RootContext = (); - -/// Computes the state root of a block's Merkle Patricia Trie given an [ExecutionOutcome] and a list -/// of [EIP1186AccountProofResponse] storage proofs. -pub fn compute_state_root( - execution_outcome: &ExecutionOutcome, - storage_proofs: &[AccountProof], - db: &DB, -) -> eyre::Result -where - DB: ExtDatabaseRef, -{ - // Reconstruct prefix sets manually to record pre-images for subsequent lookups. - let mut hashed_state = HashedPostState::default(); - let mut account_reverse_lookup = HashMap::::default(); - let mut storage_reverse_lookup = HashMap::::default(); - for (address, account) in execution_outcome.bundle_accounts_iter() { - let hashed_address = keccak256(address); - account_reverse_lookup.insert(hashed_address, address); - hashed_state.accounts.insert(hashed_address, account.info.clone().map(Into::into)); - - let mut hashed_storage = HashedStorage::new(account.status.was_destroyed()); - for (key, value) in &account.storage { - let slot = B256::new(key.to_be_bytes()); - let hashed_slot = keccak256(slot); - storage_reverse_lookup.insert(hashed_slot, slot); - hashed_storage.storage.insert(hashed_slot, value.present_value); - } - - hashed_state.storages.insert(hashed_address, hashed_storage); - } - - // Compute the storage roots for each account. - let mut storage_roots = HashMap::::default(); - let prefix_sets = hashed_state.construct_prefix_sets(); - let account_prefix_set = prefix_sets.account_prefix_set.freeze(); - for account_nibbles in account_prefix_set.iter() { - let hashed_address = B256::from_slice(&account_nibbles.pack()); - let address = *account_reverse_lookup.get(&hashed_address).unwrap(); - let storage_prefix_sets = - prefix_sets.storage_prefix_sets.get(&hashed_address).cloned().unwrap_or_default(); - - let proof = storage_proofs.iter().find(|x| x.address == address).unwrap(); - let root = if proof.storage_proofs.is_empty() { - proof.storage_root - } else { - #[cfg(feature = "preimage_context")] - let context = Some(address); - #[cfg(not(feature = "preimage_context"))] - let context = (); - - compute_root_from_proofs( - storage_prefix_sets.freeze().iter().map(|storage_nibbles| { - let hashed_slot = B256::from_slice(&storage_nibbles.pack()); - let slot = storage_reverse_lookup.get(&hashed_slot).unwrap(); - let storage_proof = - proof.storage_proofs.iter().find(|x| x.key.0 == slot).unwrap(); - let encoded = Some( - hashed_state - .storages - .get(&hashed_address) - .and_then(|s| s.storage.get(&hashed_slot).cloned()) - .unwrap_or_default(), - ) - .filter(|v| !v.is_zero()) - .map(|v| alloy_rlp::encode_fixed_size(&v).to_vec()); - (storage_nibbles.clone(), encoded, storage_proof.proof.clone()) - }), - db, - context, - )? - }; - storage_roots.insert(hashed_address, root); - } - - #[cfg(feature = "preimage_context")] - let context = None; - #[cfg(not(feature = "preimage_context"))] - let context = (); - - // Compute the state root of the entire trie. - let mut rlp_buf = Vec::with_capacity(128); - compute_root_from_proofs( - account_prefix_set.iter().map(|account_nibbles| { - let hashed_address = B256::from_slice(&account_nibbles.pack()); - let address = *account_reverse_lookup.get(&hashed_address).unwrap(); - let proof = storage_proofs.iter().find(|x| x.address == address).unwrap(); - - let storage_root = *storage_roots.get(&hashed_address).unwrap(); - - let account = hashed_state.accounts.get(&hashed_address).unwrap().unwrap_or_default(); - let encoded = if account.is_empty() && storage_root == EMPTY_ROOT_HASH { - None - } else { - rlp_buf.clear(); - TrieAccount::from((account, storage_root)).encode(&mut rlp_buf); - Some(rlp_buf.clone()) - }; - (account_nibbles.clone(), encoded, proof.proof.clone()) - }), - db, - context, - ) +use eyre::Result; +use reth_trie::{AccountProof, HashedPostState, TrieAccount}; +use revm::primitives::{Address, HashMap, B256}; +use serde::{Deserialize, Serialize}; + +/// Module containing MPT code adapted from `zeth`. +mod mpt; +use mpt::{proofs_to_tries, MptNode}; + +/// Ethereum state trie and account storage tries. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct EthereumState { + pub state_trie: MptNode, + pub storage_tries: HashMap, } -/// Given a list of Merkle-Patricia proofs, compute the root of the trie. -fn compute_root_from_proofs( - items: impl IntoIterator>, Vec)>, - db: &DB, - #[allow(unused)] root_context: RootContext, -) -> eyre::Result -where - DB: ExtDatabaseRef, -{ - let mut trie_nodes = BTreeMap::default(); - let mut ignored_keys = HashSet::::default(); - - for (key, value, proof) in items { - let mut path = Nibbles::default(); - let mut proof_iter = proof.iter().peekable(); - - while let Some(encoded) = proof_iter.next() { - let mut next_path = path.clone(); - match TrieNode::decode(&mut &encoded[..])? { - TrieNode::Branch(branch) => { - next_path.push(key[path.len()]); - let mut stack_ptr = branch.as_ref().first_child_index(); - for index in CHILD_INDEX_RANGE { - let mut branch_child_path = path.clone(); - branch_child_path.push(index); - - if branch.state_mask.is_bit_set(index) { - if !key.starts_with(&branch_child_path) { - let child = &branch.stack[stack_ptr]; - if child.len() == B256::len_bytes() + 1 { - // The child node is referred to by hash. - trie_nodes.insert( - branch_child_path, - Either::Left(B256::from_slice(&child[1..])), - ); - } else { - // The child node is encoded in-place. This can happen when the - // encoded child itself is shorter than 32 bytes: - // - // https://github.com/ethereum/ethereum-org-website/blob/6eed7bcfd708ca605447dd9b8fde8f74cfcaf8d9/public/content/developers/docs/data-structures-and-encoding/patricia-merkle-trie/index.md?plain=1#L186 - if let TrieNode::Leaf(child_leaf) = - TrieNode::decode(&mut &child[..])? - { - branch_child_path.extend_from_slice(&child_leaf.key); - trie_nodes.insert( - branch_child_path, - Either::Right(child_leaf.value), - ); - } else { - // Same as the case for an extension node's child below, - // this is possible in theory but extremely unlikely (even - // more unlikely than the extension node's case as the node - // header takes up extra space), making it impractical to - // find proper test cases. It's better to be left - // unimplemented. - unimplemented!( - "branch child is a non-leaf node encoded in place" - ); - } - } - } - stack_ptr += 1; - } - } - } - TrieNode::Extension(extension) => { - next_path.extend_from_slice(&extension.key); - - // Add the extended branch node if this is the last proof item. This can happen - // when proving the previous absence of a new node that shares the prefix with - // the extension node. - if proof_iter.peek().is_none() { - let child = &extension.child; - if child.len() == B256::len_bytes() + 1 { - // The extension child is referenced by hash. - trie_nodes.insert( - next_path.clone(), - Either::Left(B256::from_slice(&child[1..])), - ); - } else { - // An extension's child can only be a branch. Since here it's also not a - // hash, it can only be a branch node encoded in place. This could - // happen in theory when two leaf nodes share a very long common prefix - // and both have very short values. - // - // In practice, since key paths are Keccak hashes, it's extremely - // difficult to get two slots like this for testing. Since this cannot - // be properly tested, it's more preferable to leave it unimplemented to - // be alerted when this is hit (which is extremely unlikely). - // - // Using `unimplemented!` instead of `todo!` because of this. - // - // To support this, the underlying `alloy-trie` crate (which is - // currently faulty for not supported in-place encoded nodes) must first - // be patched to support adding in-place nodes to the hash builder. - // Relevant PR highlighting the issue: - // - // https://github.com/alloy-rs/trie/pull/27 - unimplemented!("extension child is a branch node encoded in place") - } - } - } - TrieNode::Leaf(leaf) => { - next_path.extend_from_slice(&leaf.key); - if next_path == key { - if value.is_none() { - // The proof points to the node of interest, meaning the node previously - // exists. The node does not exist now, so the parent pointing to this - // child needs to be eliminated too. - - // Recover the path before the extensions. We either have to clone - // before the extension or recover here. Recovering here is probably - // more efficient as long as deletion is not the majority of the - // updates. - ignored_keys - .insert(next_path.slice(0..(next_path.len() - leaf.key.len()))); - } - } else { - // The proof points to a neighbour. This happens when proving the previous - // absence of the node of interest. - // - // We insert this neighbour node only if it's vacant to avoid overwriting - // it when the neighbour node itself is being updated. - if let Entry::Vacant(entry) = trie_nodes.entry(next_path.clone()) { - entry.insert(Either::Right(leaf.value.clone())); - } - } - } - }; - path = next_path; - } - - if let Some(value) = value { - // This overwrites any value that might have been inserted during proof walking, which - // can happen when an immediate upper neighbour is inserted where the already inserted - // value would be outdated. - trie_nodes.insert(key, Either::Right(value)); - } else { - // This is a node deletion. If this key is not ignored then an insertion of an immediate - // upper neighbour would result in this node being added (and thus treated as not - // deleted) as part of the proof walking process. - ignored_keys.insert(key); - } +impl EthereumState { + /// Builds Ethereum state tries from relevant proofs before and after a state transition. + pub fn from_proofs( + state_root: B256, + parent_proofs: &HashMap, + proofs: &HashMap, + ) -> Result { + proofs_to_tries(state_root, parent_proofs, proofs).map_err(|err| eyre::eyre!("{}", err)) } - // Ignore branch child hashes in the path of leaves or lower child hashes. - let mut keys = trie_nodes.keys().peekable(); - while let Some(key) = keys.next() { - if keys.peek().map_or(false, |next| next.starts_with(key)) { - ignored_keys.insert(key.clone()); - } - } + /// Mutates state based on diffs provided in [`HashedPostState`]. + pub fn update(&mut self, post_state: &HashedPostState) { + for (hashed_address, account) in post_state.accounts.iter() { + let hashed_address = hashed_address.as_slice(); - // Build the hash tree. - let mut hash_builder = HashBuilder::default(); - let mut trie_nodes = - trie_nodes.into_iter().filter(|(path, _)| !ignored_keys.contains(path)).peekable(); - while let Some((mut path, value)) = trie_nodes.next() { - match value { - Either::Left(branch_hash) => { - let parent_branch_path = path.slice(..path.len() - 1); - let has_neighbour = (!hash_builder.key.is_empty() && - hash_builder.key.starts_with(&parent_branch_path)) || - trie_nodes - .peek() - .map_or(false, |next| next.0.starts_with(&parent_branch_path)); + match account { + Some(account) => { + let state_storage = &post_state.storages.get(hashed_address).unwrap(); + let storage_root = { + let storage_trie = self.storage_tries.get_mut(hashed_address).unwrap(); - if has_neighbour { - hash_builder.add_branch(path, branch_hash, false); - } else { - // Parent was a branch node but now all but one children are gone. We - // technically have to modify this branch node, but the `alloy-trie` hash - // builder handles this automatically when supplying child nodes. - - #[cfg(feature = "preimage_context")] - let preimage = db - .trie_node_ref_with_context( - branch_hash, - PreimageContext { address: &root_context, branch_path: &path }, - ) - .unwrap(); - #[cfg(not(feature = "preimage_context"))] - let preimage = db.trie_node_ref(branch_hash).unwrap(); - - match TrieNode::decode(&mut &preimage[..]).unwrap() { - TrieNode::Branch(_) => { - // This node is a branch node that's referenced by hash. There's no need - // to handle the content as the node itself is unchanged. - hash_builder.add_branch(path, branch_hash, false); + if state_storage.wiped { + storage_trie.clear(); } - TrieNode::Extension(extension) => { - // This node is an extension node. Simply prepend the leaf node's key - // with the original branch index. `alloy-trie` automatically handles - // this so we only have to reconstruct the full key path. - path.extend_from_slice(&extension.key); - // In theory, it's possible that this extension node's child branch is - // encoded in-place, though it should be extremely rare, as for that to - // happen, at least 2 storage nodes must share a very long prefix, which - // is very unlikely to happen given that they're hashes. - // - // Moreover, `alloy-trie` currently does not offer an API for this rare - // case anyway. See relevant (but not directly related) PR: - // - // https://github.com/alloy-rs/trie/pull/27 - if extension.child.len() == B256::len_bytes() + 1 { - hash_builder.add_branch( - path, - B256::from_slice(&extension.child[1..]), - false, - ); + for (key, value) in state_storage.storage.iter() { + let key = key.as_slice(); + if value.is_zero() { + storage_trie.delete(key).unwrap(); } else { - todo!("handle in-place extension child") + storage_trie.insert_rlp(key, *value).unwrap(); } } - TrieNode::Leaf(leaf) => { - // Same as the extension node's case: we only have to reconstruct the - // full path. - path.extend_from_slice(&leaf.key); - hash_builder.add_leaf(path, &leaf.value); - } - } - } - } - Either::Right(leaf_value) => { - hash_builder.add_leaf(path, &leaf_value); - } - } - } - let root = hash_builder.root(); - Ok(root) -} - -#[cfg(test)] -mod tests { - use super::*; - - use alloy_trie::proof::ProofRetainer; - use hex_literal::hex; - /// Leaf node A: - /// - /// e1 => list len = 33 - /// 3a => odd leaf with path `a` - /// 9f => string len = 31 - /// 9e => string len = 30 - /// 888888888888888888888888888888888888888888888888888888888888 => value - /// - /// Flattened: - /// e13a9f9e888888888888888888888888888888888888888888888888888888888888 - /// - /// Trie node hash: - /// c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 - const LEAF_A: Bytes = Bytes::from_static(&hex!( - "e13a9f9e888888888888888888888888888888888888888888888888888888888888" - )); + storage_trie.hash() + }; - struct TestTrieDb { - preimages: Vec, - } - - impl TestTrieDb { - fn new() -> Self { - Self { preimages: vec![LEAF_A] } - } - } - - impl ExtDatabaseRef for TestTrieDb { - type Error = std::convert::Infallible; - - fn trie_node_ref(&self, hash: B256) -> std::result::Result { - for preimage in self.preimages.iter() { - if keccak256(preimage) == hash { - return std::result::Result::Ok(preimage.to_owned()); + let state_account = TrieAccount { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash: account.get_bytecode_hash(), + }; + self.state_trie.insert_rlp(hashed_address, state_account).unwrap(); + } + None => { + self.state_trie.delete(hashed_address).unwrap(); } } - - panic!("missing preimage for test") - } - - fn trie_node_ref_with_context( - &self, - hash: B256, - _context: PreimageContext<'_>, - ) -> Result { - self.trie_node_ref(hash) } } - #[test] - fn test_delete_single_leaf() { - // Trie before with nodes - // - // - `1a`: 888888888888888888888888888888888888888888888888888888888888 - // - `2a`: 888888888888888888888888888888888888888888888888888888888888 - // - `3a`: 888888888888888888888888888888888888888888888888888888888888 - // - // Root: - // - // f8 => list len of len = 1 - // 71 => list len = 113 - // 80 - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // - // Flattened: - // f87180a0c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c7 - // 2d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c72d0c79d673ad5a - // cb10a951f0e82cf2e461b98c4e5afb0348ccab5bb42180808080808080808080808080 - // - // Root hash - // 929a169d86a02de55457b8928bd3cdae55b24fe2771f7a3edaa992c0500c4427 - - // Deleting node `2a`: - // - // - `1a`: 888888888888888888888888888888888888888888888888888888888888 - // - `3a`: 888888888888888888888888888888888888888888888888888888888888 - // - // New root: - // - // f8 => list len of len = 1 - // 51 => list len = 81 - // 80 - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // 80 - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // - // Flattened: - // f85180a0c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb42180a0c2c2 - // c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb42180808080808080808080 - // 808080 - // - // Root hash - // ff07cbbe26d25f65cf2ff08dc127e71b8cb238bee5da9df515422ff7eaa8d67e - - let root = compute_root_from_proofs( - [( - Nibbles::from_nibbles([0x2, 0xa]), - None, - vec![ - Bytes::from_static(&hex!( - "\ -f87180a0c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c7\ -2d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c72d0c79d673ad5a\ -cb10a951f0e82cf2e461b98c4e5afb0348ccab5bb42180808080808080808080808080" - )), - LEAF_A, - ], - )], - &TestTrieDb::new(), - None, - ) - .unwrap(); - - assert_eq!(root, hex!("ff07cbbe26d25f65cf2ff08dc127e71b8cb238bee5da9df515422ff7eaa8d67e")); - } - - #[test] - fn test_delete_multiple_leaves() { - // Trie before with nodes - // - // - `1a`: 888888888888888888888888888888888888888888888888888888888888 - // - `2a`: 888888888888888888888888888888888888888888888888888888888888 - // - `3a`: 888888888888888888888888888888888888888888888888888888888888 - // - `4a`: 888888888888888888888888888888888888888888888888888888888888 - // - // Root: - // - // f8 => list len of len = 1 - // 91 => list len = 145 - // 80 - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // - // Flattened: - // f89180a0c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c7 - // 2d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c72d0c79d673ad5a - // cb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c72d0c79d673ad5acb10a951f0e82c - // f2e461b98c4e5afb0348ccab5bb421808080808080808080808080 - // - // Root hash - // d34c1443edf7e282fcfd056db2ec24bcaf797dc3a039e0628473b069a2e8b1be - - // Deleting node `2a` and `3a`: - // - // - `1a`: 888888888888888888888888888888888888888888888888888888888888 - // - // New root: - // - // f8 => list len of len = 1 - // 51 => list len = 81 - // 80 - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // 80 - // 80 - // a0 => branch hash - // c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421 => leaf node A - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // 80 - // - // Flattened: - // f85180a0c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb4218080a0c2 - // c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421808080808080808080 - // 808080 - // - // Root hash - // 4a2aa1a2188e9bf279d51729b0c5789e4f0605c85752f9ca47760fcbe0f80244 - - let root = compute_root_from_proofs( - [ - ( - Nibbles::from_nibbles([0x2, 0xa]), - None, - vec![ - Bytes::from_static(&hex!( - "\ -f89180a0c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c7\ -2d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c72d0c79d673ad5a\ -cb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c72d0c79d673ad5acb10a951f0e82c\ -f2e461b98c4e5afb0348ccab5bb421808080808080808080808080" - )), - LEAF_A, - ], - ), - ( - Nibbles::from_nibbles([0x3, 0xa]), - None, - vec![ - Bytes::from_static(&hex!( - "\ -f89180a0c2c2c72d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c7\ -2d0c79d673ad5acb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c72d0c79d673ad5a\ -cb10a951f0e82cf2e461b98c4e5afb0348ccab5bb421a0c2c2c72d0c79d673ad5acb10a951f0e82c\ -f2e461b98c4e5afb0348ccab5bb421808080808080808080808080" - )), - LEAF_A, - ], - ), - ], - &TestTrieDb::new(), - None, - ) - .unwrap(); - - assert_eq!(root, hex!("4a2aa1a2188e9bf279d51729b0c5789e4f0605c85752f9ca47760fcbe0f80244")); - } - - #[test] - fn test_insert_with_updated_neighbour() { - let value_1 = hex!("9e888888888888888888888888888888888888888888888888888888888888"); - let value_2 = hex!("9e999999999999999999999999999999999999999999999999999999999999"); - - // Trie before as a branch with 2 nodes: - // - // - `11a`: 888888888888888888888888888888888888888888888888888888888888 - // - `2a`: 888888888888888888888888888888888888888888888888888888888888 - - let mut hash_builder = - HashBuilder::default().with_proof_retainer(ProofRetainer::new(vec![ - Nibbles::from_nibbles([0x1, 0x1, 0xa]), - Nibbles::from_nibbles([0x1, 0x1, 0xb]), - ])); - hash_builder.add_leaf(Nibbles::from_nibbles([0x1, 0x1, 0xa]), &value_1); - hash_builder.add_leaf(Nibbles::from_nibbles([0x2, 0xa]), &value_1); - - hash_builder.root(); - let proofs = hash_builder.take_proofs(); - - // Trie after updating `11a` and inserting `11b`: - // - // - `11a`: 999999999999999999999999999999999999999999999999999999999999 - // - `11b`: 888888888888888888888888888888888888888888888888888888888888 - // - `2a`: 888888888888888888888888888888888888888888888888888888888888 - // - // Root branch child slot 1 turns from a leaf to another branch. - - let mut hash_builder = HashBuilder::default(); - hash_builder.add_leaf(Nibbles::from_nibbles([0x1, 0x1, 0xa]), &value_2); - hash_builder.add_leaf(Nibbles::from_nibbles([0x1, 0x1, 0xb]), &value_1); - hash_builder.add_leaf(Nibbles::from_nibbles([0x2, 0xa]), &value_1); - - let root = compute_root_from_proofs( - [ - ( - Nibbles::from_nibbles([0x1, 0x1, 0xa]), - Some( - hex!("9e999999999999999999999999999999999999999999999999999999999999") - .to_vec(), - ), - vec![ - proofs.get(&Nibbles::default()).unwrap().to_owned(), - proofs.get(&Nibbles::from_nibbles([0x1])).unwrap().to_owned(), - ], - ), - ( - Nibbles::from_nibbles([0x1, 0x1, 0xb]), - Some( - hex!("9e888888888888888888888888888888888888888888888888888888888888") - .to_vec(), - ), - vec![ - proofs.get(&Nibbles::default()).unwrap().to_owned(), - proofs.get(&Nibbles::from_nibbles([0x1])).unwrap().to_owned(), - ], - ), - ], - &TestTrieDb::new(), - None, - ) - .unwrap(); - - assert_eq!(root, hash_builder.root()); - } - - #[test] - fn test_insert_with_deleted_neighbour() { - let value = hex!("9e888888888888888888888888888888888888888888888888888888888888"); - - // Trie before as a branch with 2 nodes: - // - // - `11a`: 888888888888888888888888888888888888888888888888888888888888 - // - `2a`: 888888888888888888888888888888888888888888888888888888888888 - - let mut hash_builder = - HashBuilder::default().with_proof_retainer(ProofRetainer::new(vec![ - Nibbles::from_nibbles([0x1, 0x1, 0xa]), - Nibbles::from_nibbles([0x1, 0x1, 0xb]), - ])); - hash_builder.add_leaf(Nibbles::from_nibbles([0x1, 0x1, 0xa]), &value); - hash_builder.add_leaf(Nibbles::from_nibbles([0x2, 0xa]), &value); - - hash_builder.root(); - let proofs = hash_builder.take_proofs(); - - // Trie after deleting `11a` and inserting `11b`: - // - // - `11b`: 888888888888888888888888888888888888888888888888888888888888 - // - `2a`: 888888888888888888888888888888888888888888888888888888888888 - // - // Root branch child slot 1 turns from a leaf to another branch. - - let mut hash_builder = HashBuilder::default(); - hash_builder.add_leaf(Nibbles::from_nibbles([0x1, 0x1, 0xb]), &value); - hash_builder.add_leaf(Nibbles::from_nibbles([0x2, 0xa]), &value); - - let root = compute_root_from_proofs( - [ - ( - Nibbles::from_nibbles([0x1, 0x1, 0xa]), - None, - vec![ - proofs.get(&Nibbles::default()).unwrap().to_owned(), - proofs.get(&Nibbles::from_nibbles([0x1])).unwrap().to_owned(), - ], - ), - ( - Nibbles::from_nibbles([0x1, 0x1, 0xb]), - Some( - hex!("9e888888888888888888888888888888888888888888888888888888888888") - .to_vec(), - ), - vec![ - proofs.get(&Nibbles::default()).unwrap().to_owned(), - proofs.get(&Nibbles::from_nibbles([0x1])).unwrap().to_owned(), - ], - ), - ], - &TestTrieDb::new(), - None, - ) - .unwrap(); - - assert_eq!(root, hash_builder.root()); - } - - #[test] - fn test_only_root_node_left() { - let value = hex!("9e888888888888888888888888888888888888888888888888888888888888"); - - // Trie before as a branch with 2 nodes: - // - // - `1a`: 888888888888888888888888888888888888888888888888888888888888 - // - `2a`: 888888888888888888888888888888888888888888888888888888888888 - - let mut hash_builder = HashBuilder::default() - .with_proof_retainer(ProofRetainer::new(vec![Nibbles::from_nibbles([0x1, 0xa])])); - hash_builder.add_leaf(Nibbles::from_nibbles([0x1, 0xa]), &value); - hash_builder.add_leaf(Nibbles::from_nibbles([0x2, 0xa]), &value); - - hash_builder.root(); - let proofs = hash_builder.take_proofs(); - - dbg!(&proofs); - - // Trie after deleting `1a`: - // - // - `2a`: 888888888888888888888888888888888888888888888888888888888888 - // - // Root branch child slot 1 turns from a leaf to another branch. - - let mut hash_builder = HashBuilder::default(); - hash_builder.add_leaf(Nibbles::from_nibbles([0x2, 0xa]), &value); - - let root = compute_root_from_proofs( - [( - Nibbles::from_nibbles([0x1, 0xa]), - None, - vec![ - proofs.get(&Nibbles::default()).unwrap().to_owned(), - proofs.get(&Nibbles::from_nibbles([0x1])).unwrap().to_owned(), - ], - )], - &TestTrieDb::new(), - None, - ) - .unwrap(); - - assert_eq!(root, hash_builder.root()); + /// Computes the state root. + pub fn state_root(&self) -> B256 { + self.state_trie.hash() } } diff --git a/crates/mpt/src/mpt.rs b/crates/mpt/src/mpt.rs new file mode 100644 index 0000000..fbddb4e --- /dev/null +++ b/crates/mpt/src/mpt.rs @@ -0,0 +1,1322 @@ +// This code is modified from the original implementation of Zeth. +// +// Reference: https://github.com/risc0/zeth +// +// Copyright 2023 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. + +#![allow(unreachable_pub)] +#![allow(dead_code)] + +use alloc::boxed::Box; +use alloy_primitives::{b256, B256}; +use alloy_rlp::Encodable; +use core::{ + cell::RefCell, + cmp, + fmt::{Debug, Write}, + iter, mem, +}; +use reth_trie::AccountProof; +use revm::primitives::HashMap; + +use rlp::{Decodable, DecoderError, Prototype, Rlp}; +use serde::{Deserialize, Serialize}; +use thiserror::Error as ThisError; + +use anyhow::{Context, Result}; +use reth_primitives::Address; + +use super::EthereumState; + +pub trait RlpBytes { + /// Returns the RLP-encoding. + fn to_rlp(&self) -> Vec; +} + +impl RlpBytes for T +where + T: alloy_rlp::Encodable, +{ + #[inline] + fn to_rlp(&self) -> Vec { + let rlp_length = self.length(); + let mut out = Vec::with_capacity(rlp_length); + self.encode(&mut out); + debug_assert_eq!(out.len(), rlp_length); + out + } +} + +/// Root hash of an empty trie. +pub const EMPTY_ROOT: B256 = + b256!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); + +extern crate alloc; + +/// Represents the Keccak-256 hash of an empty byte slice. +/// +/// This is a constant value and can be used as a default or placeholder +/// in various cryptographic operations. +pub const KECCAK_EMPTY: B256 = + b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); + +/// 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] { + // TODO: Remove this benchmarking code once performance testing is complete. + // std::hint::black_box(sha2::Sha256::digest(&data)); + *alloy_primitives::utils::keccak256(data) +} + +/// 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)] +pub struct MptNode { + /// The type and data of the node. + data: MptNodeData, + /// Cache for a previously computed reference of this node. This is skipped during + /// serialization. + #[serde(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), + /// Represents errors related to the RLP encoding and decoding, specifically legacy + /// errors. + #[error("RLP error")] + LegacyRlp(#[from] DecoderError), +} + +/// 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, Deserialize)] +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([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, Box), + /// Represents a sub-trie by its hash, allowing for efficient storage of large + /// sub-tries without storing their entire content. + Digest(B256), +} + +/// 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), +} + +/// 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 { + 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); + } + } + } + + /// 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. +/// +/// **Note**: This implementation is still using the older RLP library and needs to be +/// migrated to `alloy_rlp` in the future. +// TODO: migrate to alloy_rlp +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(rlp: &Rlp<'_>) -> Result { + match rlp.prototype()? { + Prototype::Null | Prototype::Data(0) => Ok(MptNodeData::Null.into()), + Prototype::List(2) => { + let path: Vec = rlp.val_at(0)?; + let prefix = path[0]; + if (prefix & (2 << 4)) == 0 { + let node: MptNode = Decodable::decode(&rlp.at(1)?)?; + Ok(MptNodeData::Extension(path, Box::new(node)).into()) + } else { + Ok(MptNodeData::Leaf(path, rlp.val_at(1)?).into()) + } + } + Prototype::List(17) => { + let mut node_list = Vec::with_capacity(16); + for node_rlp in rlp.iter().take(16) { + match node_rlp.prototype()? { + Prototype::Null | Prototype::Data(0) => { + node_list.push(None); + } + _ => node_list.push(Some(Box::new(Decodable::decode(&node_rlp)?))), + } + } + let value: Vec = rlp.val_at(16)?; + if value.is_empty() { + Ok(MptNodeData::Branch(node_list.try_into().unwrap()).into()) + } else { + Err(DecoderError::Custom("branch node with value")) + } + } + Prototype::Data(32) => { + let bytes: Vec = rlp.as_val()?; + Ok(MptNodeData::Digest(B256::from_slice(&bytes)).into()) + } + _ => Err(DecoderError::RlpIncorrectListLen), + } + } +} + +/// 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(); + } + + /// Decodes an RLP-encoded [MptNode] from the provided byte slice. + /// + /// This method allows for the deserialization of a previously serialized [MptNode]. + #[inline] + pub fn decode(bytes: impl AsRef<[u8]>) -> Result { + rlp::decode(bytes.as_ref()).map_err(Error::from) + } + + /// 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 [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)?)), + None => Ok(None), + } + } + + 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 or digest, convert to an extension + MptNodeData::Branch(_) | MptNodeData::Digest(_) => { + self.data = MptNodeData::Extension( + to_encoded_path(&[index as u8], false), + orphan, + ); + } + 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 or digest, the extension is still correct + MptNodeData::Branch(_) | MptNodeData::Digest(_) => {} + } + } + MptNodeData::Digest(digest) => return Err(Error::NodeNotResolved(*digest)), + }; + + 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) + } + + /// 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), value.to_rlp()) + } + + 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); + } + } + 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()) +} + +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]>]) -> Result> { + Ok(proof.iter().map(MptNode::decode).collect::, _>>()?) +} + +/// Creates a Merkle Patricia trie from an EIP-1186 proof. +/// For inclusion proofs the returned trie contains exactly one leaf with the value. +pub fn mpt_from_proof(proof_nodes: &[MptNode]) -> 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; + }; + + // the next node must have a digest reference + let MptNodeReference::Digest(ref child_ref) = replacement.reference() else { + panic!("node {} in proof is not referenced by hash", i + 1); + }; + // find the child that references the next node + let resolved: MptNode = match node.as_data().clone() { + MptNodeData::Branch(mut children) => { + if let Some(child) = children.iter_mut().flatten().find( + |child| matches!(child.as_data(), MptNodeData::Digest(d) if d == child_ref), + ) { + *child = Box::new(replacement); + } else { + panic!("node {} does not reference the successor", i); + } + MptNodeData::Branch(children).into() + } + MptNodeData::Extension(prefix, child) => { + if !matches!(child.as_data(), MptNodeData::Digest(d) if d == child_ref) { + panic!("node {} does not reference the successor", i); + } + MptNodeData::Extension(prefix, Box::new(replacement)).into() + } + MptNodeData::Null | MptNodeData::Leaf(_, _) | MptNodeData::Digest(_) => { + panic!("node {} has no children to replace", i); + } + }; + + next = Some(resolved); + } + + // 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]) -> Result { + let proof_trie = mpt_from_proof(proof_nodes).unwrap(); + // for valid proofs, the get must not fail + let value = proof_trie.get(key).unwrap(); + + Ok(value.is_none()) +} + +/// 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 +} + +/// 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::Digest(_) => {} + 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(), + ) + } + } + }; + res +} + +pub fn proofs_to_tries( + state_root: B256, + parent_proofs: &HashMap, + proofs: &HashMap, +) -> Result { + // if no addresses are provided, return the trie only consisting of the state root + if parent_proofs.is_empty() { + return Ok(EthereumState { + state_trie: node_from_digest(state_root), + storage_tries: HashMap::new(), + }); + } + + let mut storage: HashMap = HashMap::with_capacity(parent_proofs.len()); + + let mut state_nodes = HashMap::new(); + let mut state_root_node = MptNode::default(); + for (address, proof) in parent_proofs { + let proof_nodes = parse_proof(&proof.proof).unwrap(); + mpt_from_proof(&proof_nodes).unwrap(); + + // 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 fini_proofs = proofs.get(address).unwrap(); + + // assure that addresses can be deleted from the state trie + add_orphaned_leafs(address, &fini_proofs.proof, &mut state_nodes)?; + + // if no slots are provided, return the trie only consisting of the storage root + let storage_root = proof.storage_root; + if proof.storage_proofs.is_empty() { + let storage_root_node = node_from_digest(storage_root); + storage.insert(B256::from(&keccak(address)), storage_root_node); + continue; + } + + let mut storage_nodes = HashMap::new(); + let mut storage_root_node = MptNode::default(); + for storage_proof in &proof.storage_proofs { + let proof_nodes = parse_proof(&storage_proof.proof).unwrap(); + mpt_from_proof(&proof_nodes).unwrap(); + + // 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 &fini_proofs.storage_proofs { + add_orphaned_leafs(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(), storage_root); + + storage.insert(B256::from(&keccak(address)), storage_trie); + } + let state_trie = resolve_nodes(&state_root_node, &state_nodes); + assert_eq!(state_trie.hash(), state_root); + + Ok(EthereumState { state_trie, storage_tries: storage }) +} + +/// Adds all the leaf nodes of non-inclusion proofs to the nodes. +fn add_orphaned_leafs( + key: impl AsRef<[u8]>, + proof: &[impl AsRef<[u8]>], + nodes_by_reference: &mut HashMap, +) -> Result<()> { + if !proof.is_empty() { + let proof_nodes = parse_proof(proof).context("invalid proof encoding")?; + if is_not_included(&keccak(key), &proof_nodes)? { + // add the leaf node to the nodes + let leaf = proof_nodes.last().unwrap(); + shorten_node_path(leaf).into_iter().for_each(|node| { + nodes_by_reference.insert(node.reference(), node); + }); + } + } + + Ok(()) +} + +/// Creates a new MPT node from a digest. +fn node_from_digest(digest: B256) -> MptNode { + match digest { + EMPTY_ROOT | B256::ZERO => MptNode::default(), + _ => MptNodeData::Digest(digest).into(), + } +} + +#[cfg(test)] +mod tests { + use hex_literal::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 == node.to_rlp().to_vec()) + ); + } + } + + #[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(out).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(out).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 trie = MptNode::decode(trie.to_rlp()).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 decoded = MptNode::decode(trie.to_rlp()).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(&i.to_rlp(), i).unwrap()); + + // check hash against trie build in reverse + let mut reference = MptNode::default(); + for j in (0..=i).rev() { + reference.insert_rlp(&j.to_rlp(), j).unwrap(); + } + assert_eq!(trie.hash(), reference.hash()); + + // try RLP roundtrip + let decoded = MptNode::decode(trie.to_rlp()).unwrap(); + assert_eq!(trie.hash(), decoded.hash()); + } + + // get + for i in 0..N { + assert_eq!(trie.get_rlp(&i.to_rlp()).unwrap(), Some(i)); + assert!(trie.get(&(i + N).to_rlp()).unwrap().is_none()); + } + + // delete + for i in 0..N { + assert!(trie.delete(&i.to_rlp()).unwrap()); + + let mut reference = MptNode::default(); + for j in ((i + 1)..N).rev() { + reference.insert_rlp(&j.to_rlp(), j).unwrap(); + } + assert_eq!(trie.hash(), reference.hash()); + } + assert!(trie.is_empty()); + } +} diff --git a/crates/primitives/src/account_proof.rs b/crates/primitives/src/account_proof.rs index 05df150..4138af3 100644 --- a/crates/primitives/src/account_proof.rs +++ b/crates/primitives/src/account_proof.rs @@ -1,36 +1,6 @@ use alloy_rpc_types::EIP1186AccountProofResponse; -use reth_primitives::{revm_primitives::Bytecode, Account, B256}; +use reth_primitives::Account; use reth_trie::{AccountProof, StorageProof, EMPTY_ROOT_HASH}; -use revm_primitives::keccak256; -use serde::{Deserialize, Serialize}; - -/// The account proof with the bytecode. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct AccountProofWithBytecode { - /// The account proof. - pub proof: AccountProof, - /// The bytecode of the account. - pub code: Bytecode, -} - -impl AccountProofWithBytecode { - pub fn from_eip1186_proof(proof: EIP1186AccountProofResponse, bytecode: Bytecode) -> Self { - Self { proof: eip1186_proof_to_account_proof(proof), code: bytecode } - } - - /// Verifies the account proof against the provided state root. - pub fn verify(&self, state_root: B256) -> eyre::Result<()> { - self.proof - .verify(state_root) - .map_err(|err| eyre::eyre!("Account proof verification failed: {err}"))?; - if let Some(info) = &self.proof.info { - if info.bytecode_hash.unwrap() != keccak256(self.code.bytes()) { - return Err(eyre::eyre!("Code hash does not match the code")); - } - } - Ok(()) - } -} /// Converts an [EIP1186AccountProofResponse] to an [AccountProof]. pub fn eip1186_proof_to_account_proof(proof: EIP1186AccountProofResponse) -> AccountProof { diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 09c2dc6..6dd0820 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -1,3 +1,2 @@ pub mod account_proof; pub mod chain_spec; -pub mod storage; diff --git a/crates/primitives/src/storage.rs b/crates/primitives/src/storage.rs deleted file mode 100644 index 008ca9c..0000000 --- a/crates/primitives/src/storage.rs +++ /dev/null @@ -1,29 +0,0 @@ -use reth_primitives::{Address, Bytes, B256}; -use reth_trie::Nibbles; - -/// Custom database access methods implemented by RSP storage backends. -pub trait ExtDatabaseRef { - /// The database error type. - type Error; - - /// Gets the preimage of a trie node given its Keccak hash. - fn trie_node_ref(&self, hash: B256) -> Result; - - /// Gets the preimage of a trie node given its Keccak hash, with additional context that could - /// be helpful when the program is not running in a constrained environment. - fn trie_node_ref_with_context( - &self, - hash: B256, - context: PreimageContext, - ) -> Result; -} - -/// Additional context for retrieving trie node preimages. These are useful when the JSON-RPC node -/// does not serve the `debug_dbGet`. -pub struct PreimageContext<'a> { - /// The account address if calculating a storage trie root; `None` if calculating the state - /// root. - pub address: &'a Option
, - /// The trie key path of the branch child containing the hash whose preimage is being fetched. - pub branch_path: &'a Nibbles, -} diff --git a/crates/storage/rpc-db/src/lib.rs b/crates/storage/rpc-db/src/lib.rs index 2551644..7eb1064 100644 --- a/crates/storage/rpc-db/src/lib.rs +++ b/crates/storage/rpc-db/src/lib.rs @@ -1,28 +1,21 @@ -use std::{cell::RefCell, iter::once, marker::PhantomData}; +use std::{ + cell::RefCell, + collections::{BTreeMap, BTreeSet}, + marker::PhantomData, +}; use alloy_provider::{network::AnyNetwork, Provider}; use alloy_rpc_types::BlockId; use alloy_transport::Transport; -use futures::future::join_all; -use rayon::prelude::*; use reth_primitives::{ revm_primitives::{AccountInfo, Bytecode}, - Address, Bytes, B256, U256, + Address, B256, U256, }; use reth_revm::DatabaseRef; use reth_storage_errors::{db::DatabaseError, provider::ProviderError}; -use reth_trie::Nibbles; -use revm_primitives::{keccak256, HashMap, HashSet}; -use rsp_primitives::{ - account_proof::AccountProofWithBytecode, - storage::{ExtDatabaseRef, PreimageContext}, -}; +use revm_primitives::HashMap; use rsp_witness_db::WitnessDb; -/// The maximum number of addresses/slots to attempt for brute-forcing the key to be used for -/// fetching trie node preimage via `eth_getProof`. -const BRUTE_FORCE_LIMIT: u64 = 0xffffffff_u64; - /// A database that fetches data from a [Provider] over a [Transport]. #[derive(Debug, Clone)] pub struct RpcDb { @@ -38,8 +31,6 @@ pub struct RpcDb { pub storage: RefCell>>, /// The cached block hashes. pub block_hashes: RefCell>, - /// The cached trie node values. - pub trie_nodes: RefCell>, /// A phantom type to make the struct generic over the transport. pub _phantom: PhantomData, } @@ -65,7 +56,6 @@ impl + Clone> RpcDb { accounts: RefCell::new(HashMap::new()), storage: RefCell::new(HashMap::new()), block_hashes: RefCell::new(HashMap::new()), - trie_nodes: RefCell::new(HashMap::new()), _phantom: PhantomData, } } @@ -148,171 +138,36 @@ impl + Clone> RpcDb { Ok(hash) } - /// Fetch a trie node based on its Keccak hash using the `debug_dbGet` method. - pub async fn fetch_trie_node( - &self, - hash: B256, - context: Option>, - ) -> Result { - tracing::info!("fetching trie node {}", hash); - - // Fetch the trie node value from a geth node with `state.scheme=hash`. - let value = match self.provider.client().request::<_, Bytes>("debug_dbGet", (hash,)).await { - Ok(value) => value, - Err(err) => match context { - Some(context) => { - // The `debug_dbGet` method failed for some reason. Fall back to brute-forcing - // the slot/address needed to recover the preimage via the `eth_getProof` method - // instead. - tracing::debug!( - "failed to fetch preimage from debug_dbGet; \ - falling back to using eth_getProof: address={:?}, prefix={:?}", - context.address, - context.branch_path - ); - - self.fetch_trie_node_via_proof(hash, context).await? - } - None => return Err(RpcDbError::RpcError(err.to_string())), - }, - }; - - // Record the trie node value to the state. - self.trie_nodes.borrow_mut().insert(hash, value.clone()); - - Ok(value) - } - - /// Fetches the [AccountProof] for every account that was used during the lifetime of the - /// [RpcDb]. - pub async fn fetch_used_accounts_and_proofs( - &self, - ) -> HashMap { - tracing::info!("fetching used account proofs"); - - let futures: Vec<_> = { - let accounts = self.accounts.borrow(); - let storage = self.storage.borrow(); - - // Collect all of the addresses we touched. - let mut addresses: HashSet
= accounts.keys().copied().collect(); - addresses.extend(storage.keys()); - - // Create a future for each address to fetch a proof of the account and storage keys. - addresses - .into_iter() - .map(|address| { - // Get all of the storage keys for the address. - let mut storage_keys_for_address: Vec = storage - .get(&address) - .map(|storage_map| storage_map.keys().map(|k| (*k).into()).collect()) - .unwrap_or_default(); - storage_keys_for_address.sort(); - - // Fetch the proof for the address + storage keys. - async move { - loop { - match self - .provider - .get_proof(address, storage_keys_for_address.clone()) - .block_id(self.block) - .await - { - Ok(proof) => break (address, proof), - Err(err) => { - tracing::info!( - "error fetching account proof for {}: {}. Retrying in 1s", - address, - err - ); - tokio::time::sleep(std::time::Duration::from_secs(1)).await - } - } - } - } - }) - .collect() - }; - - // Get the EIP-1186 proofs for the accounts that were touched. - let results = join_all(futures).await; - let eip1186_proofs: Vec<_> = results.into_iter().collect(); - - // Convert the EIP-1186 proofs to [AccountProofWithBytecode]. + /// Gets all the state keys used. The client uses this to read the actual state data from tries. + pub fn get_state_requests(&self) -> HashMap> { let accounts = self.accounts.borrow(); - let account_proofs: HashMap = eip1186_proofs - .into_iter() - .map(|(address, proof)| { - let bytecode = accounts.get(&address).unwrap().code.clone().unwrap(); - let account_proof = AccountProofWithBytecode::from_eip1186_proof(proof, bytecode); - let address: Address = (*address).into(); - (address, account_proof) + let storage = self.storage.borrow(); + + accounts + .keys() + .chain(storage.keys()) + .map(|&address| { + let storage_keys_for_address: BTreeSet = storage + .get(&address) + .map(|storage_map| storage_map.keys().cloned().collect()) + .unwrap_or_default(); + + (address, storage_keys_for_address.into_iter().collect()) }) - .collect(); - - account_proofs + .collect() } - /// Fetches a trie node via `eth_getProof` with a hacky workaround when `debug_dbGet` is not - /// available. - async fn fetch_trie_node_via_proof( - &self, - hash: B256, - context: PreimageContext<'_>, - ) -> Result { - let (address, storage_keys) = match context.address { - Some(address) => { - // Computing storage root. Brute force the slot. - let slot = Self::find_key_preimage::<32>(context.branch_path) - .ok_or(RpcDbError::PreimageNotFound)?; - - (address.to_owned(), vec![slot.into()]) - } - None => { - // Computing state root. Brute force the address. - let address = Self::find_key_preimage::<20>(context.branch_path) - .ok_or(RpcDbError::PreimageNotFound)?; - - (address.into(), vec![]) - } - }; - - let account_proof = self - .provider - .get_proof(address, storage_keys) - .block_id(self.block) - .await - .map_err(|e| RpcDbError::RpcError(e.to_string()))?; - - for proof in account_proof - .storage_proof - .into_iter() - .map(|storage_proof| storage_proof.proof) - .chain(once(account_proof.account_proof)) - { - // The preimage we're looking for is more likely to be at the end of the proof. - for node in proof.into_iter().rev() { - if hash == keccak256(&node) { - return Ok(node) - } - } - } - - Err(RpcDbError::PreimageNotFound) - } - - /// Uses brute force to locate a key path preimage that contains a certain prefix. - fn find_key_preimage(prefix: &Nibbles) -> Option<[u8; BYTES]> { - (0..BRUTE_FORCE_LIMIT).into_par_iter().find_map_any(|nonce| { - let mut buffer = [0u8; BYTES]; - buffer[(BYTES - 8)..].copy_from_slice(&nonce.to_be_bytes()); + /// Gets all account bytecodes. + pub fn get_bytecodes(&self) -> Vec { + let accounts = self.accounts.borrow(); - if Nibbles::unpack(keccak256(buffer)).starts_with(prefix) { - Some(buffer) - } else { - None - } - }) + accounts + .values() + .flat_map(|account| account.code.clone()) + .map(|code| (code.hash_slow(), code)) + .collect::>() + .into_values() + .collect::>() } } @@ -356,45 +211,12 @@ impl + Clone> DatabaseRef for R } } -impl + Clone> ExtDatabaseRef for RpcDb { - type Error = ProviderError; - - fn trie_node_ref(&self, hash: B256) -> Result { - let handle = tokio::runtime::Handle::try_current().map_err(|_| { - ProviderError::Database(DatabaseError::Other("no tokio runtime found".to_string())) - })?; - let result = - tokio::task::block_in_place(|| handle.block_on(self.fetch_trie_node(hash, None))); - let value = - result.map_err(|e| ProviderError::Database(DatabaseError::Other(e.to_string())))?; - Ok(value) - } - - fn trie_node_ref_with_context( - &self, - hash: B256, - context: PreimageContext, - ) -> Result { - let handle = tokio::runtime::Handle::try_current().map_err(|_| { - ProviderError::Database(DatabaseError::Other("no tokio runtime found".to_string())) - })?; - let result = tokio::task::block_in_place(|| { - handle.block_on(self.fetch_trie_node(hash, Some(context))) - }); - let value = - result.map_err(|e| ProviderError::Database(DatabaseError::Other(e.to_string())))?; - Ok(value) - } -} - impl> From> for WitnessDb { fn from(value: RpcDb) -> Self { Self { - state_root: value.state_root, accounts: value.accounts.borrow().clone(), storage: value.storage.borrow().clone(), block_hashes: value.block_hashes.borrow().clone(), - trie_nodes: value.trie_nodes.borrow().clone(), } } } diff --git a/crates/storage/witness-db/src/lib.rs b/crates/storage/witness-db/src/lib.rs index 6e1c4e8..8078701 100644 --- a/crates/storage/witness-db/src/lib.rs +++ b/crates/storage/witness-db/src/lib.rs @@ -1,25 +1,20 @@ use reth_primitives::{ revm_primitives::{db::DatabaseRef, AccountInfo, Bytecode}, - Bytes, B256, + B256, }; use reth_storage_errors::provider::ProviderError; use revm_primitives::{Address, HashMap, U256}; -use rsp_primitives::storage::{ExtDatabaseRef, PreimageContext}; use serde::{Deserialize, Serialize}; /// A database used to witness state inside the zkVM. #[derive(Debug, Serialize, Deserialize)] pub struct WitnessDb { - /// The state root. - pub state_root: B256, /// The accounts. pub accounts: HashMap, /// The storage values, indexed by account address and slot. pub storage: HashMap>, /// The block hashes, indexed by block number. pub block_hashes: HashMap, - /// The trie node preimages, indexed by Keccak hash. - pub trie_nodes: HashMap, } impl DatabaseRef for WitnessDb { @@ -41,20 +36,3 @@ impl DatabaseRef for WitnessDb { Ok(*self.block_hashes.get(&number).unwrap()) } } - -impl ExtDatabaseRef for WitnessDb { - type Error = ProviderError; - - fn trie_node_ref(&self, hash: B256) -> Result { - // TODO: avoid cloning - Ok(self.trie_nodes.get(&hash).unwrap().to_owned()) - } - - fn trie_node_ref_with_context( - &self, - hash: B256, - _context: PreimageContext, - ) -> Result { - self.trie_node_ref(hash) - } -}