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    /// Pre-resolves, via breadth-first batched reads, every trie node that the
193    /// upcoming serial `insert`/`remove` loop over `sorted_paths` would
194    /// otherwise resolve one at a time (one `db.get` per level per key).
195    ///
196    /// Nodes are installed into the in-memory arena exactly the way lazy
197    /// resolution does it (see [`NodeRef::get_node_mut`]): the decoded node
198    /// replaces the `NodeRef::Hash` with a `NodeRef::Node` that keeps the SAME
199    /// memoized hash (`OnceLock::from(hash)`). This is purely a caching step:
200    /// it never modifies any node's content, never marks anything dirty, and
201    /// is therefore transparent to the resulting root hash and to the
202    /// persisted node set produced by `collect_changes_since_last_hash`
203    /// (verified by the `trie_prefetch` equivalence test). Prefetching extra
204    /// nodes for keys that later diverge during insert is safe for the same
205    /// reason: an unmodified, hash-preserved node is never re-emitted.
206    ///
207    /// No-op if `sorted_paths` is empty or if the trie's root isn't a
208    /// `NodeRef::Hash(NodeHash::Hashed(_))` (empty trie, inline root, or a
209    /// root that's already resolved — nothing left to batch).
210    pub fn prefetch_sorted(&mut self, sorted_paths: &[Nibbles]) -> Result<(), TrieError> {
211        if sorted_paths.is_empty() {
212            return Ok(());
213        }
214        if !matches!(self.root, NodeRef::Hash(NodeHash::Hashed(_))) {
215            return Ok(());
216        }
217
218        // Resolve the root itself first, exactly like the first `insert`
219        // would (a single `db.get`).
220        if self
221            .root
222            .get_node_mut(self.db.as_ref(), Nibbles::default())?
223            .is_none()
224        {
225            // Root hash points to a node that isn't in the DB. Let the real
226            // insert/remove loop hit (and report) this inconsistency;
227            // prefetching can't fix it and shouldn't hide it.
228            return Ok(());
229        }
230
231        let all_indices: Vec<usize> = (0..sorted_paths.len()).collect();
232        // Defensive guard against a malformed/cyclic trie: legitimate paths
233        // are at most 65 nibbles (64 nibbles + leaf terminator) deep.
234        const MAX_LEVELS: usize = 68;
235
236        for _ in 0..MAX_LEVELS {
237            // Clone the root's Arc (cheap refcount bump) so the following
238            // immutable scan doesn't hold a borrow of `self.root`, freeing it
239            // up for the later mutable install pass without any `&mut`
240            // reference held across the `multi_get` call in between.
241            let root_node = match &self.root {
242                NodeRef::Node(node, _) => node.clone(),
243                _ => break,
244            };
245
246            let mut to_fetch: Vec<Nibbles> = Vec::new();
247            collect_prefetch_targets(
248                &root_node,
249                &Nibbles::default(),
250                &all_indices,
251                sorted_paths,
252                &mut to_fetch,
253            );
254            // Release the extra strong ref to the root before the mutable
255            // install pass, so `Arc::make_mut` below sees strong count 1 (the
256            // common single-owner case) and does not clone the resolved path.
257            drop(root_node);
258            if to_fetch.is_empty() {
259                break;
260            }
261
262            let mut results = self.db.multi_get(&to_fetch).into_iter();
263
264            let NodeRef::Node(root_arc, _) = &mut self.root else {
265                break;
266            };
267            let root_mut = Arc::make_mut(root_arc);
268            install_prefetch_targets(
269                root_mut,
270                &Nibbles::default(),
271                &all_indices,
272                sorted_paths,
273                &mut results,
274            )?;
275        }
276
277        Ok(())
278    }
279
280    /// Remove a value from the trie given its RLP-encoded path.
281    /// Returns the value if it was succesfully removed or None if it wasn't part of the trie
282    pub fn remove(&mut self, path: &[u8]) -> Result<Option<ValueRLP>, TrieError> {
283        self.dirty.insert(Nibbles::from_bytes(path));
284        if !self.root.is_valid() {
285            return Ok(None);
286        }
287        self.pending_removal.insert(Nibbles::from_bytes(path));
288
289        // If the trie is not empty, call the root node's removal logic.
290        let (is_trie_empty, value) = self
291            .root
292            .get_node_mut(self.db.as_ref(), Nibbles::default())?
293            .ok_or_else(|| {
294                TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFoundNoHash))
295            })?
296            .remove(self.db.as_ref(), Nibbles::from_bytes(path))?;
297        if is_trie_empty {
298            self.root = NodeRef::default();
299        } else {
300            self.root.clear_hash();
301        }
302
303        Ok(value)
304    }
305
306    /// Return the hash of the trie's root node.
307    /// Returns keccak(RLP_NULL) if the trie is empty
308    /// Also commits changes to the DB
309    pub fn hash(&mut self, crypto: &dyn Crypto) -> Result<H256, TrieError> {
310        self.commit(crypto)?;
311        Ok(self.hash_no_commit(crypto))
312    }
313
314    /// Return the hash of the trie's root node.
315    /// Returns keccak(RLP_NULL) if the trie is empty
316    pub fn hash_no_commit(&self, crypto: &dyn Crypto) -> H256 {
317        if self.root.is_valid() {
318            // 512 is the maximum size of an encoded node
319            let mut buf = Vec::with_capacity(512);
320            self.root
321                .compute_hash_no_alloc(&mut buf, crypto)
322                .finalize(crypto)
323        } else {
324            EMPTY_TRIE_HASH
325        }
326    }
327
328    pub fn get_root_node(&self, path: Nibbles) -> Result<Arc<Node>, TrieError> {
329        self.root
330            .get_node_checked(self.db.as_ref(), path)?
331            .ok_or_else(|| {
332                TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(
333                    self.root
334                        .compute_hash(&NativeCrypto)
335                        .finalize(&NativeCrypto),
336                )))
337            })
338    }
339
340    /// Returns a list of changes in a TrieNode format since last root hash processed.
341    ///
342    /// # Returns
343    ///
344    /// A tuple containing the hash and the list of changes.
345    pub fn collect_changes_since_last_hash(
346        &mut self,
347        crypto: &dyn Crypto,
348    ) -> (H256, Vec<TrieNode>) {
349        let updates = self.commit_without_storing(crypto);
350        let ret_hash = self.hash_no_commit(crypto);
351        (ret_hash, updates)
352    }
353
354    /// Compute the hash of the root node and flush any changes into the database.
355    ///
356    /// This method will also compute the hash of all internal nodes indirectly. It will not clear
357    /// the cached nodes.
358    pub fn commit(&mut self, crypto: &dyn Crypto) -> Result<(), TrieError> {
359        let acc = self.commit_without_storing(crypto);
360        self.db.put_batch(acc)?;
361
362        // Commit the underlying transaction
363        self.db.commit()?;
364
365        Ok(())
366    }
367
368    /// Computes the nodes that would be added if updating the trie.
369    /// Nodes are given with their hash pre-calculated.
370    pub fn commit_without_storing(&mut self, crypto: &dyn Crypto) -> Vec<TrieNode> {
371        let mut acc = Vec::new();
372        if self.root.is_valid() {
373            self.root.commit(Nibbles::default(), &mut acc, crypto);
374        }
375        if self.root.compute_hash(crypto) == NodeHash::Hashed(EMPTY_TRIE_HASH) {
376            acc.push((Nibbles::default(), vec![RLP_NULL]))
377        }
378        acc.extend(self.pending_removal.drain().map(|nib| (nib, vec![])));
379
380        acc
381    }
382
383    /// Obtain a merkle proof for the given path.
384    /// The proof will contain all the encoded nodes traversed until reaching the node where the path is stored (including this last node).
385    /// The proof will still be constructed even if the path is not stored in the trie, proving its absence.
386    ///
387    /// Note: This method has a different behavior in regard to non-existent trie root nodes. Normal
388    ///   behavior is to return `Err(InconsistentTrie)`, but this method will return
389    ///   `Ok(Vec::new())` instead.
390    pub fn get_proof(&self, path: &[u8]) -> Result<Vec<NodeRLP>, TrieError> {
391        if self.root.is_valid() {
392            let hash = self.root.compute_hash(&NativeCrypto);
393
394            let mut node_path = Vec::new();
395            if let NodeHash::Inline((data, len)) = hash {
396                node_path.push(data[..len as usize].to_vec());
397            }
398
399            let root = match self
400                .root
401                .get_node_checked(self.db.as_ref(), Nibbles::default())?
402            {
403                Some(x) => x,
404                None => return Ok(Vec::new()),
405            };
406            root.get_path(self.db.as_ref(), Nibbles::from_bytes(path), &mut node_path)?;
407
408            Ok(node_path)
409        } else {
410            Ok(Vec::new())
411        }
412    }
413
414    /// Obtains all encoded nodes traversed until reaching the node where every path is stored.
415    /// The list doesn't include the root node, this is returned separately.
416    /// Will still be constructed even if some path is not stored in the trie.
417    pub fn get_proofs(
418        &self,
419        paths: &[PathRLP],
420    ) -> Result<(Option<NodeRLP>, Vec<NodeRLP>), TrieError> {
421        if self.root.is_valid() {
422            let encoded_root = self.get_root_node(Nibbles::default())?.encode_to_vec();
423
424            let mut node_path: FxHashSet<_> = Default::default();
425            for path in paths {
426                let mut nodes = self.get_proof(path)?;
427                nodes.swap_remove(0);
428                node_path.extend(nodes);
429            }
430
431            Ok((Some(encoded_root), node_path.into_iter().collect()))
432        } else {
433            Ok((None, Vec::new()))
434        }
435    }
436
437    #[cfg(feature = "std")]
438    pub fn empty_in_memory() -> Self {
439        Self::new(Box::new(InMemoryTrieDB::new(Arc::new(
440            std::sync::Mutex::new(BTreeMap::new()),
441        ))))
442    }
443
444    /// Gets node with embedded references to child nodes, all in just one `Node`.
445    pub fn get_embedded_root(
446        all_nodes: &BTreeMap<H256, Node>,
447        root_hash: H256,
448    ) -> Result<NodeRef, TrieError> {
449        // If the root hash is of the empty trie then we can get away by setting the NodeRef to default
450        if root_hash == EMPTY_TRIE_HASH {
451            return Ok(NodeRef::default());
452        }
453
454        let root_rlp = all_nodes.get(&root_hash).ok_or_else(|| {
455            TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(root_hash)))
456        })?;
457
458        fn get_embedded_node(
459            all_nodes: &BTreeMap<H256, Node>,
460            cur_node: &Node,
461        ) -> Result<Node, TrieError> {
462            Ok(match cur_node.clone() {
463                Node::Branch(mut node) => {
464                    for choice in &mut node.choices {
465                        let NodeRef::Hash(hash) = *choice else {
466                            continue;
467                        };
468
469                        if hash.is_valid() {
470                            *choice = match all_nodes.get(&hash.finalize(&NativeCrypto)) {
471                                Some(node) => get_embedded_node(all_nodes, node)?.into(),
472                                None => hash.into(),
473                            };
474                        }
475                    }
476
477                    (*node).into()
478                }
479                Node::Extension(mut node) => {
480                    let NodeRef::Hash(hash) = node.child else {
481                        return Ok(node.into());
482                    };
483
484                    node.child = match all_nodes.get(&hash.finalize(&NativeCrypto)) {
485                        Some(node) => get_embedded_node(all_nodes, node)?.into(),
486                        None => hash.into(),
487                    };
488
489                    node.into()
490                }
491                Node::Leaf(node) => node.into(),
492            })
493        }
494
495        let root = get_embedded_node(all_nodes, root_rlp)?;
496        Ok(root.into())
497    }
498
499    /// Gets node with embedded references to child nodes, all in just one `Node`.
500    ///
501    /// Note that this method caches the hash of each node, so it assumes the provided `all_nodes` are well-formed.
502    pub fn get_embedded_root_committed(
503        all_nodes: &FxHashMap<H256, Node>,
504        root_hash: H256,
505        crypto: &dyn Crypto,
506    ) -> Result<NodeRef, TrieError> {
507        // If the root hash is of the empty trie then we can get away by setting the NodeRef to default
508        if root_hash == EMPTY_TRIE_HASH {
509            return Ok(NodeRef::default());
510        }
511
512        let root_rlp = all_nodes.get(&root_hash).ok_or_else(|| {
513            TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFound(root_hash)))
514        })?;
515
516        /// Creates an embedded node reference with its hash slot pre-seeded.
517        ///
518        /// The caller must guarantee that `hash` is the hash of the referenced
519        /// node, for example because the node was just resolved by looking that
520        /// hash up. Seeding lets later hash computations over the subtree reuse
521        /// the known value instead of re-encoding and re-hashing it.
522        fn node_with_hash(node: Node, hash: NodeHash) -> NodeRef {
523            NodeRef::Node(Arc::new(node), OnceLock::from(hash))
524        }
525
526        fn get_embedded_node_committed(
527            all_nodes: &FxHashMap<H256, Node>,
528            cur_node: &Node,
529            crypto: &dyn Crypto,
530        ) -> Result<Node, TrieError> {
531            Ok(match cur_node.clone() {
532                Node::Branch(mut node) => {
533                    for choice in &mut node.choices {
534                        let NodeRef::Hash(hash) = *choice else {
535                            continue;
536                        };
537
538                        if hash.is_valid() {
539                            *choice = match all_nodes.get(&hash.finalize(crypto)) {
540                                Some(node) => node_with_hash(
541                                    get_embedded_node_committed(all_nodes, node, crypto)?,
542                                    hash,
543                                ),
544                                None => hash.into(),
545                            };
546                        }
547                    }
548
549                    (*node).into()
550                }
551                Node::Extension(mut node) => {
552                    let NodeRef::Hash(hash) = node.child else {
553                        return Ok(node.into());
554                    };
555
556                    node.child = match all_nodes.get(&hash.finalize(crypto)) {
557                        Some(node) => node_with_hash(
558                            get_embedded_node_committed(all_nodes, node, crypto)?,
559                            hash,
560                        ),
561                        None => hash.into(),
562                    };
563
564                    node.into()
565                }
566                Node::Leaf(node) => node.into(),
567            })
568        }
569
570        let root = get_embedded_node_committed(all_nodes, root_rlp, crypto)?;
571        Ok(node_with_hash(root, NodeHash::Hashed(root_hash)))
572    }
573
574    /// Builds a trie from a set of nodes with an empty InMemoryTrieDB as a backend because the nodes are embedded in the root.
575    ///
576    /// Note: This method will not ensure that all node references are valid. Invalid references
577    ///   will cause other methods (including, but not limited to `Trie::get`, `Trie::insert` and
578    ///   `Trie::remove`) to return `Err(InconsistentTrie)`.
579    /// Note: This method will ignore any dangling nodes. All nodes that are not accessible from the
580    ///   root node are considered dangling.
581    #[cfg(feature = "std")]
582    pub fn from_nodes(
583        root_hash: H256,
584        state_nodes: &BTreeMap<H256, Node>,
585    ) -> Result<Self, TrieError> {
586        let mut trie = Trie::new(Box::new(InMemoryTrieDB::default()));
587        let root = Self::get_embedded_root(state_nodes, root_hash)?;
588        trie.root = root;
589
590        Ok(trie)
591    }
592
593    /// Builds an in-memory trie from the given elements and returns its hash
594    pub fn compute_hash_from_unsorted_iter(
595        iter: impl Iterator<Item = (PathRLP, ValueRLP)>,
596        crypto: &dyn Crypto,
597    ) -> H256 {
598        let mut trie = Trie::stateless();
599        for (path, value) in iter {
600            // Unwraping here won't panic as our in_memory trie DB won't fail
601            trie.insert(path, value).unwrap();
602        }
603
604        trie.hash_no_commit(crypto)
605    }
606
607    /// 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
608    /// Only use it for proof verification or computing a hash from an iterator
609    pub(crate) fn stateless() -> Trie {
610        // We will only be using the trie's cache so we don't need a working DB
611        struct NullTrieDB;
612
613        impl TrieDB for NullTrieDB {
614            fn get(&self, _key: Nibbles) -> Result<Option<Vec<u8>>, TrieError> {
615                Ok(None)
616            }
617
618            fn put_batch(&self, _key_values: Vec<TrieNode>) -> Result<(), TrieError> {
619                Ok(())
620            }
621        }
622
623        Trie::new(Box::new(NullTrieDB))
624    }
625
626    /// Obtain the encoded node given its path.
627    /// Allows usage of full paths (byte slice of 32 bytes) or compact-encoded nibble slices (with length lower than 32)
628    pub fn get_node(&self, partial_path: &PathRLP) -> Result<Vec<u8>, TrieError> {
629        // Convert compact-encoded nibbles into a byte slice if necessary
630        let partial_path = match partial_path.len() {
631            // Compact-encoded nibbles
632            n if n < 32 => Nibbles::decode_compact(partial_path),
633            // Full path (No conversion needed)
634            32 => Nibbles::from_bytes(partial_path),
635            // We won't handle paths with length over 32
636            _ => return Ok(vec![]),
637        };
638
639        fn get_node_inner(
640            db: &dyn TrieDB,
641            current_path: Nibbles,
642            node: &Node,
643            mut partial_path: Nibbles,
644        ) -> Result<Vec<u8>, TrieError> {
645            // If we reached the end of the partial path, return the current node
646            if partial_path.is_empty() {
647                return Ok(node.encode_to_vec());
648            }
649            match node {
650                Node::Branch(branch_node) => match partial_path.next_choice() {
651                    Some(idx) => {
652                        let child_ref = &branch_node.choices[idx];
653                        if child_ref.is_valid() {
654                            let child_path = current_path.append_new(idx as u8);
655                            let child_node = child_ref
656                                .get_node_checked(db, child_path.clone())?
657                                .ok_or_else(|| {
658                                    TrieError::InconsistentTree(Box::new(
659                                        InconsistentTreeError::NodeNotFoundOnBranchNode(
660                                            child_ref
661                                                .compute_hash(&NativeCrypto)
662                                                .finalize(&NativeCrypto),
663                                            branch_node
664                                                .compute_hash(&NativeCrypto)
665                                                .finalize(&NativeCrypto),
666                                            child_path.clone(),
667                                        ),
668                                    ))
669                                })?;
670                            get_node_inner(db, child_path, &child_node, partial_path)
671                        } else {
672                            Ok(vec![])
673                        }
674                    }
675                    _ => Ok(vec![]),
676                },
677                Node::Extension(extension_node) => {
678                    if partial_path.skip_prefix(&extension_node.prefix)
679                        && extension_node.child.is_valid()
680                    {
681                        let child_path = partial_path.concat(&extension_node.prefix);
682                        let child_node = extension_node
683                            .child
684                            .get_node_checked(db, child_path.clone())?
685                            .ok_or_else(|| {
686                                TrieError::InconsistentTree(Box::new(
687                                    InconsistentTreeError::ExtensionNodeChildNotFound(
688                                        ExtensionNodeErrorData {
689                                            node_hash: extension_node
690                                                .child
691                                                .compute_hash(&NativeCrypto)
692                                                .finalize(&NativeCrypto),
693                                            extension_node_hash: extension_node
694                                                .compute_hash(&NativeCrypto)
695                                                .finalize(&NativeCrypto),
696                                            extension_node_prefix: extension_node.prefix.clone(),
697                                            node_path: child_path.clone(),
698                                        },
699                                    ),
700                                ))
701                            })?;
702                        get_node_inner(db, child_path, &child_node, partial_path)
703                    } else {
704                        Ok(vec![])
705                    }
706                }
707                Node::Leaf(_) => Ok(vec![]),
708            }
709        }
710
711        // Fetch node
712        if self.root.is_valid() {
713            let root_node = self.get_root_node(Default::default())?;
714            get_node_inner(
715                self.db.as_ref(),
716                Default::default(),
717                &root_node,
718                partial_path,
719            )
720        } else {
721            Ok(Vec::new())
722        }
723    }
724
725    pub fn root_node(&self) -> Result<Option<Arc<Node>>, TrieError> {
726        if self.root.is_valid() {
727            self.root.get_node(self.db.as_ref(), Nibbles::default())
728        } else {
729            Ok(None)
730        }
731    }
732
733    /// Creates a new Trie based on a temporary InMemory DB
734    pub fn new_temp() -> Self {
735        let db = InMemoryTrieDB::new(Default::default());
736        Trie::new(Box::new(db))
737    }
738
739    /// Creates a new Trie based on a temporary InMemory DB, with a specified root
740    ///
741    /// This is usually used to create a Trie from a root that was embedded with the rest of the nodes.
742    pub fn new_temp_with_root(root: NodeRef) -> Self {
743        let db = InMemoryTrieDB::new(Default::default());
744        let mut trie = Trie::new(Box::new(db));
745        trie.root = root;
746        trie
747    }
748
749    /// Validates that the Trie isn't missing any nodes expected in the branches
750    ///
751    /// This is used internally with debug assertions to check the status of the trie
752    /// after syncing operations.
753    /// Note: this operation validates the hashes because the iterator uses
754    /// get_node_checked. We shouldn't downgrade that to the unchecked version
755    pub fn validate(self) -> Result<(), TrieError> {
756        let mut expected_count = if self.root.is_valid() { 1 } else { 0 };
757        for (_, node) in self.into_iter() {
758            expected_count -= 1;
759            match node {
760                Node::Branch(branch_node) => {
761                    expected_count += branch_node
762                        .choices
763                        .iter()
764                        .filter(|child| child.is_valid())
765                        .count();
766                }
767                Node::Extension(_) => {
768                    expected_count += 1;
769                }
770                Node::Leaf(_) => {}
771            }
772        }
773        if expected_count != 0 {
774            return Err(TrieError::Verify(format!(
775                "Node count mismatch, expected {expected_count} more"
776            )));
777        }
778        Ok(())
779    }
780
781    /// Validate the trie structure in parallel by splitting at the root branch node.
782    /// Each of the root's 16 subtrees is validated independently using rayon.
783    #[cfg(feature = "std")]
784    pub fn validate_parallel(self) -> Result<(), TrieError> {
785        use rayon::prelude::*;
786
787        if !self.root.is_valid() {
788            return Ok(());
789        }
790
791        let db = &*self.db;
792        let root_node = self
793            .root
794            .get_node_checked(db, Nibbles::default())?
795            .ok_or_else(|| TrieError::Verify("Root node not found".to_string()))?;
796
797        match &*root_node {
798            Node::Branch(branch_node) => {
799                let children: Vec<(Nibbles, NodeRef)> = branch_node
800                    .choices
801                    .iter()
802                    .enumerate()
803                    .filter(|(_, child)| child.is_valid())
804                    .map(|(i, child)| {
805                        let path = Nibbles::default().append_new(i as u8);
806                        (path, child.clone())
807                    })
808                    .collect();
809
810                children.par_iter().try_for_each(|(start_path, start_ref)| {
811                    validate_subtree(db, start_path.clone(), start_ref.clone())
812                })
813            }
814            _ => {
815                // Non-branch root (rare): validate sequentially
816                validate_subtree(db, Nibbles::default(), self.root.clone())
817            }
818        }
819    }
820}
821
822/// Immutable BFS-boundary scan used by [`Trie::prefetch_sorted`]: given an
823/// already-resolved node and the set of key indices routed to it, finds every
824/// `NodeRef::Hash` child that lies on the path of at least one of those keys,
825/// stopping the recursion at the first unresolved node in each branch
826/// (already-resolved descendants, if any, are explored further in the same
827/// call). Mirrors the descent performed by `Node::get`/`BranchNode::insert`/
828/// `ExtensionNode::insert`.
829fn collect_prefetch_targets(
830    node: &Node,
831    path: &Nibbles,
832    key_indices: &[usize],
833    sorted_paths: &[Nibbles],
834    out: &mut Vec<Nibbles>,
835) {
836    match node {
837        Node::Branch(branch) => {
838            let depth = path.len();
839            let mut buckets: [Vec<usize>; 16] = core::array::from_fn(|_| Vec::new());
840            for &i in key_indices {
841                let p = sorted_paths[i].as_ref();
842                if depth >= p.len() {
843                    continue;
844                }
845                let nibble = p[depth];
846                if nibble < 16 {
847                    buckets[nibble as usize].push(i);
848                }
849            }
850            for (nibble, indices) in buckets.iter().enumerate() {
851                if indices.is_empty() {
852                    continue;
853                }
854                let child_path = path.append_new(nibble as u8);
855                match &branch.choices[nibble] {
856                    NodeRef::Hash(NodeHash::Hashed(_)) => out.push(child_path),
857                    NodeRef::Node(child, _) => {
858                        collect_prefetch_targets(child, &child_path, indices, sorted_paths, out)
859                    }
860                    // Inline (embedded, no separate disk node) or invalid/empty: nothing to fetch.
861                    _ => {}
862                }
863            }
864        }
865        Node::Extension(ext) => {
866            let depth = path.len();
867            let prefix = ext.prefix.as_ref();
868            let matched: Vec<usize> = key_indices
869                .iter()
870                .copied()
871                .filter(|&i| {
872                    let p = sorted_paths[i].as_ref();
873                    depth + prefix.len() <= p.len() && &p[depth..depth + prefix.len()] == prefix
874                })
875                .collect();
876            if matched.is_empty() {
877                // No key continues through this prefix: they diverge and will
878                // cause the extension to be restructured on insert; nothing
879                // to prefetch here.
880                return;
881            }
882            let child_path = path.concat(&ext.prefix);
883            match &ext.child {
884                NodeRef::Hash(NodeHash::Hashed(_)) => out.push(child_path),
885                NodeRef::Node(child, _) => {
886                    collect_prefetch_targets(child, &child_path, &matched, sorted_paths, out)
887                }
888                _ => {}
889            }
890        }
891        // Terminal: leaves have no children to prefetch.
892        Node::Leaf(_) => {}
893    }
894}
895
896/// Mutable counterpart to [`collect_prefetch_targets`] used by
897/// [`Trie::prefetch_sorted`]: repeats the identical descent/routing so it
898/// visits `NodeRef::Hash` children in the exact same order, installing the
899/// corresponding (same-order) `multi_get` result into the arena
900/// hash-preservingly, exactly as `NodeRef::get_node_mut` installs a single
901/// resolved node. On a `None`/empty result the ref is left untouched (absent
902/// path); the subsequent `insert` will create it.
903fn install_prefetch_targets(
904    node: &mut Node,
905    path: &Nibbles,
906    key_indices: &[usize],
907    sorted_paths: &[Nibbles],
908    results: &mut alloc::vec::IntoIter<Result<Option<Vec<u8>>, TrieError>>,
909) -> Result<(), TrieError> {
910    match node {
911        Node::Branch(branch) => {
912            let depth = path.len();
913            let mut buckets: [Vec<usize>; 16] = core::array::from_fn(|_| Vec::new());
914            for &i in key_indices {
915                let p = sorted_paths[i].as_ref();
916                if depth >= p.len() {
917                    continue;
918                }
919                let nibble = p[depth];
920                if nibble < 16 {
921                    buckets[nibble as usize].push(i);
922                }
923            }
924            for (nibble, indices) in buckets.iter().enumerate() {
925                if indices.is_empty() {
926                    continue;
927                }
928                let child_path = path.append_new(nibble as u8);
929                let slot = &mut branch.choices[nibble];
930                match slot {
931                    NodeRef::Hash(hash @ NodeHash::Hashed(_)) => {
932                        let hash = *hash;
933                        let bytes = results.next().ok_or_else(|| {
934                            TrieError::DbError(anyhow::anyhow!(
935                                "prefetch_sorted: multi_get returned fewer results than requested"
936                            ))
937                        })??;
938                        if let Some(bytes) = bytes.filter(|b| !b.is_empty()) {
939                            let decoded = Node::decode(&bytes).map_err(TrieError::RLPDecode)?;
940                            *slot = NodeRef::Node(Arc::new(decoded), OnceLock::from(hash));
941                        }
942                    }
943                    NodeRef::Node(child, _) => {
944                        let child_mut = Arc::make_mut(child);
945                        install_prefetch_targets(
946                            child_mut,
947                            &child_path,
948                            indices,
949                            sorted_paths,
950                            results,
951                        )?;
952                    }
953                    _ => {}
954                }
955            }
956        }
957        Node::Extension(ext) => {
958            let depth = path.len();
959            let prefix = ext.prefix.as_ref();
960            let matched: Vec<usize> = key_indices
961                .iter()
962                .copied()
963                .filter(|&i| {
964                    let p = sorted_paths[i].as_ref();
965                    depth + prefix.len() <= p.len() && &p[depth..depth + prefix.len()] == prefix
966                })
967                .collect();
968            if matched.is_empty() {
969                return Ok(());
970            }
971            let child_path = path.concat(&ext.prefix);
972            let slot = &mut ext.child;
973            match slot {
974                NodeRef::Hash(hash @ NodeHash::Hashed(_)) => {
975                    let hash = *hash;
976                    let bytes = results.next().ok_or_else(|| {
977                        TrieError::DbError(anyhow::anyhow!(
978                            "prefetch_sorted: multi_get returned fewer results than requested"
979                        ))
980                    })??;
981                    if let Some(bytes) = bytes.filter(|b| !b.is_empty()) {
982                        let decoded = Node::decode(&bytes).map_err(TrieError::RLPDecode)?;
983                        *slot = NodeRef::Node(Arc::new(decoded), OnceLock::from(hash));
984                    }
985                }
986                NodeRef::Node(child, _) => {
987                    let child_mut = Arc::make_mut(child);
988                    install_prefetch_targets(
989                        child_mut,
990                        &child_path,
991                        &matched,
992                        sorted_paths,
993                        results,
994                    )?;
995                }
996                _ => {}
997            }
998        }
999        Node::Leaf(_) => {}
1000    }
1001    Ok(())
1002}
1003
1004/// Validate a subtree rooted at `start_ref`, checking that all referenced nodes exist
1005/// and their hashes match.
1006#[cfg(feature = "std")]
1007fn validate_subtree(
1008    db: &dyn TrieDB,
1009    start_path: Nibbles,
1010    start_ref: NodeRef,
1011) -> Result<(), TrieError> {
1012    let mut expected_count: isize = 1;
1013    let mut stack = vec![(start_path, start_ref)];
1014
1015    while let Some((path, node_ref)) = stack.pop() {
1016        let node = node_ref
1017            .get_node_checked(db, path.clone())?
1018            .ok_or_else(|| TrieError::Verify(format!("Missing node at path {path:?}")))?;
1019
1020        expected_count -= 1;
1021        match &*node {
1022            Node::Branch(branch) => {
1023                for (choice, child) in branch.choices.iter().enumerate().rev() {
1024                    if child.is_valid() {
1025                        expected_count += 1;
1026                        stack.push((path.append_new(choice as u8), child.clone()));
1027                    }
1028                }
1029            }
1030            Node::Extension(ext) => {
1031                expected_count += 1;
1032                stack.push((path.concat(&ext.prefix), ext.child.clone()));
1033            }
1034            Node::Leaf(_) => {}
1035        }
1036    }
1037
1038    if expected_count != 0 {
1039        return Err(TrieError::Verify(format!(
1040            "Node count mismatch in subtree, expected {expected_count} more"
1041        )));
1042    }
1043    Ok(())
1044}
1045
1046impl IntoIterator for Trie {
1047    type Item = (Nibbles, Node);
1048
1049    type IntoIter = TrieIterator;
1050
1051    fn into_iter(self) -> Self::IntoIter {
1052        TrieIterator::new(self)
1053    }
1054}
1055
1056pub struct ProofTrie(Trie);
1057
1058impl ProofTrie {
1059    pub fn insert(
1060        &mut self,
1061        partial_path: Nibbles,
1062        external_ref: NodeHash,
1063    ) -> Result<(), TrieError> {
1064        if self.0.root.is_valid() {
1065            // If the trie is not empty, call the root node's insertion logic.
1066            self.0
1067                .root
1068                .get_node_mut(self.0.db.as_ref(), Nibbles::default())?
1069                .ok_or_else(|| {
1070                    TrieError::InconsistentTree(Box::new(InconsistentTreeError::RootNotFoundNoHash))
1071                })?
1072                .insert(self.0.db.as_ref(), partial_path, external_ref)?;
1073            self.0.root.clear_hash();
1074        } else {
1075            self.0.root = external_ref.into();
1076        };
1077
1078        Ok(())
1079    }
1080
1081    pub fn hash(&self, crypto: &dyn Crypto) -> H256 {
1082        self.0.hash_no_commit(crypto)
1083    }
1084}
1085
1086impl From<Trie> for ProofTrie {
1087    fn from(value: Trie) -> Self {
1088        Self(value)
1089    }
1090}
1091
1092#[cfg(test)]
1093mod tests {
1094    use super::*;
1095    use ethrex_crypto::keccak::keccak_hash;
1096
1097    // Pins the hardcoded `EMPTY_TRIE_HASH` to `keccak256(RLP_NULL)` so a typo in the
1098    // byte literal can't silently diverge from the computed value.
1099    #[test]
1100    fn empty_trie_hash_matches_keccak() {
1101        assert_eq!(EMPTY_TRIE_HASH, H256(keccak_hash([RLP_NULL])));
1102    }
1103}