Skip to main content

reth_trie_common/hash_builder/
state.rs

1use crate::TrieMask;
2use alloc::vec::Vec;
3use alloy_trie::{hash_builder::HashBuilderValue, nodes::RlpNode, HashBuilder};
4use nybbles::Nibbles;
5
6/// The hash builder state for storing in the database.
7/// Check the `reth-trie` crate for more info on hash builder.
8#[derive(Debug, Clone, PartialEq, Eq, Default)]
9#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
10#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
11pub struct HashBuilderState {
12    /// The current key.
13    pub key: Vec<u8>,
14    /// The current node value.
15    pub value: HashBuilderValue,
16    /// The builder stack.
17    pub stack: Vec<RlpNode>,
18
19    /// Group masks.
20    pub groups: Vec<TrieMask>,
21    /// Tree masks.
22    pub tree_masks: Vec<TrieMask>,
23    /// Hash masks.
24    pub hash_masks: Vec<TrieMask>,
25
26    /// Flag indicating if the current node is stored in the database.
27    pub stored_in_database: bool,
28}
29
30impl From<HashBuilderState> for HashBuilder {
31    fn from(state: HashBuilderState) -> Self {
32        Self {
33            key: Nibbles::from_nibbles_unchecked(state.key),
34            stack: state.stack,
35            value: state.value,
36            state_masks: state.groups,
37            tree_masks: state.tree_masks,
38            hash_masks: state.hash_masks,
39            stored_in_database: state.stored_in_database,
40            updated_branch_nodes: None,
41            proof_retainer: None,
42            rlp_buf: Vec::with_capacity(32),
43            all_branch_nodes: false,
44        }
45    }
46}
47
48impl From<HashBuilder> for HashBuilderState {
49    fn from(state: HashBuilder) -> Self {
50        Self {
51            key: state.key.to_vec(),
52            stack: state.stack,
53            value: state.value,
54            groups: state.state_masks,
55            tree_masks: state.tree_masks,
56            hash_masks: state.hash_masks,
57            stored_in_database: state.stored_in_database,
58        }
59    }
60}
61
62#[cfg(any(test, feature = "reth-codec"))]
63impl reth_codecs::Compact for HashBuilderState {
64    fn to_compact<B>(&self, buf: &mut B) -> usize
65    where
66        B: bytes::BufMut + AsMut<[u8]>,
67    {
68        let mut len = 0;
69
70        // Collection codecs return flag bits, not their encoded byte length.
71        let start = buf.remaining_mut();
72        self.key.to_compact(buf);
73        len += start - buf.remaining_mut();
74
75        buf.put_u16(self.stack.len() as u16);
76        len += 2;
77        for item in &self.stack {
78            buf.put_u16(item.len() as u16);
79            buf.put_slice(&item[..]);
80            len += 2 + item.len();
81        }
82
83        let start = buf.remaining_mut();
84        self.value.to_compact(buf);
85        len += start - buf.remaining_mut();
86
87        buf.put_u16(self.groups.len() as u16);
88        len += 2;
89        for item in &self.groups {
90            len += (*item).to_compact(buf);
91        }
92
93        buf.put_u16(self.tree_masks.len() as u16);
94        len += 2;
95        for item in &self.tree_masks {
96            len += (*item).to_compact(buf);
97        }
98
99        buf.put_u16(self.hash_masks.len() as u16);
100        len += 2;
101        for item in &self.hash_masks {
102            len += (*item).to_compact(buf);
103        }
104
105        buf.put_u8(self.stored_in_database as u8);
106        len += 1;
107        len
108    }
109
110    fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) {
111        use bytes::Buf;
112
113        let (key, mut buf) = Vec::from_compact(buf, 0);
114
115        let stack_len = buf.get_u16() as usize;
116        let mut stack = Vec::with_capacity(stack_len);
117        for _ in 0..stack_len {
118            let item_len = buf.get_u16() as usize;
119            stack.push(RlpNode::from_raw(&buf[..item_len]).unwrap());
120            buf.advance(item_len);
121        }
122
123        let (value, mut buf) = HashBuilderValue::from_compact(buf, 0);
124
125        let groups_len = buf.get_u16() as usize;
126        let mut groups = Vec::with_capacity(groups_len);
127        for _ in 0..groups_len {
128            let (item, rest) = TrieMask::from_compact(buf, 0);
129            groups.push(item);
130            buf = rest;
131        }
132
133        let tree_masks_len = buf.get_u16() as usize;
134        let mut tree_masks = Vec::with_capacity(tree_masks_len);
135        for _ in 0..tree_masks_len {
136            let (item, rest) = TrieMask::from_compact(buf, 0);
137            tree_masks.push(item);
138            buf = rest;
139        }
140
141        let hash_masks_len = buf.get_u16() as usize;
142        let mut hash_masks = Vec::with_capacity(hash_masks_len);
143        for _ in 0..hash_masks_len {
144            let (item, rest) = TrieMask::from_compact(buf, 0);
145            hash_masks.push(item);
146            buf = rest;
147        }
148
149        let stored_in_database = buf.get_u8() != 0;
150        (Self { key, stack, value, groups, tree_masks, hash_masks, stored_in_database }, buf)
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    #[cfg(feature = "arbitrary")]
158    use proptest::{collection::vec, prelude::*};
159    use reth_codecs::Compact;
160
161    #[test]
162    fn hash_builder_state_regression() {
163        let mut state = HashBuilderState::default();
164        state.stack.push(Default::default());
165        let mut buf = vec![];
166        let len = state.clone().to_compact(&mut buf);
167        let (decoded, _) = HashBuilderState::from_compact(&buf, len);
168        assert_eq!(state, decoded);
169    }
170
171    #[test]
172    fn hash_builder_state_fixed_slice() {
173        let mut state = HashBuilderState { key: vec![1, 2, 3], ..Default::default() };
174        state.value.set_bytes_owned(vec![42; 128]);
175        let mut expected = vec![];
176        assert_eq!(state.to_compact(&mut expected), expected.len());
177
178        let mut storage = vec![0; expected.len() + 1];
179        let mut remaining = storage.as_mut_slice();
180        let len = state.to_compact(&mut remaining);
181        assert_eq!(len, expected.len());
182        assert_eq!(remaining.len(), 1);
183        assert_eq!(&storage[..len], expected);
184        let (decoded, rest) = HashBuilderState::from_compact(&storage[..len], len);
185        assert_eq!(decoded, state);
186        assert!(rest.is_empty());
187    }
188
189    // Generate fields directly: the arbitrary interop's fixed byte buffer can run out while
190    // generating variable-length fields, aborting the test before checking the codec.
191    #[cfg(feature = "arbitrary")]
192    proptest::proptest! {
193        #[test]
194        fn hash_builder_state_roundtrip(
195            key in vec(0u8..16, 0..=64),
196            value in any::<HashBuilderValue>(),
197            stack in vec(any::<RlpNode>(), 0..=64),
198            groups in vec(any::<TrieMask>(), 0..=64),
199            tree_masks in vec(any::<TrieMask>(), 0..=64),
200            hash_masks in vec(any::<TrieMask>(), 0..=64),
201            stored_in_database in any::<bool>(),
202        ) {
203            let state = HashBuilderState {
204                key, value, stack, groups, tree_masks, hash_masks, stored_in_database,
205            };
206            let mut buf = vec![];
207            let len = state.to_compact(&mut buf);
208            prop_assert_eq!(len, buf.len());
209            let (decoded, rest) = HashBuilderState::from_compact(&buf, len);
210            prop_assert_eq!(state, decoded);
211            prop_assert!(rest.is_empty());
212        }
213    }
214}