Skip to main content

ethrex_trie/
node.rs

1mod branch;
2mod extension;
3mod leaf;
4
5use alloc::sync::Arc;
6#[cfg(not(feature = "std"))]
7use alloc::{boxed::Box, vec::Vec};
8#[cfg(feature = "std")]
9pub use std::sync::OnceLock;
10
11/// Non-atomic `OnceLock` replacement used on `no_std` builds — which include the
12/// single-threaded zkVM guest (see the crate's dependency wiring in
13/// `ethrex-common`, which builds this crate without the `std` feature).
14///
15/// `std::sync::OnceLock`'s atomics are pure overhead in the single-threaded zkVM
16/// guest, so this mirrors `once_cell::unsync::OnceCell`: interior mutability
17/// through a plain `UnsafeCell`, no atomics.
18///
19/// It is deliberately **not** `Sync` — there is no `unsafe impl Sync`, so the
20/// `UnsafeCell` makes the type `!Sync` and the compiler forbids sharing it across
21/// threads. That keeps it sound without assuming `no_std` implies a single thread:
22/// a multi-threaded `no_std` consumer fails to compile instead of racing on the
23/// cell.
24#[cfg(not(feature = "std"))]
25pub struct OnceLock<T>(core::cell::UnsafeCell<Option<T>>);
26
27#[cfg(not(feature = "std"))]
28impl<T> OnceLock<T> {
29    #[inline]
30    fn new() -> Self {
31        Self(core::cell::UnsafeCell::new(None))
32    }
33
34    #[inline]
35    fn get(&self) -> Option<&T> {
36        unsafe { &*self.0.get() }.as_ref()
37    }
38
39    #[inline]
40    fn get_or_init(&self, f: impl FnOnce() -> T) -> &T {
41        match self.get_or_try_init(|| Ok::<T, core::convert::Infallible>(f())) {
42            Ok(val) => val,
43            Err(e) => match e {},
44        }
45    }
46
47    #[inline]
48    fn get_or_try_init<E>(&self, f: impl FnOnce() -> Result<T, E>) -> Result<&T, E> {
49        if let Some(val) = self.get() {
50            return Ok(val);
51        }
52        self.try_init(f)
53    }
54
55    #[inline]
56    fn set(&self, value: T) -> Result<(), T> {
57        match self.try_insert(value) {
58            Ok(_) => Ok(()),
59            Err((_, value)) => Err(value),
60        }
61    }
62
63    #[inline]
64    fn try_insert(&self, value: T) -> Result<&T, (&T, T)> {
65        if let Some(old) = self.get() {
66            return Err((old, value));
67        }
68        let slot = unsafe { &mut *self.0.get() };
69        Ok(slot.insert(value))
70    }
71
72    #[inline]
73    fn try_init<E>(&self, f: impl FnOnce() -> Result<T, E>) -> Result<&T, E> {
74        let val = f()?;
75        let slot = unsafe { &mut *self.0.get() };
76        debug_assert!(slot.is_none());
77        Ok(slot.insert(val))
78    }
79
80    #[inline]
81    fn take(&mut self) -> Option<T> {
82        self.0.get_mut().take()
83    }
84}
85
86#[cfg(not(feature = "std"))]
87impl<T: PartialEq> PartialEq for OnceLock<T> {
88    #[inline]
89    fn eq(&self, other: &Self) -> bool {
90        self.get() == other.get()
91    }
92}
93
94#[cfg(not(feature = "std"))]
95impl<T> Default for OnceLock<T> {
96    #[inline]
97    fn default() -> Self {
98        Self::new()
99    }
100}
101
102#[cfg(not(feature = "std"))]
103impl<T: Eq> Eq for OnceLock<T> {}
104
105#[cfg(not(feature = "std"))]
106impl<T: Clone> Clone for OnceLock<T> {
107    #[inline]
108    fn clone(&self) -> OnceLock<T> {
109        match self.get() {
110            Some(value) => OnceLock::from(value.clone()),
111            None => OnceLock::new(),
112        }
113    }
114}
115
116#[cfg(not(feature = "std"))]
117impl<T: core::fmt::Debug> core::fmt::Debug for OnceLock<T> {
118    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119        let mut d = f.debug_tuple("OnceLock");
120        match self.get() {
121            Some(v) => d.field(v),
122            None => d.field(&format_args!("<uninit>")),
123        };
124        d.finish()
125    }
126}
127
128#[cfg(not(feature = "std"))]
129impl<T> From<T> for OnceLock<T> {
130    #[inline]
131    fn from(value: T) -> Self {
132        OnceLock(core::cell::UnsafeCell::new(Some(value)))
133    }
134}
135
136pub use branch::BranchNode;
137use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode};
138pub use extension::ExtensionNode;
139pub use leaf::LeafNode;
140use rkyv::{
141    de::Pooling,
142    rancor::Source,
143    ser::{Allocator, Sharing, Writer},
144    validation::{ArchiveContext, SharedContext},
145    with::Skip,
146};
147
148use ethrex_crypto::{Crypto, NativeCrypto};
149
150use crate::{NodeRLP, TrieDB, error::TrieError, nibbles::Nibbles};
151
152use super::{ValueRLP, node_hash::NodeHash};
153
154/// A reference to a node.
155///
156/// Explicit rkyv bounds are needed because this is a recursive type, whose
157/// bounds can't be automatically resolved.
158#[derive(
159    Clone,
160    Debug,
161    serde::Serialize,
162    serde::Deserialize,
163    rkyv::Serialize,
164    rkyv::Deserialize,
165    rkyv::Archive,
166)]
167#[rkyv(serialize_bounds(__S: Writer + Allocator + Sharing, __S::Error: Source))]
168#[rkyv(deserialize_bounds(__D: Pooling, __D::Error: Source))]
169#[rkyv(bytecheck(bounds(__C: ArchiveContext + SharedContext)))]
170pub enum NodeRef {
171    /// The node is embedded within the reference.
172    Node(
173        #[rkyv(omit_bounds)] Arc<Node>,
174        #[rkyv(with = Skip)]
175        #[serde(skip)]
176        OnceLock<NodeHash>,
177    ),
178    /// The node is in the database, referenced by its hash.
179    Hash(NodeHash),
180}
181
182impl NodeRef {
183    /// Gets a shared reference to the inner node.
184    /// Requires that the trie is in a consistent state, ie that all leaves being pointed are in the database.
185    /// Outside of snapsync this should always be the case.
186    pub fn get_node(&self, db: &dyn TrieDB, path: Nibbles) -> Result<Option<Arc<Node>>, TrieError> {
187        match self {
188            NodeRef::Node(node, _) => Ok(Some(node.clone())),
189            NodeRef::Hash(hash @ NodeHash::Inline(_)) => {
190                Ok(Some(Arc::new(Node::decode(hash.as_ref())?)))
191            }
192            NodeRef::Hash(_) => db
193                .get(path)?
194                .filter(|rlp| !rlp.is_empty())
195                .map(|rlp| Ok(Arc::new(Node::decode(&rlp)?)))
196                .transpose(),
197        }
198    }
199
200    /// Gets a shared reference to the inner node, checking its hash.
201    /// Returns `Ok(None)` if the hash is invalid.
202    ///
203    /// Uses `NativeCrypto` directly because this function is only reachable from
204    /// native storage/sync paths (`get_root_node`, `get_proof`, `validate`,
205    /// `verify_range`, trie iterator) — never from the guest program path, which
206    /// traverses via `Node::get()`.
207    pub fn get_node_checked(
208        &self,
209        db: &dyn TrieDB,
210        path: Nibbles,
211    ) -> Result<Option<Arc<Node>>, TrieError> {
212        match self {
213            NodeRef::Node(node, _) => Ok(Some(node.clone())),
214            NodeRef::Hash(hash @ NodeHash::Inline(_)) => {
215                Ok(Some(Arc::new(Node::decode(hash.as_ref())?)))
216            }
217            NodeRef::Hash(hash @ NodeHash::Hashed(_)) => {
218                db.get(path)?
219                    .filter(|rlp| !rlp.is_empty())
220                    .and_then(|rlp| match Node::decode(&rlp) {
221                        Ok(node) => (node.compute_hash(&NativeCrypto) == *hash)
222                            .then_some(Ok(Arc::new(node))),
223                        Err(err) => Some(Err(TrieError::RLPDecode(err))),
224                    })
225                    .transpose()
226            }
227        }
228    }
229
230    /// Gets a mutable shared reference to the inner node.
231    ///
232    /// # Caution
233    ///
234    /// 1. If more than one strong reference exists to this node, it will be cloned (see `Arc::make_mut`).
235    /// 2. Mutating the inner node without updating parents can lead to trie inconsistencies.
236    pub(crate) fn get_node_mut(
237        &mut self,
238        db: &dyn TrieDB,
239        path: Nibbles,
240    ) -> Result<Option<&mut Node>, TrieError> {
241        match self {
242            NodeRef::Node(node, _) => Ok(Some(Arc::make_mut(node))),
243            NodeRef::Hash(hash @ NodeHash::Inline(_)) => {
244                let node = Node::decode(hash.as_ref())?;
245                *self = NodeRef::Node(Arc::new(node), OnceLock::from(*hash));
246                self.get_node_mut(db, path)
247            }
248            NodeRef::Hash(hash @ NodeHash::Hashed(_)) => {
249                let Some(node) = db
250                    .get(path.clone())?
251                    .filter(|rlp| !rlp.is_empty())
252                    .map(|rlp| Node::decode(&rlp).map_err(TrieError::RLPDecode))
253                    .transpose()?
254                else {
255                    return Ok(None);
256                };
257                *self = NodeRef::Node(Arc::new(node), OnceLock::from(*hash));
258                self.get_node_mut(db, path)
259            }
260        }
261    }
262
263    pub fn is_valid(&self) -> bool {
264        match self {
265            NodeRef::Node(_, _) => true,
266            NodeRef::Hash(hash) => hash.is_valid(),
267        }
268    }
269
270    pub fn commit(
271        &mut self,
272        path: Nibbles,
273        acc: &mut Vec<(Nibbles, Vec<u8>)>,
274        crypto: &dyn Crypto,
275    ) -> NodeHash {
276        match *self {
277            NodeRef::Node(ref mut node, ref mut hash) => {
278                if let Some(hash) = hash.get() {
279                    return *hash;
280                }
281                match Arc::make_mut(node) {
282                    Node::Branch(node) => {
283                        for (choice, node) in &mut node.choices.iter_mut().enumerate() {
284                            node.commit(path.append_new(choice as u8), acc, crypto);
285                        }
286                    }
287                    Node::Extension(node) => {
288                        node.child.commit(path.concat(&node.prefix), acc, crypto);
289                    }
290                    Node::Leaf(_) => {}
291                }
292                let mut buf = Vec::new();
293                node.encode(&mut buf);
294                let hash = *hash.get_or_init(|| NodeHash::from_encoded(&buf, crypto));
295                if let Node::Leaf(leaf) = node.as_ref() {
296                    acc.push((path.concat(&leaf.partial), leaf.value.clone()));
297                }
298                acc.push((path, buf));
299
300                hash
301            }
302            NodeRef::Hash(hash) => hash,
303        }
304    }
305
306    pub fn compute_hash(&self, crypto: &dyn Crypto) -> NodeHash {
307        *self.compute_hash_ref(crypto)
308    }
309
310    pub fn compute_hash_ref(&self, crypto: &dyn Crypto) -> &NodeHash {
311        match self {
312            NodeRef::Node(node, hash) => hash.get_or_init(|| node.compute_hash(crypto)),
313            NodeRef::Hash(hash) => hash,
314        }
315    }
316
317    pub fn compute_hash_no_alloc(&self, buf: &mut Vec<u8>, crypto: &dyn Crypto) -> &NodeHash {
318        match self {
319            NodeRef::Node(node, hash) => {
320                hash.get_or_init(|| node.compute_hash_no_alloc(buf, crypto))
321            }
322            NodeRef::Hash(hash) => hash,
323        }
324    }
325
326    pub fn memoize_hashes(&self, buf: &mut Vec<u8>, crypto: &dyn Crypto) {
327        if let NodeRef::Node(node, hash) = &self
328            && hash.get().is_none()
329        {
330            node.memoize_hashes(buf, crypto);
331            let _ = hash.set(node.compute_hash_no_alloc(buf, crypto));
332        }
333    }
334
335    /// Resets the memoized hash of this Node
336    ///
337    /// This is used when mutating a node in place, in which case the memoized hash
338    /// is not valid anymore.
339    pub fn clear_hash(&mut self) {
340        if let NodeRef::Node(_, hash) = self {
341            hash.take();
342        }
343    }
344}
345
346impl Default for NodeRef {
347    fn default() -> Self {
348        Self::Hash(NodeHash::default())
349    }
350}
351
352impl From<Node> for NodeRef {
353    fn from(value: Node) -> Self {
354        Self::Node(Arc::new(value), OnceLock::new())
355    }
356}
357
358impl From<NodeHash> for NodeRef {
359    fn from(value: NodeHash) -> Self {
360        Self::Hash(value)
361    }
362}
363
364impl From<Arc<Node>> for NodeRef {
365    fn from(value: Arc<Node>) -> Self {
366        Self::Node(value, OnceLock::new())
367    }
368}
369
370impl PartialEq for NodeRef {
371    fn eq(&self, other: &Self) -> bool {
372        let mut buf = Vec::new();
373        self.compute_hash_no_alloc(&mut buf, &NativeCrypto)
374            == other.compute_hash_no_alloc(&mut buf, &NativeCrypto)
375    }
376}
377
378pub enum ValueOrHash {
379    Value(ValueRLP),
380    Hash(NodeHash),
381}
382
383impl From<ValueRLP> for ValueOrHash {
384    fn from(value: ValueRLP) -> Self {
385        Self::Value(value)
386    }
387}
388
389impl From<NodeHash> for ValueOrHash {
390    fn from(value: NodeHash) -> Self {
391        Self::Hash(value)
392    }
393}
394
395#[derive(
396    Debug,
397    Clone,
398    PartialEq,
399    serde::Serialize,
400    serde::Deserialize,
401    rkyv::Deserialize,
402    rkyv::Serialize,
403    rkyv::Archive,
404)]
405/// A Node in an Ethereum Compatible Patricia Merkle Trie
406pub enum Node {
407    Branch(Box<BranchNode>),
408    Extension(ExtensionNode),
409    Leaf(LeafNode),
410}
411
412impl Default for Node {
413    fn default() -> Self {
414        // empty leaf node as a placeholder
415        Self::Leaf(LeafNode {
416            partial: Nibbles::from_bytes(&[]),
417            value: Vec::new(),
418        })
419    }
420}
421
422impl From<Box<BranchNode>> for Node {
423    fn from(val: Box<BranchNode>) -> Self {
424        Node::Branch(val)
425    }
426}
427
428impl From<BranchNode> for Node {
429    fn from(val: BranchNode) -> Self {
430        Node::Branch(Box::new(val))
431    }
432}
433
434impl From<ExtensionNode> for Node {
435    fn from(val: ExtensionNode) -> Self {
436        Node::Extension(val)
437    }
438}
439
440impl From<LeafNode> for Node {
441    fn from(val: LeafNode) -> Self {
442        Node::Leaf(val)
443    }
444}
445
446impl Node {
447    /// Retrieves a value from the subtrie originating from this node given its path
448    pub fn get(&self, db: &dyn TrieDB, path: Nibbles) -> Result<Option<ValueRLP>, TrieError> {
449        match self {
450            Node::Branch(n) => n.get(db, path),
451            Node::Extension(n) => n.get(db, path),
452            Node::Leaf(n) => n.get(path),
453        }
454    }
455
456    /// Inserts a value into the subtrie originating from this node.
457    pub fn insert(
458        &mut self,
459        db: &dyn TrieDB,
460        path: Nibbles,
461        value: impl Into<ValueOrHash>,
462    ) -> Result<(), TrieError> {
463        let new_node = match self {
464            Node::Branch(n) => {
465                n.insert(db, path, value.into())?;
466                Ok(None)
467            }
468            Node::Extension(n) => n.insert(db, path, value.into()),
469            Node::Leaf(n) => n.insert(path, value.into()),
470        };
471        if let Some(new_node) = new_node? {
472            *self = new_node;
473        }
474        Ok(())
475    }
476
477    /// Removes a value from the subtrie originating from this node given its path
478    /// Returns a bool indicating if the new subtrie is empty, and the removed value if it existed in the subtrie
479    pub fn remove(
480        &mut self,
481        db: &dyn TrieDB,
482        path: Nibbles,
483    ) -> Result<(bool, Option<ValueRLP>), TrieError> {
484        let (new_root, value) = match self {
485            Node::Branch(n) => n.remove(db, path),
486            Node::Extension(n) => n.remove(db, path),
487            Node::Leaf(n) => n.remove(path),
488        }?;
489
490        let is_trie_empty = new_root.is_none();
491        if let Some(NodeRemoveResult::New(new_root)) = new_root {
492            *self = new_root;
493        }
494        Ok((is_trie_empty, value))
495    }
496
497    /// Traverses own subtrie until reaching the node containing `path`
498    /// Appends all encoded nodes traversed to `node_path` (including self)
499    /// Only nodes with encoded len over or equal to 32 bytes are included
500    pub fn get_path(
501        &self,
502        db: &dyn TrieDB,
503        path: Nibbles,
504        node_path: &mut Vec<Vec<u8>>,
505    ) -> Result<(), TrieError> {
506        match self {
507            Node::Branch(n) => n.get_path(db, path, node_path),
508            Node::Extension(n) => n.get_path(db, path, node_path),
509            Node::Leaf(n) => n.get_path(node_path),
510        }
511    }
512
513    /// Computes the node's hash
514    pub fn compute_hash(&self, crypto: &dyn Crypto) -> NodeHash {
515        let mut buf = Vec::new();
516        self.memoize_hashes(&mut buf, crypto);
517        match self {
518            Node::Branch(n) => n.compute_hash_no_alloc(&mut buf, crypto),
519            Node::Extension(n) => n.compute_hash_no_alloc(&mut buf, crypto),
520            Node::Leaf(n) => n.compute_hash_no_alloc(&mut buf, crypto),
521        }
522    }
523
524    /// Computes the node's hash
525    pub fn compute_hash_no_alloc(&self, buf: &mut Vec<u8>, crypto: &dyn Crypto) -> NodeHash {
526        self.memoize_hashes(buf, crypto);
527        match self {
528            Node::Branch(n) => n.compute_hash_no_alloc(buf, crypto),
529            Node::Extension(n) => n.compute_hash_no_alloc(buf, crypto),
530            Node::Leaf(n) => n.compute_hash_no_alloc(buf, crypto),
531        }
532    }
533
534    /// Recursively memoizes the hashes of all nodes of the subtrie that has
535    /// `self` as root (post-order traversal)
536    pub fn memoize_hashes(&self, buf: &mut Vec<u8>, crypto: &dyn Crypto) {
537        match self {
538            Node::Branch(n) => {
539                for child in &n.choices {
540                    child.memoize_hashes(buf, crypto);
541                }
542            }
543            Node::Extension(n) => n.child.memoize_hashes(buf, crypto),
544            _ => {}
545        }
546    }
547
548    /// Recursively encodes all embedded nodes of the subtrie that has
549    /// `self` as root.
550    ///
551    /// This won't encode nodes which are not embedded in `self`.
552    pub fn encode_subtrie(&self, encoded: &mut Vec<NodeRLP>) -> Result<(), TrieError> {
553        match self {
554            Node::Branch(node) => {
555                for choice in &node.choices {
556                    if let NodeRef::Node(choice, _) = choice {
557                        choice.encode_subtrie(encoded)?;
558                    }
559                }
560            }
561            Node::Extension(node) => {
562                if let NodeRef::Node(child, _) = &node.child {
563                    child.encode_subtrie(encoded)?;
564                }
565            }
566            Node::Leaf(_) => {}
567        };
568
569        encoded.push(self.encode_to_vec());
570        Ok(())
571    }
572}
573
574/// Used as return type for `Node` remove operations that may resolve into either:
575/// - a mutation of the `Node`
576/// - a new `Node`
577pub enum NodeRemoveResult {
578    Mutated,
579    New(Node),
580}