Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
56 changes: 56 additions & 0 deletions crates/common/trie/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub enum InconsistentTreeError {
RootNotFound(H256),
#[error("Root node not found")]
RootNotFoundNoHash,
#[error("Node count mismatch during validation: {0}")]
NodeCountMismatch(NodeCountMismatchData),
}

#[derive(Debug)]
Expand All @@ -52,3 +54,57 @@ impl std::fmt::Display for ExtensionNodeErrorData {
)
}
}

#[derive(Debug, Clone, Copy)]
pub enum NodeType {
Branch,
Extension,
Leaf,
}

impl std::fmt::Display for NodeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NodeType::Branch => write!(f, "Branch"),
NodeType::Extension => write!(f, "Extension"),
NodeType::Leaf => write!(f, "Leaf"),
}
}
}

#[derive(Debug)]
pub struct NodeCountMismatchData {
/// The count mismatch: positive = missing nodes, negative = extra nodes
pub expected_count: isize,
/// Path to the last successfully validated node
pub last_valid_path: Option<Nibbles>,
pub last_node_type: Option<NodeType>,
pub nodes_traversed: usize,
/// Hash of the last validated node (for cross-referencing)
pub last_node_hash: Option<H256>,
}

impl std::fmt::Display for NodeCountMismatchData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mismatch = if self.expected_count > 0 {
format!("Expected {} more node(s)", self.expected_count)
} else {
format!("Found {} unexpected node(s)", self.expected_count.abs())
};
let node_type = self
.last_node_type
.map(|t| t.to_string())
.unwrap_or_else(|| "unknown".to_string());
let hash = self
.last_node_hash
.as_ref()
.map(|h| format!("{:#x}", h))
.unwrap_or_else(|| "unknown".to_string());

write!(
f,
"{}. Traversed {} nodes. Last valid node: {} at path {:?} (hash: {})",
mismatch, self.nodes_traversed, node_type, self.last_valid_path, hash
)
}
}
53 changes: 45 additions & 8 deletions crates/common/trie/trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ pub use self::{
node_hash::NodeHash,
};

pub use self::error::{ExtensionNodeErrorData, InconsistentTreeError, TrieError};
pub use self::error::{
ExtensionNodeErrorData, InconsistentTreeError, NodeCountMismatchData, NodeType, TrieError,
};
use self::{node::LeafNode, trie_iter::TrieIterator};

use ethrex_rlp::decode::RLPDecode;
Expand Down Expand Up @@ -533,29 +535,64 @@ impl Trie {
///
/// This is used internally with debug assertions to check the status of the trie
/// after syncing operations.
/// Note: this operation validates the hashes because the iterator uses
/// get_node_checked. We shouldn't downgrade that to the unchecked version
///
/// # Returns
///
/// Returns `Ok(())` if the trie structure is consistent.
///
/// Returns `Err(TrieError::InconsistentTree(NodeCountMismatch))` if there is a
/// mismatch between expected and actual node counts. The error includes diagnostic
/// information: path to the last validated node, node type, hash, and traversal count.
///
/// # Note
///
/// This operation validates the hashes because the iterator uses
/// `get_node_checked`. We shouldn't downgrade that to the unchecked version.
pub fn validate(self) -> Result<(), TrieError> {
let mut expected_count = if self.root.is_valid() { 1 } else { 0 };
for (_, node) in self.into_iter() {
let mut expected_count: isize = if self.root.is_valid() { 1 } else { 0 };

// Track context for error reporting
let mut last_valid_path: Option<Nibbles> = None;
let mut last_node_type: Option<NodeType> = None;
let mut last_node_hash: Option<H256> = None;
let mut nodes_traversed = 0;

for (path, node) in self.into_iter() {
// Capture context before processing this node
last_valid_path = Some(path.clone());
last_node_type = Some(match &node {
Node::Branch(_) => NodeType::Branch,
Node::Extension(_) => NodeType::Extension,
Node::Leaf(_) => NodeType::Leaf,
});
last_node_hash = Some(node.compute_hash().finalize());
nodes_traversed += 1;

expected_count -= 1;
match node {
Node::Branch(branch_node) => {
expected_count += branch_node
.choices
.iter()
.filter(|child| child.is_valid())
.count();
.count() as isize;
}
Node::Extension(_) => {
expected_count += 1;
}
Node::Leaf(_) => {}
}
}

if expected_count != 0 {
return Err(TrieError::Verify(format!(
"Node count mismatch, expected {expected_count} more"
return Err(TrieError::InconsistentTree(Box::new(
InconsistentTreeError::NodeCountMismatch(NodeCountMismatchData {
expected_count,
last_valid_path,
last_node_type,
nodes_traversed,
last_node_hash,
}),
)));
}
Ok(())
Expand Down
Loading