Skip to main content

reth_trie_sparse/
debug_recorder.rs

1//! Debug recorder for tracking mutating operations on sparse tries.
2//!
3//! This module is only available with the `trie-debug` feature and provides
4//! infrastructure for recording all mutations to a [`crate::ParallelSparseTrie`]
5//! for post-hoc debugging of state root mismatches.
6
7use alloc::{string::String, vec::Vec};
8use alloy_primitives::{hex, Bytes, B256};
9use alloy_trie::nodes::TrieNode;
10use reth_trie_common::Nibbles;
11use serde::Serialize;
12
13/// Records mutating operations performed on a sparse trie in the order they occurred.
14#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
15pub struct TrieDebugRecorder {
16    ops: Vec<RecordedOp>,
17}
18
19impl TrieDebugRecorder {
20    /// Creates a new empty recorder.
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Clears all recorded operations.
26    pub fn reset(&mut self) {
27        self.ops.clear();
28    }
29
30    /// Records a single operation.
31    pub fn record(&mut self, op: RecordedOp) {
32        self.ops.push(op);
33    }
34
35    /// Returns a reference to the recorded operations.
36    pub fn ops(&self) -> &[RecordedOp] {
37        &self.ops
38    }
39
40    /// Takes and returns the recorded operations, leaving the recorder empty.
41    pub fn take_ops(&mut self) -> Vec<RecordedOp> {
42        core::mem::take(&mut self.ops)
43    }
44
45    /// Returns `true` if no operations have been recorded.
46    pub const fn is_empty(&self) -> bool {
47        self.ops.is_empty()
48    }
49}
50
51/// A mutating operation recorded from a sparse trie.
52#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
53pub enum RecordedOp {
54    /// Records a `reveal_nodes` call with the nodes that were revealed.
55    RevealNodes {
56        /// The proof trie nodes that were revealed.
57        nodes: Vec<ProofTrieNodeRecord>,
58    },
59    /// Records an `update_leaves` call with the leaf updates.
60    UpdateLeaves {
61        /// The leaf updates that were applied.
62        updates: Vec<(B256, LeafUpdateRecord)>,
63        /// Keys remaining in the updates map after the call (i.e. those that could not be applied
64        /// due to blinded nodes).
65        remaining_keys: Vec<B256>,
66        /// Proof targets returned via the callback as `(key, min_len)` pairs.
67        proof_targets: Vec<(B256, u8)>,
68    },
69    /// Records an `update_subtrie_hashes` call.
70    UpdateSubtrieHashes,
71    /// Records a `root()` call.
72    Root,
73    /// Records a `set_root` call with the root node that was set.
74    SetRoot {
75        /// The root trie node that was set.
76        node: ProofTrieNodeRecord,
77    },
78}
79
80/// A serializable record of a proof trie node.
81#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
82pub struct ProofTrieNodeRecord {
83    /// The nibble path of the node.
84    pub path: Nibbles,
85    /// The trie node.
86    pub node: TrieNodeRecord,
87    /// The branch node masks `(hash_mask, tree_mask)` stored as raw `u16` values, if present.
88    pub masks: Option<(u16, u16)>,
89}
90
91impl ProofTrieNodeRecord {
92    /// Creates a record from a [`reth_trie_common::ProofTrieNode`].
93    pub fn from_proof_trie_node(node: &reth_trie_common::ProofTrieNode) -> Self {
94        Self {
95            path: node.path,
96            node: TrieNodeRecord(node.node.clone()),
97            masks: node.masks.map(|masks| (masks.hash_mask.get(), masks.tree_mask.get())),
98        }
99    }
100
101    /// Creates a record from a [`reth_trie_common::ProofTrieNodeV2`].
102    pub fn from_proof_trie_node_v2(node: &reth_trie_common::ProofTrieNodeV2) -> Self {
103        use reth_trie_common::TrieNodeV2;
104        let trie_node = match &node.node {
105            TrieNodeV2::EmptyRoot => TrieNode::EmptyRoot,
106            TrieNodeV2::Leaf(leaf) => TrieNode::Leaf(leaf.clone()),
107            TrieNodeV2::Extension(ext) => TrieNode::Extension(ext.clone()),
108            TrieNodeV2::Branch(branch) => TrieNode::Branch(alloy_trie::nodes::BranchNode::new(
109                branch.stack.clone(),
110                branch.state_mask,
111            )),
112        };
113        Self {
114            path: node.path,
115            node: TrieNodeRecord(trie_node),
116            masks: node.masks.map(|masks| (masks.hash_mask.get(), masks.tree_mask.get())),
117        }
118    }
119}
120
121/// A newtype wrapper around [`TrieNode`] with custom serialization that hex-encodes byte fields
122/// (leaf values, branch stack entries, extension child pointers) instead of serializing them as
123/// raw integer arrays.
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct TrieNodeRecord(pub TrieNode);
126
127impl From<TrieNode> for TrieNodeRecord {
128    fn from(node: TrieNode) -> Self {
129        Self(node)
130    }
131}
132
133impl Serialize for TrieNodeRecord {
134    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
135        use serde::ser::SerializeStructVariant;
136        match &self.0 {
137            TrieNode::EmptyRoot => serializer.serialize_unit_variant("TrieNode", 0, "EmptyRoot"),
138            TrieNode::Branch(branch) => {
139                let stack_hex: Vec<String> =
140                    branch.stack.iter().map(|n| hex::encode(n.as_ref())).collect();
141                let mut sv = serializer.serialize_struct_variant("TrieNode", 1, "Branch", 2)?;
142                sv.serialize_field("stack", &stack_hex)?;
143                sv.serialize_field("state_mask", &branch.state_mask.get())?;
144                sv.end()
145            }
146            TrieNode::Extension(ext) => {
147                let mut sv = serializer.serialize_struct_variant("TrieNode", 2, "Extension", 2)?;
148                sv.serialize_field("key", &ext.key)?;
149                sv.serialize_field("child", &hex::encode(ext.child.as_ref()))?;
150                sv.end()
151            }
152            TrieNode::Leaf(leaf) => {
153                let mut sv = serializer.serialize_struct_variant("TrieNode", 3, "Leaf", 2)?;
154                sv.serialize_field("key", &leaf.key)?;
155                sv.serialize_field("value", &hex::encode(&leaf.value))?;
156                sv.end()
157            }
158        }
159    }
160}
161
162/// A serializable record of a leaf update, mirroring [`crate::LeafUpdate`].
163#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
164pub enum LeafUpdateRecord {
165    /// The leaf value was changed to the given RLP-encoded value.
166    Changed(Bytes),
167    /// The leaf value was touched but the new value is not yet known.
168    Touched,
169}
170
171impl From<&crate::LeafUpdate> for LeafUpdateRecord {
172    fn from(update: &crate::LeafUpdate) -> Self {
173        match update {
174            crate::LeafUpdate::Changed(value) => Self::Changed(value.clone().into()),
175            crate::LeafUpdate::Touched => Self::Touched,
176        }
177    }
178}