Skip to main content

ethrex_trie/
trie.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2// In no_std builds the per-node hash cache (`OnceLock`) is a non-atomic, `!Sync`
3// cell (see `node::OnceLock`), which makes `Node` `!Sync` and trips
4// `arc_with_non_send_sync` on the `Arc<Node>` in `NodeRef`. The `Arc` is
5// deliberate: the type is shared with the std build, where `Node` is `Send + Sync`
6// and nodes are handed across rayon workers during parallel merkleization. The
7// single-threaded no_std consumer (the zkVM guest) never shares across threads, so
8// the atomic refcount is harmless here and keeps a single unified node type across
9// both builds.
10#![cfg_attr(not(feature = "std"), allow(clippy::arc_with_non_send_sync))]
11
12#[macro_use]
13extern crate alloc;
14
15pub mod db;
16pub mod error;
17// Witness recording (Arc<Mutex>) is host-only; the guest verifies against a witness.
18#[cfg(feature = "std")]
19pub mod logger;
20mod nibbles;
21pub mod node;
22mod node_hash;
23pub mod rkyv_utils;
24mod rlp;
25#[cfg(test)]
26mod test_utils;
27// Parallel merkleization (std threads + crossbeam) is host-only.
28#[cfg(feature = "std")]
29pub mod threadpool;
30mod trie_iter;
31#[cfg(feature = "std")]
32pub mod trie_sorted;
33mod verify_range;
34
35use alloc::collections::BTreeMap;
36use alloc::sync::Arc;
37#[cfg(not(feature = "std"))]
38use alloc::{boxed::Box, vec::Vec};
39use ethereum_types::H256;
40use ethrex_crypto::{Crypto, NativeCrypto};
41use ethrex_rlp::constants::RLP_NULL;
42use ethrex_rlp::encode::RLPEncode;
43
44#[cfg(feature = "std")]
45use rustc_hash::FxHashSet;
46// `FxHashMap` is part of the public API (`get_embedded_root_committed`). Its concrete
47// type is build-dependent, so callers that build this crate `no_std` (e.g. the guest
48// via `ethrex-common`) must name it through this re-export to stay type-compatible.
49#[cfg(feature = "std")]
50pub use rustc_hash::FxHashMap;
51// rustc-hash's map/set aliases require std; use hashbrown with the Fx hasher otherwise.
52#[cfg(not(feature = "std"))]
53pub type FxHashMap<K, V> = hashbrown::HashMap<K, V, rustc_hash::FxBuildHasher>;
54#[cfg(not(feature = "std"))]
55type FxHashSet<K> = hashbrown::HashSet<K, rustc_hash::FxBuildHasher>;
56
57pub use self::db::{InMemoryTrieDB, TrieDB};
58#[cfg(feature = "std")]
59pub use self::logger::{TrieLogger, TrieWitness};
60pub use self::nibbles::Nibbles;
61#[cfg(feature = "std")]
62pub use self::threadpool::ThreadPool;
63pub use self::verify_range::verify_range;
64pub use self::{
65    node::{Node, NodeRef, OnceLock},
66    node_hash::NodeHash,
67};
68
69pub use self::error::{ExtensionNodeErrorData, InconsistentTreeError, TrieError};
70use self::{node::LeafNode, trie_iter::TrieIterator};
71
72use ethrex_rlp::decode::RLPDecode;
73
74/// Hash of an empty trie: `keccak256(RLP_NULL)`, a well-known Ethereum constant.
75/// Hardcoded so it needs no lazy initialization, works in `const` contexts, and
76/// pulls no lazy-cell dependency into `no_std` builds. The
77/// `empty_trie_hash_matches_keccak` test pins it to the computed value.
78pub const EMPTY_TRIE_HASH: H256 = H256([
79    0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e,
80    0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21,
81]);
82
83/// RLP-encoded trie path
84pub type PathRLP = Vec<u8>;
85/// RLP-encoded trie value
86pub type ValueRLP = Vec<u8>;
87/// RLP-encoded trie node
88pub type NodeRLP = Vec<u8>;
89/// Represents a node in the Merkle Patricia Trie.
90pub type TrieNode = (Nibbles, NodeRLP);
91
92/// Ethereum-compatible Merkle Patricia Trie
93pub struct Trie {
94    db: Box<dyn TrieDB>,
95    pub root: NodeRef,
96    pending_removal: FxHashSet<Nibbles>,
97    dirty: FxHashSet<Nibbles>,
98}
99
100// `Default` builds an in-memory (InMemoryTrieDB) trie.
101impl Default for Trie {
102    fn default() -> Self {
103        Self::new_temp()
104    }
105}
106
107impl Trie {
108    /// Creates a new Trie from a clean DB
109    pub fn new(db: Box<dyn TrieDB>) -> Self {
110        Self {
111            db,
112            root: NodeRef::default(),
113            pending_removal: Default::default(),
114            dirty: Default::default(),
115        }
116    }
117
118    /// Creates a trie from an already-initialized DB and sets root as the root node of the trie
119    pub fn open(db: Box<dyn TrieDB>, root: H256) -> Self {
120        Self {
121            db,
122            root: if root != EMPTY_TRIE_HASH {
123                NodeHash::from(root).into()
124            } else {
125                Default::default()
126            },
127            pending_removal: Default::default(),
128            dirty: Default::default(),
129        }
130    }
131
132    /// Return a reference to the internal database.
133    ///
134    /// Warning: All changes made to the db will bypass the trie and may cause the trie to suddenly
135    ///   become inconsistent.
136    pub fn db(&self) -> &dyn TrieDB {
137        self.db.as_ref()
138    }
139
140    /// Retrieve an RLP-encoded value from the trie given its RLP-encoded path.
141    pub fn get(&self, pathrlp: &[u8]) -> Result<Option<ValueRLP>, TrieError> {
142        let path = Nibbles::from_bytes(pathrlp);
143
144        if !self.dirty.contains(&path) && self.db().flatkeyvalue_computed(path.clone()) {
145            let Some(value_rlp) = self.db.get(path)? else {
146                return Ok(None);
147            };
148            if value_rlp.is_empty() {
149                return Ok(None);
150            }
151            return Ok(Some(value_rlp));
152        }
153
154        Ok(match self.root {
155            NodeRef::Node(ref node, _) => node.get(self.db.as_ref(), path)?,
156            NodeRef::Hash(hash) if hash.is_valid() => {
157                Node::decode(&self.db.get(Nibbles::default())?.ok_or_else(|| {
158                    TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(
159                        hash.finalize(&NativeCrypto),
160                    )))
161                })?)
162                .map_err(TrieError::RLPDecode)?
163                .get(self.db.as_ref(), path)?
164            }
165            _ => None,
166        })
167    }
168
169    /// Insert an RLP-encoded value into the trie.
170    pub fn insert(&mut self, path: PathRLP, value: ValueRLP) -> Result<(), TrieError> {
171        let path = Nibbles::from_bytes(&path);
172        self.pending_removal.remove(&path);
173        self.dirty.insert(path.clone());
174
175        if self.root.is_valid() {
176            // If the trie is not empty, call the root node's insertion logic.
177            self.root
178                .get_node_mut(self.db.as_ref(), Nibbles::default())?
179                .ok_or_else(|| {
180                    TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFoundNoHash))
181                })?
182                .insert(self.db.as_ref(), path, value)?
183        } else {
184            // If the trie is empty, just add a leaf.
185            self.root = Node::from(LeafNode::new(path, value)).into()
186        };
187        self.root.clear_hash();
188
189        Ok(())
190    }
191
192    /// Remove a value from the trie given its RLP-encoded path.
193    /// Returns the value if it was succesfully removed or None if it wasn't part of the trie
194    pub fn remove(&mut self, path: &[u8]) -> Result<Option<ValueRLP>, TrieError> {
195        self.dirty.insert(Nibbles::from_bytes(path));
196        if !self.root.is_valid() {
197            return Ok(None);
198        }
199        self.pending_removal.insert(Nibbles::from_bytes(path));
200
201        // If the trie is not empty, call the root node's removal logic.
202        let (is_trie_empty, value) = self
203            .root
204            .get_node_mut(self.db.as_ref(), Nibbles::default())?
205            .ok_or_else(|| {
206                TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFoundNoHash))
207            })?
208            .remove(self.db.as_ref(), Nibbles::from_bytes(path))?;
209        if is_trie_empty {
210            self.root = NodeRef::default();
211        } else {
212            self.root.clear_hash();
213        }
214
215        Ok(value)
216    }
217
218    /// Return the hash of the trie's root node.
219    /// Returns keccak(RLP_NULL) if the trie is empty
220    /// Also commits changes to the DB
221    pub fn hash(&mut self, crypto: &dyn Crypto) -> Result<H256, TrieError> {
222        self.commit(crypto)?;
223        Ok(self.hash_no_commit(crypto))
224    }
225
226    /// Return the hash of the trie's root node.
227    /// Returns keccak(RLP_NULL) if the trie is empty
228    pub fn hash_no_commit(&self, crypto: &dyn Crypto) -> H256 {
229        if self.root.is_valid() {
230            // 512 is the maximum size of an encoded node
231            let mut buf = Vec::with_capacity(512);
232            self.root
233                .compute_hash_no_alloc(&mut buf, crypto)
234                .finalize(crypto)
235        } else {
236            EMPTY_TRIE_HASH
237        }
238    }
239
240    pub fn get_root_node(&self, path: Nibbles) -> Result<Arc<Node>, TrieError> {
241        self.root
242            .get_node_checked(self.db.as_ref(), path)?
243            .ok_or_else(|| {
244                TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(
245                    self.root
246                        .compute_hash(&NativeCrypto)
247                        .finalize(&NativeCrypto),
248                )))
249            })
250    }
251
252    /// Returns a list of changes in a TrieNode format since last root hash processed.
253    ///
254    /// # Returns
255    ///
256    /// A tuple containing the hash and the list of changes.
257    pub fn collect_changes_since_last_hash(
258        &mut self,
259        crypto: &dyn Crypto,
260    ) -> (H256, Vec<TrieNode>) {
261        let updates = self.commit_without_storing(crypto);
262        let ret_hash = self.hash_no_commit(crypto);
263        (ret_hash, updates)
264    }
265
266    /// Compute the hash of the root node and flush any changes into the database.
267    ///
268    /// This method will also compute the hash of all internal nodes indirectly. It will not clear
269    /// the cached nodes.
270    pub fn commit(&mut self, crypto: &dyn Crypto) -> Result<(), TrieError> {
271        let acc = self.commit_without_storing(crypto);
272        self.db.put_batch(acc)?;
273
274        // Commit the underlying transaction
275        self.db.commit()?;
276
277        Ok(())
278    }
279
280    /// Computes the nodes that would be added if updating the trie.
281    /// Nodes are given with their hash pre-calculated.
282    pub fn commit_without_storing(&mut self, crypto: &dyn Crypto) -> Vec<TrieNode> {
283        let mut acc = Vec::new();
284        if self.root.is_valid() {
285            self.root.commit(Nibbles::default(), &mut acc, crypto);
286        }
287        if self.root.compute_hash(crypto) == NodeHash::Hashed(EMPTY_TRIE_HASH) {
288            acc.push((Nibbles::default(), vec![RLP_NULL]))
289        }
290        acc.extend(self.pending_removal.drain().map(|nib| (nib, vec![])));
291
292        acc
293    }
294
295    /// Obtain a merkle proof for the given path.
296    /// The proof will contain all the encoded nodes traversed until reaching the node where the path is stored (including this last node).
297    /// The proof will still be constructed even if the path is not stored in the trie, proving its absence.
298    ///
299    /// Note: This method has a different behavior in regard to non-existent trie root nodes. Normal
300    ///   behavior is to return `Err(InconsistentTrie)`, but this method will return
301    ///   `Ok(Vec::new())` instead.
302    pub fn get_proof(&self, path: &[u8]) -> Result<Vec<NodeRLP>, TrieError> {
303        if self.root.is_valid() {
304            let hash = self.root.compute_hash(&NativeCrypto);
305
306            let mut node_path = Vec::new();
307            if let NodeHash::Inline((data, len)) = hash {
308                node_path.push(data[..len as usize].to_vec());
309            }
310
311            let root = match self
312                .root
313                .get_node_checked(self.db.as_ref(), Nibbles::default())?
314            {
315                Some(x) => x,
316                None => return Ok(Vec::new()),
317            };
318            root.get_path(self.db.as_ref(), Nibbles::from_bytes(path), &mut node_path)?;
319
320            Ok(node_path)
321        } else {
322            Ok(Vec::new())
323        }
324    }
325
326    /// Obtains all encoded nodes traversed until reaching the node where every path is stored.
327    /// The list doesn't include the root node, this is returned separately.
328    /// Will still be constructed even if some path is not stored in the trie.
329    pub fn get_proofs(
330        &self,
331        paths: &[PathRLP],
332    ) -> Result<(Option<NodeRLP>, Vec<NodeRLP>), TrieError> {
333        if self.root.is_valid() {
334            let encoded_root = self.get_root_node(Nibbles::default())?.encode_to_vec();
335
336            let mut node_path: FxHashSet<_> = Default::default();
337            for path in paths {
338                let mut nodes = self.get_proof(path)?;
339                nodes.swap_remove(0);
340                node_path.extend(nodes);
341            }
342
343            Ok((Some(encoded_root), node_path.into_iter().collect()))
344        } else {
345            Ok((None, Vec::new()))
346        }
347    }
348
349    #[cfg(feature = "std")]
350    pub fn empty_in_memory() -> Self {
351        Self::new(Box::new(InMemoryTrieDB::new(Arc::new(
352            std::sync::Mutex::new(BTreeMap::new()),
353        ))))
354    }
355
356    /// Gets node with embedded references to child nodes, all in just one `Node`.
357    pub fn get_embedded_root(
358        all_nodes: &BTreeMap<H256, Node>,
359        root_hash: H256,
360    ) -> Result<NodeRef, TrieError> {
361        // If the root hash is of the empty trie then we can get away by setting the NodeRef to default
362        if root_hash == EMPTY_TRIE_HASH {
363            return Ok(NodeRef::default());
364        }
365
366        let root_rlp = all_nodes.get(&root_hash).ok_or_else(|| {
367            TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(root_hash)))
368        })?;
369
370        fn get_embedded_node(
371            all_nodes: &BTreeMap<H256, Node>,
372            cur_node: &Node,
373        ) -> Result<Node, TrieError> {
374            Ok(match cur_node.clone() {
375                Node::Branch(mut node) => {
376                    for choice in &mut node.choices {
377                        let NodeRef::Hash(hash) = *choice else {
378                            continue;
379                        };
380
381                        if hash.is_valid() {
382                            *choice = match all_nodes.get(&hash.finalize(&NativeCrypto)) {
383                                Some(node) => get_embedded_node(all_nodes, node)?.into(),
384                                None => hash.into(),
385                            };
386                        }
387                    }
388
389                    (*node).into()
390                }
391                Node::Extension(mut node) => {
392                    let NodeRef::Hash(hash) = node.child else {
393                        return Ok(node.into());
394                    };
395
396                    node.child = match all_nodes.get(&hash.finalize(&NativeCrypto)) {
397                        Some(node) => get_embedded_node(all_nodes, node)?.into(),
398                        None => hash.into(),
399                    };
400
401                    node.into()
402                }
403                Node::Leaf(node) => node.into(),
404            })
405        }
406
407        let root = get_embedded_node(all_nodes, root_rlp)?;
408        Ok(root.into())
409    }
410
411    /// Gets node with embedded references to child nodes, all in just one `Node`.
412    ///
413    /// Note that this method caches the hash of each node, so it assumes the provided `all_nodes` are well-formed.
414    pub fn get_embedded_root_committed(
415        all_nodes: &FxHashMap<H256, Node>,
416        root_hash: H256,
417        crypto: &dyn Crypto,
418    ) -> Result<NodeRef, TrieError> {
419        // If the root hash is of the empty trie then we can get away by setting the NodeRef to default
420        if root_hash == EMPTY_TRIE_HASH {
421            return Ok(NodeRef::default());
422        }
423
424        let root_rlp = all_nodes.get(&root_hash).ok_or_else(|| {
425            TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(root_hash)))
426        })?;
427
428        /// Creates an embedded node reference with its hash slot pre-seeded.
429        ///
430        /// The caller must guarantee that `hash` is the hash of the referenced
431        /// node, for example because the node was just resolved by looking that
432        /// hash up. Seeding lets later hash computations over the subtree reuse
433        /// the known value instead of re-encoding and re-hashing it.
434        fn node_with_hash(node: Node, hash: NodeHash) -> NodeRef {
435            NodeRef::Node(Arc::new(node), OnceLock::from(hash))
436        }
437
438        fn get_embedded_node_committed(
439            all_nodes: &FxHashMap<H256, Node>,
440            cur_node: &Node,
441            crypto: &dyn Crypto,
442        ) -> Result<Node, TrieError> {
443            Ok(match cur_node.clone() {
444                Node::Branch(mut node) => {
445                    for choice in &mut node.choices {
446                        let NodeRef::Hash(hash) = *choice else {
447                            continue;
448                        };
449
450                        if hash.is_valid() {
451                            *choice = match all_nodes.get(&hash.finalize(crypto)) {
452                                Some(node) => node_with_hash(
453                                    get_embedded_node_committed(all_nodes, node, crypto)?,
454                                    hash,
455                                ),
456                                None => hash.into(),
457                            };
458                        }
459                    }
460
461                    (*node).into()
462                }
463                Node::Extension(mut node) => {
464                    let NodeRef::Hash(hash) = node.child else {
465                        return Ok(node.into());
466                    };
467
468                    node.child = match all_nodes.get(&hash.finalize(crypto)) {
469                        Some(node) => node_with_hash(
470                            get_embedded_node_committed(all_nodes, node, crypto)?,
471                            hash,
472                        ),
473                        None => hash.into(),
474                    };
475
476                    node.into()
477                }
478                Node::Leaf(node) => node.into(),
479            })
480        }
481
482        let root = get_embedded_node_committed(all_nodes, root_rlp, crypto)?;
483        Ok(node_with_hash(root, NodeHash::Hashed(root_hash)))
484    }
485
486    /// Builds a trie from a set of nodes with an empty InMemoryTrieDB as a backend because the nodes are embedded in the root.
487    ///
488    /// Note: This method will not ensure that all node references are valid. Invalid references
489    ///   will cause other methods (including, but not limited to `Trie::get`, `Trie::insert` and
490    ///   `Trie::remove`) to return `Err(InconsistentTrie)`.
491    /// Note: This method will ignore any dangling nodes. All nodes that are not accessible from the
492    ///   root node are considered dangling.
493    #[cfg(feature = "std")]
494    pub fn from_nodes(
495        root_hash: H256,
496        state_nodes: &BTreeMap<H256, Node>,
497    ) -> Result<Self, TrieError> {
498        let mut trie = Trie::new(Box::new(InMemoryTrieDB::default()));
499        let root = Self::get_embedded_root(state_nodes, root_hash)?;
500        trie.root = root;
501
502        Ok(trie)
503    }
504
505    /// Builds an in-memory trie from the given elements and returns its hash
506    pub fn compute_hash_from_unsorted_iter(
507        iter: impl Iterator<Item = (PathRLP, ValueRLP)>,
508        crypto: &dyn Crypto,
509    ) -> H256 {
510        let mut trie = Trie::stateless();
511        for (path, value) in iter {
512            // Unwraping here won't panic as our in_memory trie DB won't fail
513            trie.insert(path, value).unwrap();
514        }
515
516        trie.hash_no_commit(crypto)
517    }
518
519    /// Creates a new stateless trie. This trie won't be able to store any nodes so all data will be lost after calculating the hash
520    /// Only use it for proof verification or computing a hash from an iterator
521    pub(crate) fn stateless() -> Trie {
522        // We will only be using the trie's cache so we don't need a working DB
523        struct NullTrieDB;
524
525        impl TrieDB for NullTrieDB {
526            fn get(&self, _key: Nibbles) -> Result<Option<Vec<u8>>, TrieError> {
527                Ok(None)
528            }
529
530            fn put_batch(&self, _key_values: Vec<TrieNode>) -> Result<(), TrieError> {
531                Ok(())
532            }
533        }
534
535        Trie::new(Box::new(NullTrieDB))
536    }
537
538    /// Obtain the encoded node given its path.
539    /// Allows usage of full paths (byte slice of 32 bytes) or compact-encoded nibble slices (with length lower than 32)
540    pub fn get_node(&self, partial_path: &PathRLP) -> Result<Vec<u8>, TrieError> {
541        // Convert compact-encoded nibbles into a byte slice if necessary
542        let partial_path = match partial_path.len() {
543            // Compact-encoded nibbles
544            n if n < 32 => Nibbles::decode_compact(partial_path),
545            // Full path (No conversion needed)
546            32 => Nibbles::from_bytes(partial_path),
547            // We won't handle paths with length over 32
548            _ => return Ok(vec![]),
549        };
550
551        fn get_node_inner(
552            db: &dyn TrieDB,
553            current_path: Nibbles,
554            node: &Node,
555            mut partial_path: Nibbles,
556        ) -> Result<Vec<u8>, TrieError> {
557            // If we reached the end of the partial path, return the current node
558            if partial_path.is_empty() {
559                return Ok(node.encode_to_vec());
560            }
561            match node {
562                Node::Branch(branch_node) => match partial_path.next_choice() {
563                    Some(idx) => {
564                        let child_ref = &branch_node.choices[idx];
565                        if child_ref.is_valid() {
566                            let child_path = current_path.append_new(idx as u8);
567                            let child_node = child_ref
568                                .get_node_checked(db, child_path.clone())?
569                                .ok_or_else(|| {
570                                    TrieError::InconsistentTree(Box::new(
571                                        InconsistentTreeError::NodeNotFoundOnBranchNode(
572                                            child_ref
573                                                .compute_hash(&NativeCrypto)
574                                                .finalize(&NativeCrypto),
575                                            branch_node
576                                                .compute_hash(&NativeCrypto)
577                                                .finalize(&NativeCrypto),
578                                            child_path.clone(),
579                                        ),
580                                    ))
581                                })?;
582                            get_node_inner(db, child_path, &child_node, partial_path)
583                        } else {
584                            Ok(vec![])
585                        }
586                    }
587                    _ => Ok(vec![]),
588                },
589                Node::Extension(extension_node) => {
590                    if partial_path.skip_prefix(&extension_node.prefix)
591                        && extension_node.child.is_valid()
592                    {
593                        let child_path = partial_path.concat(&extension_node.prefix);
594                        let child_node = extension_node
595                            .child
596                            .get_node_checked(db, child_path.clone())?
597                            .ok_or_else(|| {
598                                TrieError::InconsistentTree(Box::new(
599                                    InconsistentTreeError::ExtensionNodeChildNotFound(
600                                        ExtensionNodeErrorData {
601                                            node_hash: extension_node
602                                                .child
603                                                .compute_hash(&NativeCrypto)
604                                                .finalize(&NativeCrypto),
605                                            extension_node_hash: extension_node
606                                                .compute_hash(&NativeCrypto)
607                                                .finalize(&NativeCrypto),
608                                            extension_node_prefix: extension_node.prefix.clone(),
609                                            node_path: child_path.clone(),
610                                        },
611                                    ),
612                                ))
613                            })?;
614                        get_node_inner(db, child_path, &child_node, partial_path)
615                    } else {
616                        Ok(vec![])
617                    }
618                }
619                Node::Leaf(_) => Ok(vec![]),
620            }
621        }
622
623        // Fetch node
624        if self.root.is_valid() {
625            let root_node = self.get_root_node(Default::default())?;
626            get_node_inner(
627                self.db.as_ref(),
628                Default::default(),
629                &root_node,
630                partial_path,
631            )
632        } else {
633            Ok(Vec::new())
634        }
635    }
636
637    pub fn root_node(&self) -> Result<Option<Arc<Node>>, TrieError> {
638        if self.root.is_valid() {
639            self.root.get_node(self.db.as_ref(), Nibbles::default())
640        } else {
641            Ok(None)
642        }
643    }
644
645    /// Creates a new Trie based on a temporary InMemory DB
646    pub fn new_temp() -> Self {
647        let db = InMemoryTrieDB::new(Default::default());
648        Trie::new(Box::new(db))
649    }
650
651    /// Creates a new Trie based on a temporary InMemory DB, with a specified root
652    ///
653    /// This is usually used to create a Trie from a root that was embedded with the rest of the nodes.
654    pub fn new_temp_with_root(root: NodeRef) -> Self {
655        let db = InMemoryTrieDB::new(Default::default());
656        let mut trie = Trie::new(Box::new(db));
657        trie.root = root;
658        trie
659    }
660
661    /// Validates that the Trie isn't missing any nodes expected in the branches
662    ///
663    /// This is used internally with debug assertions to check the status of the trie
664    /// after syncing operations.
665    /// Note: this operation validates the hashes because the iterator uses
666    /// get_node_checked. We shouldn't downgrade that to the unchecked version
667    pub fn validate(self) -> Result<(), TrieError> {
668        let mut expected_count = if self.root.is_valid() { 1 } else { 0 };
669        for (_, node) in self.into_iter() {
670            expected_count -= 1;
671            match node {
672                Node::Branch(branch_node) => {
673                    expected_count += branch_node
674                        .choices
675                        .iter()
676                        .filter(|child| child.is_valid())
677                        .count();
678                }
679                Node::Extension(_) => {
680                    expected_count += 1;
681                }
682                Node::Leaf(_) => {}
683            }
684        }
685        if expected_count != 0 {
686            return Err(TrieError::Verify(format!(
687                "Node count mismatch, expected {expected_count} more"
688            )));
689        }
690        Ok(())
691    }
692
693    /// Validate the trie structure in parallel by splitting at the root branch node.
694    /// Each of the root's 16 subtrees is validated independently using rayon.
695    #[cfg(feature = "std")]
696    pub fn validate_parallel(self) -> Result<(), TrieError> {
697        use rayon::prelude::*;
698
699        if !self.root.is_valid() {
700            return Ok(());
701        }
702
703        let db = &*self.db;
704        let root_node = self
705            .root
706            .get_node_checked(db, Nibbles::default())?
707            .ok_or_else(|| TrieError::Verify("Root node not found".to_string()))?;
708
709        match &*root_node {
710            Node::Branch(branch_node) => {
711                let children: Vec<(Nibbles, NodeRef)> = branch_node
712                    .choices
713                    .iter()
714                    .enumerate()
715                    .filter(|(_, child)| child.is_valid())
716                    .map(|(i, child)| {
717                        let path = Nibbles::default().append_new(i as u8);
718                        (path, child.clone())
719                    })
720                    .collect();
721
722                children.par_iter().try_for_each(|(start_path, start_ref)| {
723                    validate_subtree(db, start_path.clone(), start_ref.clone())
724                })
725            }
726            _ => {
727                // Non-branch root (rare): validate sequentially
728                validate_subtree(db, Nibbles::default(), self.root.clone())
729            }
730        }
731    }
732}
733
734/// Validate a subtree rooted at `start_ref`, checking that all referenced nodes exist
735/// and their hashes match.
736#[cfg(feature = "std")]
737fn validate_subtree(
738    db: &dyn TrieDB,
739    start_path: Nibbles,
740    start_ref: NodeRef,
741) -> Result<(), TrieError> {
742    let mut expected_count: isize = 1;
743    let mut stack = vec![(start_path, start_ref)];
744
745    while let Some((path, node_ref)) = stack.pop() {
746        let node = node_ref
747            .get_node_checked(db, path.clone())?
748            .ok_or_else(|| TrieError::Verify(format!("Missing node at path {path:?}")))?;
749
750        expected_count -= 1;
751        match &*node {
752            Node::Branch(branch) => {
753                for (choice, child) in branch.choices.iter().enumerate().rev() {
754                    if child.is_valid() {
755                        expected_count += 1;
756                        stack.push((path.append_new(choice as u8), child.clone()));
757                    }
758                }
759            }
760            Node::Extension(ext) => {
761                expected_count += 1;
762                stack.push((path.concat(&ext.prefix), ext.child.clone()));
763            }
764            Node::Leaf(_) => {}
765        }
766    }
767
768    if expected_count != 0 {
769        return Err(TrieError::Verify(format!(
770            "Node count mismatch in subtree, expected {expected_count} more"
771        )));
772    }
773    Ok(())
774}
775
776impl IntoIterator for Trie {
777    type Item = (Nibbles, Node);
778
779    type IntoIter = TrieIterator;
780
781    fn into_iter(self) -> Self::IntoIter {
782        TrieIterator::new(self)
783    }
784}
785
786pub struct ProofTrie(Trie);
787
788impl ProofTrie {
789    pub fn insert(
790        &mut self,
791        partial_path: Nibbles,
792        external_ref: NodeHash,
793    ) -> Result<(), TrieError> {
794        if self.0.root.is_valid() {
795            // If the trie is not empty, call the root node's insertion logic.
796            self.0
797                .root
798                .get_node_mut(self.0.db.as_ref(), Nibbles::default())?
799                .ok_or_else(|| {
800                    TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFoundNoHash))
801                })?
802                .insert(self.0.db.as_ref(), partial_path, external_ref)?;
803            self.0.root.clear_hash();
804        } else {
805            self.0.root = external_ref.into();
806        };
807
808        Ok(())
809    }
810
811    pub fn hash(&self, crypto: &dyn Crypto) -> H256 {
812        self.0.hash_no_commit(crypto)
813    }
814}
815
816impl From<Trie> for ProofTrie {
817    fn from(value: Trie) -> Self {
818        Self(value)
819    }
820}
821
822#[cfg(test)]
823mod tests {
824    use super::*;
825    use ethrex_crypto::keccak::keccak_hash;
826
827    // Pins the hardcoded `EMPTY_TRIE_HASH` to `keccak256(RLP_NULL)` so a typo in the
828    // byte literal can't silently diverge from the computed value.
829    #[test]
830    fn empty_trie_hash_matches_keccak() {
831        assert_eq!(EMPTY_TRIE_HASH, H256(keccak_hash([RLP_NULL])));
832    }
833}