From 41a2d0f005a4580bfbbf44f988f6011faa5cb9c8 Mon Sep 17 00:00:00 2001 From: kevaundray Date: Tue, 6 May 2025 15:33:48 +0100 Subject: [PATCH] chore: add more docs to SparseTrie (#15750) Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- crates/trie/sparse/src/trie.rs | 385 +++++++++++++++++++++++++++------ 1 file changed, 323 insertions(+), 62 deletions(-) diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index de2bebd2f4..472dc70693 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -22,12 +22,26 @@ use reth_trie_common::{ use smallvec::SmallVec; use tracing::trace; -/// Struct for passing around `hash_mask` and `tree_mask` +/// Struct for passing around branch node mask information. +/// +/// Branch nodes can have up to 16 children (one for each nibble). +/// The masks represent which children are stored in different ways: +/// - `hash_mask`: Indicates which children are stored as hashes in the database +/// - `tree_mask`: Indicates which children are complete subtrees stored in the database +/// +/// These masks are essential for efficient trie traversal and serialization, as they +/// determine how nodes should be encoded and stored on disk. #[derive(Debug)] pub struct TrieMasks { /// Branch node hash mask, if any. + /// + /// When a bit is set, the corresponding child node's hash is stored in the trie. + /// + /// This mask enables selective hashing of child nodes. pub hash_mask: Option, /// Branch node tree mask, if any. + /// + /// When a bit is set, the corresponding child subtree is stored in the database. pub tree_mask: Option, } @@ -38,13 +52,31 @@ impl TrieMasks { } } -/// Inner representation of the sparse trie. -/// Sparse trie is blind by default until nodes are revealed. -#[derive(PartialEq, Eq)] +/// A sparse trie that is either in a "blind" state (no nodes are revealed, root node hash is +/// unknown) or in a "revealed" state (root node has been revealed and the trie can be updated). +/// +/// In blind mode the trie does not contain any decoded node data, which saves memory but +/// prevents direct access to node contents. The revealed mode stores decoded nodes along +/// with additional information such as values, allowing direct manipulation. +/// +/// The sparse trie design is optimised for: +/// 1. Memory efficiency - only revealed nodes are loaded into memory +/// 2. Update tracking - changes to the trie structure can be tracked and selectively persisted +/// 3. Incremental operations - nodes can be revealed as needed without loading the entire trie. +/// This is what gives rise to the notion of a "sparse" trie. +#[derive(PartialEq, Eq, Default)] pub enum SparseTrie

{ - /// None of the trie nodes are known. + /// The trie is blind -- no nodes have been revealed + /// + /// This is the default state. In this state, + /// the trie cannot be directly queried or modified until nodes are revealed. + #[default] Blind, - /// The trie nodes have been revealed. + /// Some nodes in the Trie have been revealed. + /// + /// In this state, the trie can be queried and modified for the parts + /// that have been revealed. Other parts remain blind and require revealing + /// before they can be accessed. Revealed(Box>), } @@ -57,28 +89,48 @@ impl

fmt::Debug for SparseTrie

{ } } -impl

Default for SparseTrie

{ - fn default() -> Self { - Self::Blind - } -} - impl SparseTrie { - /// Creates new blind trie. + /// Creates a new blind sparse trie. + /// + /// # Examples + /// + /// ``` + /// use reth_trie_sparse::{blinded::DefaultBlindedProvider, SparseTrie}; + /// + /// let trie: SparseTrie = SparseTrie::blind(); + /// assert!(trie.is_blind()); + /// let trie: SparseTrie = SparseTrie::default(); + /// assert!(trie.is_blind()); + /// ``` pub const fn blind() -> Self { Self::Blind } - /// Creates new revealed empty trie. + /// Creates a new revealed but empty sparse trie with `SparseNode::Empty` as root node. + /// + /// # Examples + /// + /// ``` + /// use reth_trie_sparse::{blinded::DefaultBlindedProvider, SparseTrie}; + /// + /// let trie: SparseTrie = SparseTrie::revealed_empty(); + /// assert!(!trie.is_blind()); + /// ``` pub fn revealed_empty() -> Self { Self::Revealed(Box::default()) } - /// Reveals the root node if the trie is blinded. + /// Reveals the root node, converting a blind trie into a revealed one. + /// + /// If the trie is blinded, its root node is replaced with `root`. + /// + /// The `masks` are used to determine how the node's children are stored. + /// The `retain_updates` flag controls whether changes to the trie structure + /// should be tracked. /// /// # Returns /// - /// Mutable reference to [`RevealedSparseTrie`]. + /// A mutable reference to the underlying [`RevealedSparseTrie`]. pub fn reveal_root( &mut self, root: TrieNode, @@ -95,7 +147,9 @@ impl

SparseTrie

{ matches!(self, Self::Blind) } - /// Returns reference to revealed sparse trie if the trie is not blind. + /// Returns an immutable reference to the underlying revealed sparse trie. + /// + /// Returns `None` if the trie is blinded. pub const fn as_revealed_ref(&self) -> Option<&RevealedSparseTrie

> { if let Self::Revealed(revealed) = self { Some(revealed) @@ -104,7 +158,9 @@ impl

SparseTrie

{ } } - /// Returns mutable reference to revealed sparse trie if the trie is not blind. + /// Returns a mutable reference to the underlying revealed sparse trie. + /// + /// Returns `None` if the trie is blinded. pub fn as_revealed_mut(&mut self) -> Option<&mut RevealedSparseTrie

> { if let Self::Revealed(revealed) = self { Some(revealed) @@ -113,7 +169,10 @@ impl

SparseTrie

{ } } - /// Reveals the root node if the trie is blinded. + /// Reveals the root node using a specified provider. + /// + /// This function is similar to [`Self::reveal_root`] but allows the caller to provide + /// a custom provider for fetching blinded nodes. /// /// # Returns /// @@ -136,19 +195,42 @@ impl

SparseTrie

{ Ok(self.as_revealed_mut().unwrap()) } - /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. + /// Wipes the trie by removing all nodes and values, + /// and resetting the trie to only contain an empty root node. + /// + /// Note: This method will error if the trie is blinded. pub fn wipe(&mut self) -> SparseTrieResult<()> { let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?; revealed.wipe(); Ok(()) } - /// Calculates and returns the trie root if the trie has been revealed. + /// Calculates the root hash of the trie. + /// + /// This will update any remaining dirty nodes before computing the root hash. + /// "dirty" nodes are nodes that need their hashes to be recomputed because one or more of their + /// children's hashes have changed. + /// + /// # Returns + /// + /// - `Some(B256)` with the calculated root hash if the trie is revealed. + /// - `None` if the trie is still blind. pub fn root(&mut self) -> Option { Some(self.as_revealed_mut()?.root()) } - /// Returns both the trie root and takes sparse trie updates if the trie has been revealed. + /// Returns the root hash along with any accumulated update information. + /// + /// This is useful for when you need both the root hash and information about + /// what nodes were modified, which can be used to efficiently update + /// an external database. + /// + /// # Returns + /// + /// An `Option` tuple consisting of: + /// - The trie root hash (`B256`). + /// - A [`SparseTrieUpdates`] structure containing information about updated nodes. + /// - `None` if the trie is still blind. pub fn root_with_updates(&mut self) -> Option<(B256, SparseTrieUpdates)> { let revealed = self.as_revealed_mut()?; Some((revealed.root(), revealed.take_updates())) @@ -156,14 +238,22 @@ impl

SparseTrie

{ } impl SparseTrie

{ - /// Update the leaf node. + /// Updates (or inserts) a leaf at the given key path with the specified RLP-encoded value. + /// + /// # Errors + /// + /// Returns an error if the trie is still blind, or if the update fails. pub fn update_leaf(&mut self, path: Nibbles, value: Vec) -> SparseTrieResult<()> { let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?; revealed.update_leaf(path, value)?; Ok(()) } - /// Remove the leaf node. + /// Removes a leaf node at the specified key path. + /// + /// # Errors + /// + /// Returns an error if the trie is still blind, or if the leaf cannot be removed pub fn remove_leaf(&mut self, path: &Nibbles) -> SparseTrieResult<()> { let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?; revealed.remove_leaf(path)?; @@ -173,6 +263,11 @@ impl SparseTrie

{ /// The representation of revealed sparse trie. /// +/// The revealed sparse trie contains the actual trie structure with nodes, values, and +/// tracking for changes. It supports operations like inserting, updating, and removing +/// nodes. +/// +/// /// ## Invariants /// /// - The root node is always present in `nodes` collection. @@ -181,19 +276,23 @@ impl SparseTrie

{ /// - All keys in `values` collection are full leaf paths. #[derive(Clone, PartialEq, Eq)] pub struct RevealedSparseTrie

{ - /// Blinded node provider. + /// Provider used for retrieving blinded nodes. + /// This allows lazily loading parts of the trie from an external source. provider: P, - /// All trie nodes. + /// Map from a path (nibbles) to its corresponding sparse trie node. + /// This contains all of the revealed nodes in trie. nodes: HashMap, - /// All branch node tree masks. + /// When a branch is set, the corresponding child subtree is stored in the database. branch_node_tree_masks: HashMap, - /// All branch node hash masks. + /// When a bit is set, the corresponding child is stored as a hash in the database. branch_node_hash_masks: HashMap, - /// All leaf values. + /// Map from leaf key paths to their values. + /// All values are stored here instead of directly in leaf nodes. values: HashMap>, - /// Prefix set. + /// Set of prefixes (key paths) that have been marked as updated. + /// This is used to track which parts of the trie need to be recalculated. prefix_set: PrefixSetMut, - /// Retained trie updates. + /// Optional tracking of trie updates for later use. updates: Option, /// Reusable buffer for RLP encoding of nodes. rlp_buf: Vec, @@ -301,9 +400,17 @@ impl Default for RevealedSparseTrie { } impl RevealedSparseTrie { - /// Create new revealed sparse trie from the given root node. + /// Creates a new revealed sparse trie from the given root node. + /// + /// This function initializes the internal structures and then reveals the root. + /// It is a convenient method to create a [`RevealedSparseTrie`] when you already have + /// the root node available. + /// + /// # Returns + /// + /// A [`RevealedSparseTrie`] if successful, or an error if revealing fails. pub fn from_root( - node: TrieNode, + root: TrieNode, masks: TrieMasks, retain_updates: bool, ) -> SparseTrieResult { @@ -318,13 +425,20 @@ impl RevealedSparseTrie { updates: None, } .with_updates(retain_updates); - this.reveal_node(Nibbles::default(), node, masks)?; + this.reveal_node(Nibbles::default(), root, masks)?; Ok(this) } } impl

RevealedSparseTrie

{ - /// Create new revealed sparse trie from the given root node. + /// Creates a new revealed sparse trie from the given provider and root node. + /// + /// Similar to `from_root`, but allows specifying a custom provider for + /// retrieving blinded nodes. + /// + /// # Returns + /// + /// A [`RevealedSparseTrie`] if successful, or an error if revealing fails. pub fn from_provider_and_root( provider: P, node: TrieNode, @@ -346,7 +460,14 @@ impl

RevealedSparseTrie

{ Ok(this) } - /// Set new blinded node provider on sparse trie. + /// Replaces the current provider with a new provider. + /// + /// This allows changing how blinded nodes are retrieved without + /// rebuilding the entire trie structure. + /// + /// # Returns + /// + /// A new [`RevealedSparseTrie`] with the updated provider. pub fn with_provider(self, provider: BP) -> RevealedSparseTrie { RevealedSparseTrie { provider, @@ -360,7 +481,10 @@ impl

RevealedSparseTrie

{ } } - /// Set the retention of branch node updates and deletions. + /// Configures the trie to retain information about updates. + /// + /// If `retain_updates` is true, the trie will record branch node updates and deletions. + /// This information can then be used to efficiently update an external database. pub fn with_updates(mut self, retain_updates: bool) -> Self { if retain_updates { self.updates = Some(SparseTrieUpdates::default()); @@ -368,32 +492,56 @@ impl

RevealedSparseTrie

{ self } - /// Returns a reference to the retained sparse node updates without taking them. + /// Returns a reference to the current sparse trie updates. + /// + /// If no updates have been made/recorded, returns an empty update set. pub fn updates_ref(&self) -> Cow<'_, SparseTrieUpdates> { self.updates.as_ref().map_or(Cow::Owned(SparseTrieUpdates::default()), Cow::Borrowed) } - /// Returns reference to all trie nodes. + /// Returns an immutable reference to all nodes in the sparse trie. pub const fn nodes_ref(&self) -> &HashMap { &self.nodes } - /// Returns a reference to the leaf value if present. + /// Retrieves a reference to the leaf value stored at the given key path, if it is revealed. + /// + /// This method efficiently retrieves values from the trie without traversing + /// the entire node structure, as values are stored in a separate map. + /// + /// Note: a value can exist in the full trie and this function still returns `None` + /// because the value has not been revealed. + /// Hence a `None` indicates two possibilities: + /// - The value does not exists in the trie, so it cannot be revealed + /// - The value has not yet been revealed. In order to determine which is true, one would need + /// an exclusion proof. pub fn get_leaf_value(&self, path: &Nibbles) -> Option<&Vec> { self.values.get(path) } - /// Takes and returns the retained sparse node updates + /// Consumes and returns the currently accumulated trie updates. + /// + /// This is useful when you want to apply the updates to an external database, + /// and then start tracking a new set of updates. pub fn take_updates(&mut self) -> SparseTrieUpdates { self.updates.take().unwrap_or_default() } - /// Reserves capacity for at least `additional` more nodes to be inserted. + /// Reserves capacity in the nodes map for at least `additional` more nodes. pub fn reserve_nodes(&mut self, additional: usize) { self.nodes.reserve(additional); } - /// Reveal the trie node only if it was not known already. + /// Reveals a trie node if it has not been revealed before. + /// + /// This internal function decodes a trie node and inserts it into the nodes map. + /// It handles different node types (leaf, extension, branch) by appropriately + /// adding them to the trie structure and recursively revealing their children. + /// + /// + /// # Returns + /// + /// `Ok(())` if successful, or an error if node was not revealed. pub fn reveal_node( &mut self, path: Nibbles, @@ -414,23 +562,27 @@ impl

RevealedSparseTrie

{ match node { TrieNode::EmptyRoot => { + // For an empty root, ensure that we are at the root path. debug_assert!(path.is_empty()); self.nodes.insert(path, SparseNode::Empty); } TrieNode::Branch(branch) => { + // For a branch node, iterate over all potential children let mut stack_ptr = branch.as_ref().first_child_index(); for idx in CHILD_INDEX_RANGE { if branch.state_mask.is_bit_set(idx) { let mut child_path = path.clone(); child_path.push_unchecked(idx); + // Reveal each child node or hash it has self.reveal_node_or_hash(child_path, &branch.stack[stack_ptr])?; stack_ptr += 1; } } - + // Update the branch node entry in the nodes map, handling cases where a blinded + // node is now replaced with a revealed node. match self.nodes.entry(path) { Entry::Occupied(mut entry) => match entry.get() { - // Blinded nodes can be replaced. + // Replace a hash node with a fully revealed branch node. SparseNode::Hash(hash) => { entry.insert(SparseNode::Branch { state_mask: branch.state_mask, @@ -462,6 +614,7 @@ impl

RevealedSparseTrie

{ } TrieNode::Extension(ext) => match self.nodes.entry(path) { Entry::Occupied(mut entry) => match entry.get() { + // Replace a hash node with a revealed extension node. SparseNode::Hash(hash) => { let mut child_path = entry.key().clone(); child_path.extend_from_slice_unchecked(&ext.key); @@ -495,6 +648,7 @@ impl

RevealedSparseTrie

{ }, TrieNode::Leaf(leaf) => match self.nodes.entry(path) { Entry::Occupied(mut entry) => match entry.get() { + // Replace a hash node with a revealed leaf node and store leaf node value. SparseNode::Hash(hash) => { let mut full = entry.key().clone(); full.extend_from_slice_unchecked(&leaf.key); @@ -531,6 +685,24 @@ impl

RevealedSparseTrie

{ Ok(()) } + /// Reveals either a node or its hash placeholder based on the provided child data. + /// + /// When traversing the trie, we often encounter references to child nodes that + /// are either directly embedded or represented by their hash. This method + /// handles both cases: + /// + /// 1. If the child data represents a hash (32+1=33 bytes), store it as a hash node + /// 2. Otherwise, decode the data as a [`TrieNode`] and recursively reveal it using + /// `reveal_node` + /// + /// # Returns + /// + /// Returns `Ok(())` if successful, or an error if the node cannot be revealed. + /// + /// # Error Handling + /// + /// Will error if there's a conflict between a new hash node and an existing one + /// at the same path fn reveal_node_or_hash(&mut self, path: Nibbles, child: &[u8]) -> SparseTrieResult<()> { if child.len() == B256::len_bytes() + 1 { let hash = B256::from_slice(&child[1..]); @@ -556,7 +728,22 @@ impl

RevealedSparseTrie

{ self.reveal_node(path, TrieNode::decode(&mut &child[..])?, TrieMasks::none()) } - /// Traverse trie nodes down to the leaf node and collect all nodes along the path. + /// Traverse the trie from the root down to the leaf at the given path, + /// removing and collecting all nodes along that path. + /// + /// This helper function is used during leaf removal to extract the nodes of the trie + /// that will be affected by the deletion. These nodes are then re-inserted and modified + /// as needed (collapsing extension nodes etc) given that the leaf has now been removed. + /// + /// # Returns + /// + /// Returns a vector of [`RemovedSparseNode`] representing the nodes removed during the + /// traversal. + /// + /// # Errors + /// + /// Returns an error if a blinded node or an empty node is encountered unexpectedly, + /// as these prevent proper removal of the leaf. fn take_nodes_for_path(&mut self, path: &Nibbles) -> SparseTrieResult> { let mut current = Nibbles::default(); // Start traversal from the root let mut nodes = Vec::new(); // Collect traversed nodes @@ -641,7 +828,10 @@ impl

RevealedSparseTrie

{ Ok(nodes) } - /// Wipe the trie, removing all values and nodes, and replacing the root with an empty node. + /// Removes all nodes and values from the trie, resetting it to a blank state + /// with only an empty root node. + /// + /// Note: All previously tracked changes to the trie are also removed. pub fn wipe(&mut self) { self.nodes = HashMap::from_iter([(Nibbles::default(), SparseNode::Empty)]); self.values = HashMap::default(); @@ -649,8 +839,12 @@ impl

RevealedSparseTrie

{ self.updates = self.updates.is_some().then(SparseTrieUpdates::wiped); } - /// Return the root of the sparse trie. - /// Updates all remaining dirty nodes before calculating the root. + /// Calculates and returns the root hash of the trie. + /// + /// Before computing the hash, this function processes any remaining (dirty) nodes by + /// updating their RLP encodings. The root hash is either: + /// 1. The cached hash (if no dirty nodes were found) + /// 2. The keccak256 hash of the root node's RLP representation pub fn root(&mut self) -> B256 { // Take the current prefix set let mut prefix_set = core::mem::take(&mut self.prefix_set).freeze(); @@ -662,8 +856,14 @@ impl

RevealedSparseTrie

{ } } - /// Update hashes of the nodes that are located at a level deeper than or equal to the provided - /// depth. Root node has a level of 0. + /// Recalculates and updates the RLP hashes of nodes deeper than or equal to the specified + /// `depth`. + /// + /// The root node is considered to be at level 0. This method is useful for optimizing + /// hash recalculations after localized changes to the trie structure: + /// + /// This function identifies all nodes that have changed (based on the prefix set) at the given + /// depth and recalculates their RLP representation. pub fn update_rlp_node_level(&mut self, depth: usize) { // Take the current prefix set let mut prefix_set = core::mem::take(&mut self.prefix_set).freeze(); @@ -685,12 +885,27 @@ impl

RevealedSparseTrie

{ } } - /// Returns a list of levels and paths to the nodes that were changed according to the prefix - /// set and are located at the provided depth when counting from the root node. If there's a - /// leaf at a depth less than the provided depth, it will be included in the result. + /// Returns a list of (level, path) tuples identifying the nodes that have changed at the + /// specified depth, along with a new prefix set for the paths above the provided depth that + /// remain unchanged. /// - /// Additionally, returns a new prefix set containing the paths that will not be updated, thus - /// need re-calculation. + /// Leaf nodes with a depth less than `depth` are returned too. + /// + /// This method helps optimize hash recalculations by identifying which specific + /// nodes need to be updated at each level of the trie. + /// + /// # Parameters + /// + /// - `prefix_set`: The current prefix set tracking which paths need updates. + /// - `depth`: The minimum depth (relative to the root) to include nodes in the targets. + /// + /// # Returns + /// + /// A tuple containing: + /// - A vector of `(level, Nibbles)` pairs for nodes that require updates at or below the + /// specified depth. + /// - A `PrefixSetMut` containing paths shallower than the specified depth that still need to be + /// tracked for future updates. fn get_changed_nodes_at_depth( &self, prefix_set: &mut PrefixSet, @@ -759,7 +974,16 @@ impl

RevealedSparseTrie

{ self.rlp_node(prefix_set, &mut buffers) } - /// Look up or calculate the RLP of the node at the given path specified in [`RlpNodeBuffers`]. + /// Looks up or computes the RLP encoding of the node specified by the current + /// path in the provided buffers. + /// + /// The function uses a stack (`RlpNodeBuffers::path_stack`) to track the traversal and + /// accumulate RLP encodings. + /// + /// # Parameters + /// + /// - `prefix_set`: The set of trie paths that need their nodes updated. + /// - `buffers`: The reusable buffers for stack management and temporary RLP values. /// /// # Panics /// @@ -1240,7 +1464,19 @@ impl RevealedSparseTrie

{ Ok(LeafLookup::NonExistent { diverged_at: current }) } - /// Update the leaf node with provided value. + /// Updates or inserts a leaf node at the specified key path with the provided RLP-encoded + /// value. + /// + /// This method updates the internal prefix set and, if the leaf did not previously exist, + /// adjusts the trie structure by inserting new leaf nodes, splitting branch nodes, or + /// collapsing extension nodes as needed. + /// + /// # Returns + /// + /// Returns `Ok(())` if the update is successful. + /// + /// Note: If an update requires revealing a blinded node, an error is returned if the blinded + /// provider returns an error. pub fn update_leaf(&mut self, path: Nibbles, value: Vec) -> SparseTrieResult<()> { self.prefix_set.insert(path.clone()); let existing = self.values.insert(path.clone(), value); @@ -1361,7 +1597,15 @@ impl RevealedSparseTrie

{ Ok(()) } - /// Remove leaf node from the trie. + /// Removes a leaf node from the trie at the specified key path. + /// + /// This function removes the leaf value from the internal values map and then traverses + /// the trie to remove or adjust intermediate nodes, merging or collapsing them as necessary. + /// + /// # Returns + /// + /// Returns `Ok(())` if the leaf is successfully removed, otherwise returns an error + /// if the leaf is not present or if a blinded node prevents removal. pub fn remove_leaf(&mut self, path: &Nibbles) -> SparseTrieResult<()> { if self.values.remove(path).is_none() { if let Some(&SparseNode::Hash(hash)) = self.nodes.get(path) { @@ -1564,7 +1808,7 @@ impl RevealedSparseTrie

{ enum SparseNodeType { /// Empty trie node. Empty, - /// The hash of the node that was not revealed. + /// A placeholder that stores only the hash for a node that has not been fully revealed. Hash, /// Sparse leaf node. Leaf, @@ -1687,14 +1931,28 @@ impl SparseNode { } } +/// A helper struct used to store information about a node that has been removed +/// during a deletion operation. #[derive(Debug)] struct RemovedSparseNode { + /// The path at which the node was located. path: Nibbles, + /// The removed node node: SparseNode, + /// For branch nodes, an optional nibble that should be unset due to the node being removed. + /// + /// During leaf deletion, this identifies the specific branch nibble path that + /// connects to the leaf being deleted. Then when restructuring the trie after deletion, + /// this nibble position will be cleared from the branch node's to + /// indicate that the child no longer exists. + /// + /// This is only set for branch nodes that have a direct path to the leaf being deleted. unset_branch_nibble: Option, } -/// Collection of reusable buffers for [`RevealedSparseTrie::rlp_node`]. +/// Collection of reusable buffers for [`RevealedSparseTrie::rlp_node`] calculations. +/// +/// These buffers reduce allocations when computing RLP representations during trie updates. #[derive(Debug, Default)] pub struct RlpNodeBuffers { /// Stack of RLP node paths @@ -1745,7 +2003,10 @@ struct RlpNodeStackItem { node_type: SparseNodeType, } -/// The aggregation of sparse trie updates. +/// Tracks modifications to the sparse trie structure. +/// +/// Maintains references to both modified and pruned/removed branches, enabling +/// one to make batch updates to a persistent database. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct SparseTrieUpdates { pub(crate) updated_nodes: HashMap,