Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions crates/common/trie/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ mod leaf;

use std::{
array,
collections::BTreeMap,
sync::{Arc, OnceLock},
};

pub use branch::BranchNode;
use ethereum_types::H256;
use ethrex_rlp::{
decode::{RLPDecode, decode_bytes},
encode::RLPEncode,
Expand Down Expand Up @@ -89,6 +91,55 @@ impl NodeRef {
NodeRef::Hash(hash) => *hash,
}
}

pub fn compute_hash_finalized(&mut self) -> &H256 {
let node_hash = match self {
NodeRef::Node(node, hash) => {
// uses get_or_init() instead of set() because the latter doesn't have lazy eval.
hash.get_or_init(|| node.compute_hash());
hash.get_mut().unwrap()
}
NodeRef::Hash(hash) => hash,
};
node_hash.finalize_mut()
}

pub fn resolve_subtrie(
&mut self,
all_nodes: &BTreeMap<H256, Vec<u8>>,
) -> Result<(), TrieError> {
let finalized_hash = self.compute_hash_finalized();

let mut decoded_node = match all_nodes.get(finalized_hash) {
Some(rlp) => Node::decode_raw(rlp)?,
None => return Ok(()),
};

match &mut decoded_node {
Node::Branch(node) => {
for choice in &mut node.choices {
let NodeRef::Hash(hash) = choice else {
unreachable!()
};

if hash.is_valid() {
choice.resolve_subtrie(all_nodes)?;
}
}
}
Node::Extension(node) => {
let NodeRef::Hash(_) = node.child else {
unreachable!()
};

node.child.resolve_subtrie(all_nodes)?;
}
Node::Leaf(_) => {}
};

*self = decoded_node.into();
Ok(())
}
}

impl Default for NodeRef {
Expand Down
18 changes: 18 additions & 0 deletions crates/common/trie/node_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@ impl NodeHash {
}
}

/// Returns a reference the finalized hash, hashing `self` if it's `NodeHash::Inline`, and storing the result by
/// changing it to the `NodeHash::Hashed` variant.
pub fn finalize_mut(&mut self) -> &H256 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function leaves the tree in an inconsistent state, right? If any function expects compute_hash to return the node's MPT hash, they'll receive an incorrect result. We should document that in a big warning message to avoid weird errors going forward.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean if there's a function that expects the node's RLP encoding (in the case it's <32 bytes) instead of its hash? Which caller could assume that? and also, how could that leave a trie in an inconsistent state?

if let NodeHash::Inline(_) = self {
let hash = H256::from_slice(
Keccak256::new()
.chain_update(self.as_ref())
.finalize()
.as_slice(),
);
*self = NodeHash::Hashed(hash);
}
let NodeHash::Hashed(hash_ref) = self else {
unreachable!();
};
hash_ref
}

/// Returns true if the hash is valid
/// The hash will only be considered invalid if it is empty
/// Aka if it has a default value instead of being a product of hash computation
Expand Down
49 changes: 3 additions & 46 deletions crates/common/trie/trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,52 +292,9 @@ impl Trie {
if root_hash == *EMPTY_TRIE_HASH {
return Ok(NodeRef::default());
}

let root_rlp = all_nodes.get(&root_hash).ok_or_else(|| {
TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(root_hash)))
})?;

fn get_embedded_node(
all_nodes: &BTreeMap<H256, Vec<u8>>,
cur_node_rlp: &[u8],
) -> Result<Node, TrieError> {
let cur_node = Node::decode_raw(cur_node_rlp)?;

Ok(match cur_node {
Node::Branch(mut node) => {
for choice in &mut node.choices {
let NodeRef::Hash(hash) = *choice else {
unreachable!()
};

if hash.is_valid() {
*choice = match all_nodes.get(&hash.finalize()) {
Some(rlp) => get_embedded_node(all_nodes, rlp)?.into(),
None => hash.into(),
};
}
}

(*node).into()
}
Node::Extension(mut node) => {
let NodeRef::Hash(hash) = node.child else {
unreachable!()
};

node.child = match all_nodes.get(&hash.finalize()) {
Some(rlp) => get_embedded_node(all_nodes, rlp)?.into(),
None => hash.into(),
};

node.into()
}
Node::Leaf(node) => node.into(),
})
}

let root = get_embedded_node(all_nodes, root_rlp)?;
Ok(root.into())
let mut root = NodeRef::Hash(root_hash.into());
root.resolve_subtrie(all_nodes)?;
Ok(root)
}

/// Builds a trie from a set of nodes with an empty InMemoryTrieDB as a backend because the nodes are embedded in the root.
Expand Down
Loading