Skip to main content

reth_trie/proof_v2/
node.rs

1use crate::proof_v2::DeferredValueEncoder;
2use alloy_rlp::Encodable;
3use reth_execution_errors::trie::StateProofError;
4use reth_trie_common::{
5    BranchNodeMasks, BranchNodeV2, ExtensionNodeRef, LeafNode, LeafNodeRef, Nibbles,
6    ProofTrieNodeV2, RlpNode, TrieMask, TrieNodeV2,
7};
8
9/// A trie node which is the child of a branch in the trie.
10#[derive(Debug)]
11pub(crate) enum ProofTrieBranchChild<RF> {
12    /// A leaf node whose value has yet to be calculated and encoded.
13    Leaf {
14        /// The short key of the leaf.
15        short_key: Nibbles,
16        /// The [`DeferredValueEncoder`] which will encode the leaf's value.
17        value: RF,
18    },
19    /// A branch node whose children have already been flattened into [`RlpNode`]s.
20    Branch {
21        /// The node itself, for use during RLP encoding.
22        node: BranchNodeV2,
23        /// Bitmasks carried over from cached `BranchNodeCompact` values, if any.
24        masks: Option<BranchNodeMasks>,
25    },
26    /// A node whose type is not known, as it has already been converted to an [`RlpNode`].
27    RlpNode {
28        /// The RLP-encoded node.
29        node: RlpNode,
30        /// The path from the parent branch's child nibble to the encoded node. This field can only
31        /// be set if `node` was sourced from the hashes of a cached branch, and therefore we know
32        /// that it is a blinded branch.
33        short_key: Nibbles,
34        /// Whether this node contributes to its parent's hash mask when it is a direct child.
35        hash_mask_bit: bool,
36        /// Whether this node contributes to its parent's tree mask.
37        tree_mask_bit: bool,
38    },
39}
40
41impl<RF: DeferredValueEncoder> ProofTrieBranchChild<RF> {
42    /// Converts this child into its RLP node representation.
43    ///
44    /// This potentially also returns an `RlpNode` buffer which can be re-used for other
45    /// [`ProofTrieBranchChild`]s.
46    pub(crate) fn into_rlp(
47        self,
48        buf: &mut Vec<u8>,
49    ) -> Result<(RlpNode, Option<Vec<RlpNode>>), StateProofError> {
50        match self {
51            Self::Leaf { short_key, value } => {
52                // RLP encode the value itself
53                value.encode(buf)?;
54                let value_enc_len = buf.len();
55
56                // Determine the required buffer size for the encoded leaf
57                let leaf_enc_len = LeafNodeRef::new(&short_key, buf).length();
58
59                // We want to re-use buf for the encoding of the leaf node as well. To do this we
60                // will keep appending to it, leaving the already encoded value in-place. First we
61                // must ensure the buffer is big enough, then we'll split.
62                buf.resize(value_enc_len + leaf_enc_len, 0);
63
64                // SAFETY we have just resized the above to be greater than `value_enc_len`, so it
65                // must be in-bounds.
66                let (value_buf, mut leaf_buf) =
67                    unsafe { buf.split_at_mut_unchecked(value_enc_len) };
68
69                // Encode the leaf into the right side of the split buffer, and return the RlpNode.
70                LeafNodeRef::new(&short_key, value_buf).encode(&mut leaf_buf);
71                Ok((RlpNode::from_rlp(&buf[value_enc_len..]), None))
72            }
73            Self::Branch { node: branch_node, .. } => {
74                branch_node.encode(buf);
75                Ok((RlpNode::from_rlp(buf), Some(branch_node.stack)))
76            }
77            Self::RlpNode { node, short_key, hash_mask_bit, .. } => {
78                if short_key.is_empty() {
79                    return Ok((node, None))
80                }
81
82                // Only branch hashes sourced from a cached hash mask can have an external short
83                // key. Other committed nodes already encode their key internally.
84                debug_assert!(hash_mask_bit);
85                ExtensionNodeRef::new(&short_key, node.as_slice()).encode(buf);
86                Ok((RlpNode::from_rlp(buf), None))
87            }
88        }
89    }
90
91    /// Converts this child into a [`ProofTrieNodeV2`] having the given path.
92    ///
93    /// # Errors
94    ///
95    /// Returns [`StateProofError::TrieInconsistency`] if called on a [`Self::RlpNode`].
96    pub(crate) fn into_proof_trie_node(
97        self,
98        path: Nibbles,
99        buf: &mut Vec<u8>,
100    ) -> Result<ProofTrieNodeV2, StateProofError> {
101        let (node, masks) = match self {
102            Self::Leaf { short_key, value } => {
103                value.encode(buf)?;
104                // Counter-intuitively a clone is better here than a `core::mem::take`. If we take
105                // the buffer then future RLP-encodes will need to re-allocate a new one, and
106                // RLP-encodes after those may need a bigger buffer and therefore re-alloc again.
107                //
108                // By cloning here we do a single allocation of exactly the size we need to take
109                // this value, and the passed in buffer can remain with whatever large capacity it
110                // already has.
111                let rlp_val = buf.clone();
112                (TrieNodeV2::Leaf(LeafNode::new(short_key, rlp_val)), None)
113            }
114            Self::Branch { node, masks } => (TrieNodeV2::Branch(node), masks),
115            // Cached hashes cannot be retained as proof nodes: targeted children are recalculated,
116            // while untargeted children are either combined into a branch or discarded. Reaching
117            // this arm means inconsistent cached trie data left a blinded node as the local root.
118            Self::RlpNode { .. } => {
119                return Err(StateProofError::TrieInconsistency(
120                    "cannot convert RLP node to proof node".to_string(),
121                ))
122            }
123        };
124
125        Ok(ProofTrieNodeV2 { node, path, masks })
126    }
127
128    /// Returns the child's short key.
129    pub(crate) const fn short_key(&self) -> &Nibbles {
130        match self {
131            Self::Leaf { short_key, .. } |
132            Self::Branch { node: BranchNodeV2 { key: short_key, .. }, .. } |
133            Self::RlpNode { short_key, .. } => short_key,
134        }
135    }
136
137    /// Returns this child's hash and tree mask contributions.
138    pub(crate) fn mask_bits(&self) -> (bool, bool) {
139        match self {
140            Self::Leaf { .. } => (false, false),
141            Self::Branch { node, masks } => (
142                node.key.is_empty() && node.length() >= 32,
143                masks.is_some_and(|masks| !masks.is_empty()),
144            ),
145            Self::RlpNode { short_key, hash_mask_bit, tree_mask_bit, .. } => {
146                (*hash_mask_bit && short_key.is_empty(), *tree_mask_bit)
147            }
148        }
149    }
150
151    /// Trims the given number of nibbles off the head of the short key.
152    ///
153    /// # Panics
154    ///
155    /// - If the given len is longer than the short key
156    pub(crate) fn trim_short_key_prefix(&mut self, len: usize) {
157        match self {
158            Self::Leaf { short_key, .. } | Self::RlpNode { short_key, .. } => {
159                *short_key = trim_nibbles_prefix(short_key, len);
160            }
161            Self::Branch { node: BranchNodeV2 { key, branch_rlp_node, .. }, .. } => {
162                *key = trim_nibbles_prefix(key, len);
163                if key.is_empty() {
164                    *branch_rlp_node = None;
165                }
166            }
167        }
168    }
169}
170
171/// A single branch in the trie which is under construction. The actual child nodes of the branch
172/// will be tracked as [`ProofTrieBranchChild`]s on a stack.
173#[derive(Debug)]
174pub(crate) struct ProofTrieBranch {
175    /// The length of the parent extension node's short key. If zero then the branch's parent is
176    /// not an extension but instead another branch.
177    pub(crate) ext_len: u8,
178    /// A mask tracking which child nibbles are set on the branch so far. There will be a single
179    /// child on the stack for each set bit.
180    pub(crate) state_mask: TrieMask,
181}
182
183/// Trims the first `len` nibbles from the head of the given `Nibbles`.
184///
185/// # Panics
186///
187/// Panics if the given `len` is greater than the length of the `Nibbles`.
188pub(crate) fn trim_nibbles_prefix(n: &Nibbles, len: usize) -> Nibbles {
189    debug_assert!(n.len() >= len);
190    n.slice_unchecked(len, n.len())
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn test_trim_nibbles_prefix_basic() {
199        // Create nibbles [1, 2, 3, 4, 5, 6]
200        let nibbles = Nibbles::from_nibbles([1, 2, 3, 4, 5, 6]);
201
202        // Trim first 2 nibbles
203        let trimmed = trim_nibbles_prefix(&nibbles, 2);
204        assert_eq!(trimmed.len(), 4);
205
206        // Verify the remaining nibbles are [3, 4, 5, 6]
207        assert_eq!(trimmed.get(0), Some(3));
208        assert_eq!(trimmed.get(1), Some(4));
209        assert_eq!(trimmed.get(2), Some(5));
210        assert_eq!(trimmed.get(3), Some(6));
211    }
212
213    #[test]
214    fn test_trim_nibbles_prefix_zero() {
215        // Create nibbles [10, 11, 12, 13]
216        let nibbles = Nibbles::from_nibbles([10, 11, 12, 13]);
217
218        // Trim zero nibbles - should return identical nibbles
219        let trimmed = trim_nibbles_prefix(&nibbles, 0);
220        assert_eq!(trimmed, nibbles);
221    }
222
223    #[test]
224    fn test_trim_nibbles_prefix_all() {
225        // Create nibbles [1, 2, 3, 4]
226        let nibbles = Nibbles::from_nibbles([1, 2, 3, 4]);
227
228        // Trim all nibbles - should return empty
229        let trimmed = trim_nibbles_prefix(&nibbles, 4);
230        assert!(trimmed.is_empty());
231    }
232
233    #[test]
234    fn test_trim_nibbles_prefix_empty() {
235        // Create empty nibbles
236        let nibbles = Nibbles::new();
237
238        // Trim zero from empty - should return empty
239        let trimmed = trim_nibbles_prefix(&nibbles, 0);
240        assert!(trimmed.is_empty());
241    }
242}