diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index dc19d02ab..343de1903 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -206,6 +206,78 @@ impl
fmt::Debug for RevealedSparseTrie
{
}
}
+/// Turns a [`Nibbles`] into a [`String`] by concatenating each nibbles' hex character.
+fn encode_nibbles(nibbles: &Nibbles) -> String {
+ let encoded = hex::encode(nibbles.pack());
+ encoded[..nibbles.len()].to_string()
+}
+
+impl {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // This prints the trie in preorder traversal, using a stack
+ let mut stack = Vec::new();
+ let mut visited = HashSet::new();
+
+ // 4 spaces as indent per level
+ const INDENT: &str = " ";
+
+ // Track both path and depth
+ stack.push((Nibbles::default(), self.nodes_ref().get(&Nibbles::default()).unwrap(), 0));
+
+ while let Some((path, node, depth)) = stack.pop() {
+ if !visited.insert(path.clone()) {
+ continue;
+ }
+
+ // Add indentation if alternate flag (#) is set
+ if f.alternate() {
+ write!(f, "{}", INDENT.repeat(depth))?;
+ }
+
+ let packed_path = if depth == 0 { String::from("Root") } else { encode_nibbles(&path) };
+
+ match node {
+ SparseNode::Empty | SparseNode::Hash(_) => {
+ writeln!(f, "{packed_path} -> {node:?}")?;
+ }
+ SparseNode::Leaf { key, .. } => {
+ // we want to append the key to the path
+ let mut full_path = path.clone();
+ full_path.extend_from_slice_unchecked(key);
+ let packed_path = encode_nibbles(&full_path);
+
+ writeln!(f, "{packed_path} -> {node:?}")?;
+ }
+ SparseNode::Extension { key, .. } => {
+ writeln!(f, "{packed_path} -> {node:?}")?;
+
+ // push the child node onto the stack with increased depth
+ let mut child_path = path.clone();
+ child_path.extend_from_slice_unchecked(key);
+ if let Some(child_node) = self.nodes_ref().get(&child_path) {
+ stack.push((child_path, child_node, depth + 1));
+ }
+ }
+ SparseNode::Branch { state_mask, .. } => {
+ writeln!(f, "{packed_path} -> {node:?}")?;
+
+ for i in CHILD_INDEX_RANGE.rev() {
+ if state_mask.is_bit_set(i) {
+ let mut child_path = path.clone();
+ child_path.push_unchecked(i);
+ if let Some(child_node) = self.nodes_ref().get(&child_path) {
+ stack.push((child_path, child_node, depth + 1));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
impl Default for RevealedSparseTrie {
fn default() -> Self {
Self {
@@ -2774,4 +2846,75 @@ mod tests {
assert_eq!(sparse.root(), EMPTY_ROOT_HASH);
}
+
+ #[test]
+ fn sparse_trie_display() {
+ let mut sparse = RevealedSparseTrie::default();
+
+ let value = alloy_rlp::encode_fixed_size(&U256::ZERO).to_vec();
+
+ // Extension (Key = 5) – Level 0
+ // └── Branch (Mask = 1011) – Level 1
+ // ├── 0 -> Extension (Key = 23) – Level 2
+ // │ └── Branch (Mask = 0101) – Level 3
+ // │ ├── 1 -> Leaf (Key = 1, Path = 50231) – Level 4
+ // │ └── 3 -> Leaf (Key = 3, Path = 50233) – Level 4
+ // ├── 2 -> Leaf (Key = 013, Path = 52013) – Level 2
+ // └── 3 -> Branch (Mask = 0101) – Level 2
+ // ├── 1 -> Leaf (Key = 3102, Path = 53102) – Level 3
+ // └── 3 -> Branch (Mask = 1010) – Level 3
+ // ├── 0 -> Leaf (Key = 3302, Path = 53302) – Level 4
+ // └── 2 -> Leaf (Key = 3320, Path = 53320) – Level 4
+ sparse
+ .update_leaf(Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3, 0x1]), value.clone())
+ .unwrap();
+ sparse
+ .update_leaf(Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3, 0x3]), value.clone())
+ .unwrap();
+ sparse
+ .update_leaf(Nibbles::from_nibbles([0x5, 0x2, 0x0, 0x1, 0x3]), value.clone())
+ .unwrap();
+ sparse
+ .update_leaf(Nibbles::from_nibbles([0x5, 0x3, 0x1, 0x0, 0x2]), value.clone())
+ .unwrap();
+ sparse
+ .update_leaf(Nibbles::from_nibbles([0x5, 0x3, 0x3, 0x0, 0x2]), value.clone())
+ .unwrap();
+ sparse.update_leaf(Nibbles::from_nibbles([0x5, 0x3, 0x3, 0x2, 0x0]), value).unwrap();
+
+ let normal_printed = format!("{sparse}");
+ let expected = "\
+Root -> Extension { key: Nibbles(0x05), hash: None, store_in_db_trie: None }
+5 -> Branch { state_mask: TrieMask(0000000000001101), hash: None, store_in_db_trie: None }
+50 -> Extension { key: Nibbles(0x0203), hash: None, store_in_db_trie: None }
+5023 -> Branch { state_mask: TrieMask(0000000000001010), hash: None, store_in_db_trie: None }
+50231 -> Leaf { key: Nibbles(0x), hash: None }
+50233 -> Leaf { key: Nibbles(0x), hash: None }
+52013 -> Leaf { key: Nibbles(0x000103), hash: None }
+53 -> Branch { state_mask: TrieMask(0000000000001010), hash: None, store_in_db_trie: None }
+53102 -> Leaf { key: Nibbles(0x0002), hash: None }
+533 -> Branch { state_mask: TrieMask(0000000000000101), hash: None, store_in_db_trie: None }
+53302 -> Leaf { key: Nibbles(0x02), hash: None }
+53320 -> Leaf { key: Nibbles(0x00), hash: None }
+";
+ assert_eq!(normal_printed, expected);
+
+ let alternate_printed = format!("{sparse:#}");
+ let expected = "\
+Root -> Extension { key: Nibbles(0x05), hash: None, store_in_db_trie: None }
+ 5 -> Branch { state_mask: TrieMask(0000000000001101), hash: None, store_in_db_trie: None }
+ 50 -> Extension { key: Nibbles(0x0203), hash: None, store_in_db_trie: None }
+ 5023 -> Branch { state_mask: TrieMask(0000000000001010), hash: None, store_in_db_trie: None }
+ 50231 -> Leaf { key: Nibbles(0x), hash: None }
+ 50233 -> Leaf { key: Nibbles(0x), hash: None }
+ 52013 -> Leaf { key: Nibbles(0x000103), hash: None }
+ 53 -> Branch { state_mask: TrieMask(0000000000001010), hash: None, store_in_db_trie: None }
+ 53102 -> Leaf { key: Nibbles(0x0002), hash: None }
+ 533 -> Branch { state_mask: TrieMask(0000000000000101), hash: None, store_in_db_trie: None }
+ 53302 -> Leaf { key: Nibbles(0x02), hash: None }
+ 53320 -> Leaf { key: Nibbles(0x00), hash: None }
+";
+
+ assert_eq!(alternate_printed, expected);
+ }
}