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>
This commit is contained in:
kevaundray
2025-05-06 15:33:48 +01:00
committed by GitHub
parent 39305dda1c
commit 41a2d0f005

View File

@@ -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<TrieMask>,
/// Branch node tree mask, if any.
///
/// When a bit is set, the corresponding child subtree is stored in the database.
pub tree_mask: Option<TrieMask>,
}
@@ -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<P = DefaultBlindedProvider> {
/// 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<RevealedSparseTrie<P>>),
}
@@ -57,28 +89,48 @@ impl<P> fmt::Debug for SparseTrie<P> {
}
}
impl<P> Default for SparseTrie<P> {
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<DefaultBlindedProvider> = SparseTrie::blind();
/// assert!(trie.is_blind());
/// let trie: SparseTrie<DefaultBlindedProvider> = 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<DefaultBlindedProvider> = 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<P> SparseTrie<P> {
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<P>> {
if let Self::Revealed(revealed) = self {
Some(revealed)
@@ -104,7 +158,9 @@ impl<P> SparseTrie<P> {
}
}
/// 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<P>> {
if let Self::Revealed(revealed) = self {
Some(revealed)
@@ -113,7 +169,10 @@ impl<P> SparseTrie<P> {
}
}
/// 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<P> SparseTrie<P> {
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<B256> {
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<P> SparseTrie<P> {
}
impl<P: BlindedProvider> SparseTrie<P> {
/// 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<u8>) -> 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<P: BlindedProvider> SparseTrie<P> {
/// 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<P: BlindedProvider> SparseTrie<P> {
/// - All keys in `values` collection are full leaf paths.
#[derive(Clone, PartialEq, Eq)]
pub struct RevealedSparseTrie<P = DefaultBlindedProvider> {
/// 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<Nibbles, SparseNode>,
/// All branch node tree masks.
/// When a branch is set, the corresponding child subtree is stored in the database.
branch_node_tree_masks: HashMap<Nibbles, TrieMask>,
/// 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<Nibbles, TrieMask>,
/// All leaf values.
/// Map from leaf key paths to their values.
/// All values are stored here instead of directly in leaf nodes.
values: HashMap<Nibbles, Vec<u8>>,
/// 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<SparseTrieUpdates>,
/// Reusable buffer for RLP encoding of nodes.
rlp_buf: Vec<u8>,
@@ -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<Self> {
@@ -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<P> RevealedSparseTrie<P> {
/// 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<P> RevealedSparseTrie<P> {
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<BP>(self, provider: BP) -> RevealedSparseTrie<BP> {
RevealedSparseTrie {
provider,
@@ -360,7 +481,10 @@ impl<P> RevealedSparseTrie<P> {
}
}
/// 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<P> RevealedSparseTrie<P> {
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<Nibbles, SparseNode> {
&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<u8>> {
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<P> RevealedSparseTrie<P> {
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<P> RevealedSparseTrie<P> {
}
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<P> RevealedSparseTrie<P> {
},
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<P> RevealedSparseTrie<P> {
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<P> RevealedSparseTrie<P> {
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<Vec<RemovedSparseNode>> {
let mut current = Nibbles::default(); // Start traversal from the root
let mut nodes = Vec::new(); // Collect traversed nodes
@@ -641,7 +828,10 @@ impl<P> RevealedSparseTrie<P> {
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<P> RevealedSparseTrie<P> {
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<P> RevealedSparseTrie<P> {
}
}
/// 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<P> RevealedSparseTrie<P> {
}
}
/// 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<P> RevealedSparseTrie<P> {
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<P: BlindedProvider> RevealedSparseTrie<P> {
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<u8>) -> SparseTrieResult<()> {
self.prefix_set.insert(path.clone());
let existing = self.values.insert(path.clone(), value);
@@ -1361,7 +1597,15 @@ impl<P: BlindedProvider> RevealedSparseTrie<P> {
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<P: BlindedProvider> RevealedSparseTrie<P> {
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<u8>,
}
/// 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<Nibbles, BranchNodeCompact>,