mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-19 03:04:27 -05:00
fix(trie): subtrie root node too small to have hash (#22114)
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com> Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
5
.changelog/calm-clams-buzz.md
Normal file
5
.changelog/calm-clams-buzz.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
reth-trie-sparse: patch
|
||||
---
|
||||
|
||||
Refactored sparse trie node state tracking to use RLP nodes instead of hashes. Replaced `Option<B256>` hash fields with `SparseNodeState` enum that tracks either dirty nodes or cached RLP nodes with optional database storage flags. Added debug assertions to validate leaf path lengths and improved pruning logic to use node paths directly instead of path-hash tuples.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1322,18 +1322,31 @@ mod tests {
|
||||
TrieMask,
|
||||
};
|
||||
|
||||
/// Create a leaf key (suffix) with given nibbles padded with zeros to reach `total_len`.
|
||||
fn leaf_key(suffix: impl AsRef<[u8]>, total_len: usize) -> Nibbles {
|
||||
let suffix = suffix.as_ref();
|
||||
let mut nibbles = Nibbles::from_nibbles(suffix);
|
||||
nibbles.extend(&Nibbles::from_nibbles_unchecked(vec![0; total_len - suffix.len()]));
|
||||
nibbles
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reveal_account_path_twice() {
|
||||
let provider_factory = DefaultTrieNodeProviderFactory;
|
||||
let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
|
||||
|
||||
// Full 64-nibble paths
|
||||
let full_path_0 = leaf_key([0x0], 64);
|
||||
let _full_path_1 = leaf_key([0x1], 64);
|
||||
|
||||
let leaf_value = alloy_rlp::encode(TrieAccount::default());
|
||||
// Leaf key is 63 nibbles (suffix after 1-nibble node path)
|
||||
let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
|
||||
Nibbles::default(),
|
||||
leaf_key([], 63),
|
||||
leaf_value.clone(),
|
||||
)));
|
||||
let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
|
||||
Nibbles::default(),
|
||||
leaf_key([], 63),
|
||||
leaf_value.clone(),
|
||||
)));
|
||||
|
||||
@@ -1358,39 +1371,31 @@ mod tests {
|
||||
// Reveal multiproof and check that the state trie contains the leaf node and value
|
||||
sparse.reveal_decoded_multiproof(multiproof.clone().try_into().unwrap()).unwrap();
|
||||
assert!(matches!(
|
||||
sparse.state_trie_ref().unwrap().find_leaf(&Nibbles::from_nibbles([0x0]), None),
|
||||
sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
|
||||
Ok(LeafLookup::Exists)
|
||||
));
|
||||
assert_eq!(
|
||||
sparse.state_trie_ref().unwrap().get_leaf_value(&Nibbles::from_nibbles([0x0])),
|
||||
sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
|
||||
Some(&leaf_value)
|
||||
);
|
||||
|
||||
// Remove the leaf node and check that the state trie does not contain the leaf node and
|
||||
// value
|
||||
sparse.remove_account_leaf(&Nibbles::from_nibbles([0x0]), &provider_factory).unwrap();
|
||||
sparse.remove_account_leaf(&full_path_0, &provider_factory).unwrap();
|
||||
assert!(matches!(
|
||||
sparse.state_trie_ref().unwrap().find_leaf(&Nibbles::from_nibbles([0x0]), None),
|
||||
sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
|
||||
Ok(LeafLookup::NonExistent)
|
||||
));
|
||||
assert!(sparse
|
||||
.state_trie_ref()
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0]))
|
||||
.is_none());
|
||||
assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
|
||||
|
||||
// Reveal multiproof again and check that the state trie still does not contain the leaf
|
||||
// node and value, because they were already revealed before
|
||||
sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
|
||||
assert!(matches!(
|
||||
sparse.state_trie_ref().unwrap().find_leaf(&Nibbles::from_nibbles([0x0]), None),
|
||||
sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
|
||||
Ok(LeafLookup::NonExistent)
|
||||
));
|
||||
assert!(sparse
|
||||
.state_trie_ref()
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0]))
|
||||
.is_none());
|
||||
assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1398,13 +1403,16 @@ mod tests {
|
||||
let provider_factory = DefaultTrieNodeProviderFactory;
|
||||
let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
|
||||
|
||||
// Full 64-nibble path
|
||||
let full_path_0 = leaf_key([0x0], 64);
|
||||
|
||||
let leaf_value = alloy_rlp::encode(TrieAccount::default());
|
||||
let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
|
||||
Nibbles::default(),
|
||||
leaf_key([], 63),
|
||||
leaf_value.clone(),
|
||||
)));
|
||||
let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
|
||||
Nibbles::default(),
|
||||
leaf_key([], 63),
|
||||
leaf_value.clone(),
|
||||
)));
|
||||
|
||||
@@ -1436,52 +1444,38 @@ mod tests {
|
||||
// Reveal multiproof and check that the storage trie contains the leaf node and value
|
||||
sparse.reveal_decoded_multiproof(multiproof.clone().try_into().unwrap()).unwrap();
|
||||
assert!(matches!(
|
||||
sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.find_leaf(&Nibbles::from_nibbles([0x0]), None),
|
||||
sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
|
||||
Ok(LeafLookup::Exists)
|
||||
));
|
||||
assert_eq!(
|
||||
sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0])),
|
||||
sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
|
||||
Some(&leaf_value)
|
||||
);
|
||||
|
||||
// Remove the leaf node and check that the storage trie does not contain the leaf node and
|
||||
// value
|
||||
sparse
|
||||
.remove_storage_leaf(B256::ZERO, &Nibbles::from_nibbles([0x0]), &provider_factory)
|
||||
.unwrap();
|
||||
sparse.remove_storage_leaf(B256::ZERO, &full_path_0, &provider_factory).unwrap();
|
||||
assert!(matches!(
|
||||
sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.find_leaf(&Nibbles::from_nibbles([0x0]), None),
|
||||
sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
|
||||
Ok(LeafLookup::NonExistent)
|
||||
));
|
||||
assert!(sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0]))
|
||||
.get_leaf_value(&full_path_0)
|
||||
.is_none());
|
||||
|
||||
// Reveal multiproof again and check that the storage trie still does not contain the leaf
|
||||
// node and value, because they were already revealed before
|
||||
sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
|
||||
assert!(matches!(
|
||||
sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.find_leaf(&Nibbles::from_nibbles([0x0]), None),
|
||||
sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
|
||||
Ok(LeafLookup::NonExistent)
|
||||
));
|
||||
assert!(sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0]))
|
||||
.get_leaf_value(&full_path_0)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
@@ -1490,9 +1484,12 @@ mod tests {
|
||||
let provider_factory = DefaultTrieNodeProviderFactory;
|
||||
let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
|
||||
|
||||
// Full 64-nibble path
|
||||
let full_path_0 = leaf_key([0x0], 64);
|
||||
|
||||
let leaf_value = alloy_rlp::encode(TrieAccount::default());
|
||||
let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(Nibbles::default(), leaf_value.clone()));
|
||||
let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(Nibbles::default(), leaf_value.clone()));
|
||||
let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
|
||||
let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
|
||||
|
||||
let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
|
||||
key: Nibbles::default(),
|
||||
@@ -1523,29 +1520,21 @@ mod tests {
|
||||
|
||||
// Check that the state trie contains the leaf node and value
|
||||
assert!(matches!(
|
||||
sparse.state_trie_ref().unwrap().find_leaf(&Nibbles::from_nibbles([0x0]), None),
|
||||
sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
|
||||
Ok(LeafLookup::Exists)
|
||||
));
|
||||
assert_eq!(
|
||||
sparse.state_trie_ref().unwrap().get_leaf_value(&Nibbles::from_nibbles([0x0])),
|
||||
sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
|
||||
Some(&leaf_value)
|
||||
);
|
||||
|
||||
// Remove the leaf node
|
||||
sparse.remove_account_leaf(&Nibbles::from_nibbles([0x0]), &provider_factory).unwrap();
|
||||
assert!(sparse
|
||||
.state_trie_ref()
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0]))
|
||||
.is_none());
|
||||
sparse.remove_account_leaf(&full_path_0, &provider_factory).unwrap();
|
||||
assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
|
||||
|
||||
// Reveal again - should skip already revealed paths
|
||||
sparse.reveal_account_v2_proof_nodes(v2_proof_nodes).unwrap();
|
||||
assert!(sparse
|
||||
.state_trie_ref()
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0]))
|
||||
.is_none());
|
||||
assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1553,11 +1542,12 @@ mod tests {
|
||||
let provider_factory = DefaultTrieNodeProviderFactory;
|
||||
let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
|
||||
|
||||
// Full 64-nibble path
|
||||
let full_path_0 = leaf_key([0x0], 64);
|
||||
|
||||
let storage_value: Vec<u8> = alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec();
|
||||
let leaf_1_node =
|
||||
TrieNodeV2::Leaf(LeafNode::new(Nibbles::default(), storage_value.clone()));
|
||||
let leaf_2_node =
|
||||
TrieNodeV2::Leaf(LeafNode::new(Nibbles::default(), storage_value.clone()));
|
||||
let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
|
||||
let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
|
||||
|
||||
let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
|
||||
key: Nibbles::default(),
|
||||
@@ -1580,28 +1570,20 @@ mod tests {
|
||||
|
||||
// Check that the storage trie contains the leaf node and value
|
||||
assert!(matches!(
|
||||
sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.find_leaf(&Nibbles::from_nibbles([0x0]), None),
|
||||
sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
|
||||
Ok(LeafLookup::Exists)
|
||||
));
|
||||
assert_eq!(
|
||||
sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0])),
|
||||
sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
|
||||
Some(&storage_value)
|
||||
);
|
||||
|
||||
// Remove the leaf node
|
||||
sparse
|
||||
.remove_storage_leaf(B256::ZERO, &Nibbles::from_nibbles([0x0]), &provider_factory)
|
||||
.unwrap();
|
||||
sparse.remove_storage_leaf(B256::ZERO, &full_path_0, &provider_factory).unwrap();
|
||||
assert!(sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0]))
|
||||
.get_leaf_value(&full_path_0)
|
||||
.is_none());
|
||||
|
||||
// Reveal again - should skip already revealed paths
|
||||
@@ -1609,7 +1591,7 @@ mod tests {
|
||||
assert!(sparse
|
||||
.storage_trie_ref(&B256::ZERO)
|
||||
.unwrap()
|
||||
.get_leaf_value(&Nibbles::from_nibbles([0x0]))
|
||||
.get_leaf_value(&full_path_0)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
provider::TrieNodeProvider, LeafUpdate, ParallelSparseTrie, SparseTrie as SparseTrieTrait,
|
||||
SparseTrieUpdates,
|
||||
};
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
|
||||
use alloy_primitives::{map::B256Map, B256};
|
||||
use reth_execution_errors::{SparseTrieErrorKind, SparseTrieResult};
|
||||
use reth_trie_common::{BranchNodeMasks, Nibbles, RlpNode, TrieMask, TrieNode, TrieNodeV2};
|
||||
@@ -341,39 +341,22 @@ pub enum SparseNode {
|
||||
Leaf {
|
||||
/// Remaining key suffix for the leaf node.
|
||||
key: Nibbles,
|
||||
/// Pre-computed hash of the sparse node.
|
||||
/// Can be reused unless this trie path has been updated.
|
||||
hash: Option<B256>,
|
||||
/// Tracker for the node's state, e.g. cached `RlpNode` tracking.
|
||||
state: SparseNodeState,
|
||||
},
|
||||
/// Sparse extension node with key.
|
||||
Extension {
|
||||
/// The key slice stored by this extension node.
|
||||
key: Nibbles,
|
||||
/// Pre-computed hash of the sparse node.
|
||||
/// Can be reused unless this trie path has been updated.
|
||||
///
|
||||
/// If [`None`], then the value is not known and should be calculated from scratch.
|
||||
hash: Option<B256>,
|
||||
/// Pre-computed flag indicating whether the trie node should be stored in the database.
|
||||
/// Can be reused unless this trie path has been updated.
|
||||
///
|
||||
/// If [`None`], then the value is not known and should be calculated from scratch.
|
||||
store_in_db_trie: Option<bool>,
|
||||
/// Tracker for the node's state, e.g. cached `RlpNode` tracking.
|
||||
state: SparseNodeState,
|
||||
},
|
||||
/// Sparse branch node with state mask.
|
||||
Branch {
|
||||
/// The bitmask representing children present in the branch node.
|
||||
state_mask: TrieMask,
|
||||
/// Pre-computed hash of the sparse node.
|
||||
/// Can be reused unless this trie path has been updated.
|
||||
///
|
||||
/// If [`None`], then the value is not known and should be calculated from scratch.
|
||||
hash: Option<B256>,
|
||||
/// Pre-computed flag indicating whether the trie node should be stored in the database.
|
||||
/// Can be reused unless this trie path has been updated.
|
||||
///
|
||||
/// If [`None`], then the value is not known and should be calculated from scratch.
|
||||
store_in_db_trie: Option<bool>,
|
||||
/// Tracker for the node's state, e.g. cached `RlpNode` tracking.
|
||||
state: SparseNodeState,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -390,7 +373,7 @@ impl SparseNode {
|
||||
|
||||
/// Create new [`SparseNode::Branch`] from state mask.
|
||||
pub const fn new_branch(state_mask: TrieMask) -> Self {
|
||||
Self::Branch { state_mask, hash: None, store_in_db_trie: None }
|
||||
Self::Branch { state_mask, state: SparseNodeState::Dirty }
|
||||
}
|
||||
|
||||
/// Create new [`SparseNode::Branch`] with two bits set.
|
||||
@@ -399,17 +382,17 @@ impl SparseNode {
|
||||
// set bits for both children
|
||||
(1u16 << bit_a) | (1u16 << bit_b),
|
||||
);
|
||||
Self::Branch { state_mask, hash: None, store_in_db_trie: None }
|
||||
Self::Branch { state_mask, state: SparseNodeState::Dirty }
|
||||
}
|
||||
|
||||
/// Create new [`SparseNode::Extension`] from the key slice.
|
||||
pub const fn new_ext(key: Nibbles) -> Self {
|
||||
Self::Extension { key, hash: None, store_in_db_trie: None }
|
||||
Self::Extension { key, state: SparseNodeState::Dirty }
|
||||
}
|
||||
|
||||
/// Create new [`SparseNode::Leaf`] from leaf key and value.
|
||||
pub const fn new_leaf(key: Nibbles) -> Self {
|
||||
Self::Leaf { key, hash: None }
|
||||
Self::Leaf { key, state: SparseNodeState::Dirty }
|
||||
}
|
||||
|
||||
/// Returns `true` if the node is a hash node.
|
||||
@@ -417,28 +400,41 @@ impl SparseNode {
|
||||
matches!(self, Self::Hash(_))
|
||||
}
|
||||
|
||||
/// Returns the hash of the node if it exists.
|
||||
pub const fn hash(&self) -> Option<B256> {
|
||||
match self {
|
||||
/// Returns the cached [`RlpNode`] of the node, if it's available.
|
||||
pub fn cached_rlp_node(&self) -> Option<Cow<'_, RlpNode>> {
|
||||
match &self {
|
||||
Self::Empty => None,
|
||||
Self::Hash(hash) => Some(Cow::Owned(RlpNode::word_rlp(hash))),
|
||||
Self::Leaf { state, .. } |
|
||||
Self::Extension { state, .. } |
|
||||
Self::Branch { state, .. } => state.cached_rlp_node().map(Cow::Borrowed),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cached hash of the node, if it's available.
|
||||
pub fn cached_hash(&self) -> Option<B256> {
|
||||
match &self {
|
||||
Self::Empty => None,
|
||||
Self::Hash(hash) => Some(*hash),
|
||||
Self::Leaf { hash, .. } | Self::Extension { hash, .. } | Self::Branch { hash, .. } => {
|
||||
*hash
|
||||
}
|
||||
Self::Leaf { state, .. } |
|
||||
Self::Extension { state, .. } |
|
||||
Self::Branch { state, .. } => state.cached_hash(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the hash of the node for testing purposes.
|
||||
///
|
||||
/// For [`SparseNode::Empty`] and [`SparseNode::Hash`] nodes, this method does nothing.
|
||||
/// For [`SparseNode::Empty`] and [`SparseNode::Hash`] nodes, this method panics.
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
pub const fn set_hash(&mut self, new_hash: Option<B256>) {
|
||||
pub fn set_state(&mut self, new_state: SparseNodeState) {
|
||||
match self {
|
||||
Self::Empty | Self::Hash(_) => {
|
||||
// Cannot set hash for Empty or Hash nodes
|
||||
panic!("Cannot set hash for Empty or Hash nodes")
|
||||
}
|
||||
Self::Leaf { hash, .. } | Self::Extension { hash, .. } | Self::Branch { hash, .. } => {
|
||||
*hash = new_hash;
|
||||
Self::Leaf { state, .. } |
|
||||
Self::Extension { state, .. } |
|
||||
Self::Branch { state, .. } => {
|
||||
*state = new_state;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,6 +450,52 @@ impl SparseNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the current state of a node in the trie, specifically regarding whether it's been updated
|
||||
/// or not.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SparseNodeState {
|
||||
/// The node has been updated and its new `RlpNode` has not yet been calculated.
|
||||
///
|
||||
/// If a node is dirty and has children (branches or extensions) then at least once child must
|
||||
/// also be dirty.
|
||||
Dirty,
|
||||
/// The node has a cached `RlpNode`, either from being revealed or computed after an update.
|
||||
Cached {
|
||||
/// The RLP node which is used to represent this node in its parent. Usually this is the
|
||||
/// RLP encoding of the node's hash, except for when the node RLP encodes to <32
|
||||
/// bytes.
|
||||
rlp_node: RlpNode,
|
||||
/// Flag indicating if this node is cached in the database.
|
||||
///
|
||||
/// NOTE for extension nodes this actually indicates the node's child branch is in the
|
||||
/// database, not the extension itself.
|
||||
store_in_db_trie: Option<bool>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SparseNodeState {
|
||||
/// Returns the cached [`RlpNode`] of the node, if it's available.
|
||||
pub const fn cached_rlp_node(&self) -> Option<&RlpNode> {
|
||||
match self {
|
||||
Self::Cached { rlp_node, .. } => Some(rlp_node),
|
||||
Self::Dirty => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cached hash of the node, if it's available.
|
||||
pub fn cached_hash(&self) -> Option<B256> {
|
||||
self.cached_rlp_node().and_then(|n| n.as_hash())
|
||||
}
|
||||
|
||||
/// Returns whether or not this node is stored in the db, or None if it's not known.
|
||||
pub const fn store_in_db_trie(&self) -> Option<bool> {
|
||||
match self {
|
||||
Self::Cached { store_in_db_trie, .. } => *store_in_db_trie,
|
||||
Self::Dirty => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RLP node stack item.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct RlpNodeStackItem {
|
||||
|
||||
Reference in New Issue
Block a user