chore: improve Nibbles-related code (#5631)

This commit is contained in:
DaniPopes
2023-11-30 20:28:50 +01:00
committed by GitHub
parent a4ed76d058
commit 0d522e8472
23 changed files with 313 additions and 233 deletions

View File

@ -223,7 +223,7 @@ impl Command {
(Some(in_mem), Some(incr)) => {
pretty_assertions::assert_eq!(in_mem.0, incr.0, "Nibbles don't match");
if in_mem.1 != incr.1 &&
matches!(in_mem.0, TrieKey::AccountNode(ref nibbles) if nibbles.inner.len() > self.skip_node_depth.unwrap_or_default())
matches!(in_mem.0, TrieKey::AccountNode(ref nibbles) if nibbles.len() > self.skip_node_depth.unwrap_or_default())
{
in_mem_mismatched.push(in_mem);
incremental_mismatched.push(incr);

View File

@ -311,7 +311,7 @@ impl Command {
"Nibbles don't match"
);
if incremental.1 != clean.1 &&
clean.0.inner.len() > self.skip_node_depth.unwrap_or_default()
clean.0.len() > self.skip_node_depth.unwrap_or_default()
{
incremental_account_mismatched.push(incremental);
clean_account_mismatched.push(clean);
@ -340,8 +340,7 @@ impl Command {
match (incremental_storage_trie_iter.next(), clean_storage_trie_iter.next()) {
(Some(incremental), Some(clean)) => {
if incremental != clean &&
clean.1.nibbles.inner.len() >
self.skip_node_depth.unwrap_or_default()
clean.1.nibbles.len() > self.skip_node_depth.unwrap_or_default()
{
first_mismatched_storage = Some((incremental, clean));
break

View File

@ -3,17 +3,12 @@ use reth_primitives::trie::Nibbles;
/// Benchmarks the nibble unpacking.
pub fn nibbles_benchmark(c: &mut Criterion) {
c.bench_function("Nibbles unpack", |b| {
let mut g = c.benchmark_group("nibbles");
g.bench_function("unpack", |b| {
let raw = (1..=32).collect::<Vec<u8>>();
b.iter(|| {
Nibbles::unpack(&raw);
})
b.iter(|| Nibbles::unpack(&raw))
});
}
criterion_group! {
name = benches;
config = Criterion::default();
targets = nibbles_benchmark
}
criterion_group!(benches, nibbles_benchmark);
criterion_main!(benches);

View File

@ -1,8 +1,9 @@
use super::{
nodes::{rlp_hash, BranchNode, ExtensionNode, LeafNode},
nodes::{word_rlp, BranchNode, ExtensionNode, LeafNode},
BranchNodeCompact, Nibbles, TrieMask,
};
use crate::{constants::EMPTY_ROOT_HASH, keccak256, Bytes, B256};
use itertools::Itertools;
use std::{
collections::{BTreeMap, HashMap},
fmt::Debug,
@ -62,7 +63,7 @@ pub struct HashBuilder {
impl From<HashBuilderState> for HashBuilder {
fn from(state: HashBuilderState) -> Self {
Self {
key: Nibbles::from_hex(state.key),
key: Nibbles::new_unchecked(state.key),
stack: state.stack,
value: state.value,
groups: state.groups,
@ -79,7 +80,7 @@ impl From<HashBuilderState> for HashBuilder {
impl From<HashBuilder> for HashBuilderState {
fn from(state: HashBuilder) -> Self {
Self {
key: state.key.hex_data.to_vec(),
key: state.key.to_vec(),
stack: state.stack,
value: state.value,
groups: state.groups,
@ -155,7 +156,7 @@ impl HashBuilder {
if !self.key.is_empty() {
self.update(&key);
} else if key.is_empty() {
self.stack.push(rlp_hash(value));
self.stack.push(word_rlp(&value));
}
self.set_key_value(key, value);
self.stored_in_database = stored_in_database;
@ -166,7 +167,7 @@ impl HashBuilder {
// Clears the internal state
if !self.key.is_empty() {
self.update(&Nibbles::default());
self.key.hex_data.0.clear();
self.key.clear();
self.value = HashBuilderValue::Bytes(vec![]);
}
self.current_root()
@ -209,7 +210,7 @@ impl HashBuilder {
tracing::Level::TRACE,
"loop",
i,
current = crate::hex::encode(&current.hex_data),
?current,
?build_extensions
);
let _enter = span.enter();
@ -217,7 +218,7 @@ impl HashBuilder {
let preceding_exists = !self.groups.is_empty();
let preceding_len: usize = self.groups.len().saturating_sub(1);
let common_prefix_len = succeeding.common_prefix_length(&current);
let common_prefix_len = succeeding.common_prefix_length(current.as_slice());
let len = std::cmp::max(preceding_len, common_prefix_len);
assert!(len < current.len());
@ -241,7 +242,7 @@ impl HashBuilder {
trace!(
target: "trie::hash_builder",
?extra_digit,
groups = self.groups.iter().map(|x| format!("{x:?}")).collect::<Vec<_>>().join(","),
groups = ?self.groups.iter().format(", "),
);
// Adjust the tree masks for exporting to the DB
@ -256,7 +257,7 @@ impl HashBuilder {
trace!(target: "trie::hash_builder", "skipping {} nibbles", len_from);
// The key without the common prefix
let short_node_key = current.slice_from(len_from);
let short_node_key = current.slice(len_from..);
trace!(target: "trie::hash_builder", ?short_node_key);
// Concatenate the 2 nodes together
@ -276,7 +277,7 @@ impl HashBuilder {
}
HashBuilderValue::Hash(hash) => {
trace!(target: "trie::hash_builder", ?hash, "pushing branch node hash");
self.stack.push(rlp_hash(*hash));
self.stack.push(word_rlp(hash));
if self.stored_in_database {
self.tree_masks[current.len() - 1] |=
@ -302,7 +303,7 @@ impl HashBuilder {
}, "extension node rlp");
self.rlp_buf.clear();
self.stack.push(extension_node.rlp(&mut self.rlp_buf));
self.retain_proof_from_buf(&current.slice(0, len_from));
self.retain_proof_from_buf(&current.slice(..len_from));
self.resize_masks(len_from);
}
@ -353,7 +354,7 @@ impl HashBuilder {
self.rlp_buf.clear();
let rlp = branch_node.rlp(state_mask, &mut self.rlp_buf);
self.retain_proof_from_buf(&current.slice(0, len));
self.retain_proof_from_buf(&current.slice(..len));
// Clears the stack from the branch node elements
let first_child_idx = self.stack.len() - state_mask.count_ones() as usize;
@ -403,7 +404,7 @@ impl HashBuilder {
// Send it over to the provided channel which will handle it on the
// other side of the HashBuilder
trace!(target: "trie::hash_builder", node = ?n, "intermediate node");
let common_prefix = current.slice(0, len);
let common_prefix = current.slice(..len);
if let Some(nodes) = self.updated_branch_nodes.as_mut() {
nodes.insert(common_prefix, n);
}
@ -563,7 +564,7 @@ mod tests {
let (_, updates) = hb.split();
let update = updates.get(&Nibbles::from(hex!("01").as_slice())).unwrap();
let update = updates.get(&Nibbles::new_unchecked(hex!("01"))).unwrap();
assert_eq!(update.state_mask, TrieMask::new(0b1111)); // 1st nibble: 0, 1, 2, 3
assert_eq!(update.tree_mask, TrieMask::new(0));
assert_eq!(update.hash_mask, TrieMask::new(6)); // in the 1st nibble, the ones with 1 and 2 are branches with `hashes`
@ -633,7 +634,7 @@ mod tests {
let mut hb2 = HashBuilder::default();
// Insert the branch with the `0x6` shared prefix.
hb2.add_branch(Nibbles::from_hex(vec![0x6]), branch_node_hash, false);
hb2.add_branch(Nibbles::new_unchecked([0x6]), branch_node_hash, false);
let expected = trie_root(raw_input.clone());
assert_eq!(hb.root(), expected);

View File

@ -32,27 +32,32 @@ pub struct TrieMask(u16);
impl TrieMask {
/// Creates a new `TrieMask` from the given inner value.
#[inline]
pub fn new(inner: u16) -> Self {
Self(inner)
}
/// Creates a new `TrieMask` from the given nibble.
#[inline]
pub fn from_nibble(nibble: u8) -> Self {
Self(1u16 << nibble)
}
/// Returns `true` if the current `TrieMask` is a subset of `other`.
pub fn is_subset_of(&self, other: &Self) -> bool {
*self & *other == *self
#[inline]
pub fn is_subset_of(self, other: Self) -> bool {
self & other == self
}
/// Returns `true` if a given bit is set in a mask.
pub fn is_bit_set(&self, index: u8) -> bool {
#[inline]
pub fn is_bit_set(self, index: u8) -> bool {
self.0 & (1u16 << index) != 0
}
/// Returns `true` if the mask is empty.
pub fn is_empty(&self) -> bool {
#[inline]
pub fn is_empty(self) -> bool {
self.0 == 0
}
}

View File

@ -19,7 +19,7 @@ mod subnode;
pub use self::{
mask::TrieMask,
nibbles::{Nibbles, StoredNibbles, StoredNibblesSubKey},
nibbles::{Nibbles, StoredNibblesSubKey},
storage::StorageTrieEntry,
subnode::StoredSubNode,
};

View File

@ -3,28 +3,30 @@ use alloy_rlp::RlpEncodableWrapper;
use derive_more::{Deref, From, Index};
use reth_codecs::{main_codec, Compact};
use serde::{Deserialize, Serialize};
/// The nibbles are the keys for the AccountsTrie and the subkeys for the StorageTrie.
#[main_codec]
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StoredNibbles {
/// The inner nibble bytes
pub inner: Bytes,
}
impl From<Vec<u8>> for StoredNibbles {
fn from(inner: Vec<u8>) -> Self {
Self { inner: inner.into() }
}
}
use std::{borrow::Borrow, ops::RangeBounds};
/// The representation of nibbles of the merkle trie stored in the database.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Hash, Deref)]
pub struct StoredNibblesSubKey(StoredNibbles);
pub struct StoredNibblesSubKey(pub Nibbles);
impl From<Nibbles> for StoredNibblesSubKey {
#[inline]
fn from(value: Nibbles) -> Self {
Self(value)
}
}
impl From<Vec<u8>> for StoredNibblesSubKey {
fn from(inner: Vec<u8>) -> Self {
Self(StoredNibbles { inner: inner.into() })
#[inline]
fn from(value: Vec<u8>) -> Self {
Self(Nibbles::new_unchecked(value))
}
}
impl From<StoredNibblesSubKey> for Nibbles {
#[inline]
fn from(value: StoredNibblesSubKey) -> Self {
value.0
}
}
@ -33,73 +35,122 @@ impl Compact for StoredNibblesSubKey {
where
B: bytes::BufMut + AsMut<[u8]>,
{
assert!(self.inner.len() <= 64);
let mut padded = vec![0; 64];
padded[..self.inner.len()].copy_from_slice(&self.inner[..]);
buf.put_slice(&padded);
buf.put_u8(self.inner.len() as u8);
assert!(self.0.len() <= 64);
// right-pad with zeros
buf.put_slice(&self.0[..]);
static ZERO: &[u8; 64] = &[0; 64];
buf.put_slice(&ZERO[self.0.len()..]);
buf.put_u8(self.0.len() as u8);
64 + 1
}
fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) {
let len = buf[64] as usize;
let inner = Vec::from(&buf[..len]).into();
(Self(StoredNibbles { inner }), &buf[65..])
(Self(Nibbles::new_unchecked(buf[..len].to_vec())), &buf[65..])
}
}
/// Structure representing a sequence of nibbles.
///
/// A nibble is a 4-bit value, and this structure is used to store
/// the nibble sequence representing the keys in a Merkle Patricia Trie (MPT).
/// Using nibbles simplifies trie operations and enables consistent key
/// representation in the MPT.
/// A nibble is a 4-bit value, and this structure is used to store the nibble sequence representing
/// the keys in a Merkle Patricia Trie (MPT).
/// Using nibbles simplifies trie operations and enables consistent key representation in the MPT.
///
/// The `hex_data` field is a `Vec<u8>` that stores the nibbles, with each
/// `u8` value containing a single nibble. This means that each byte in
/// `hex_data` has its upper 4 bits set to zero and the lower 4 bits
/// The internal representation is a shared heap-allocated vector ([`Bytes`]) that stores one nibble
/// per byte. This means that each byte has its upper 4 bits set to zero and the lower 4 bits
/// representing the nibble value.
#[main_codec]
#[derive(
Default, Clone, Eq, PartialEq, RlpEncodableWrapper, PartialOrd, Ord, Hash, Index, From, Deref,
Clone,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
RlpEncodableWrapper,
Index,
From,
Deref,
)]
pub struct Nibbles {
/// The inner representation of the nibble sequence.
pub hex_data: Bytes,
}
pub struct Nibbles(Bytes);
impl From<&[u8]> for Nibbles {
fn from(slice: &[u8]) -> Self {
Nibbles::from_hex(slice.to_vec())
impl From<Vec<u8>> for Nibbles {
#[inline]
fn from(value: Vec<u8>) -> Self {
Self::new_unchecked(value)
}
}
impl<const N: usize> From<&[u8; N]> for Nibbles {
fn from(arr: &[u8; N]) -> Self {
Nibbles::from_hex(arr.to_vec())
impl From<Nibbles> for Vec<u8> {
#[inline]
fn from(value: Nibbles) -> Self {
value.0.into()
}
}
impl std::fmt::Debug for Nibbles {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Nibbles").field("hex_data", &crate::hex::encode(&self.hex_data)).finish()
impl From<Nibbles> for Bytes {
#[inline]
fn from(value: Nibbles) -> Self {
value.into_bytes()
}
}
impl PartialEq<[u8]> for Nibbles {
#[inline]
fn eq(&self, other: &[u8]) -> bool {
self.as_slice() == other
}
}
impl PartialEq<Nibbles> for [u8] {
#[inline]
fn eq(&self, other: &Nibbles) -> bool {
self == other.as_slice()
}
}
impl PartialOrd<[u8]> for Nibbles {
#[inline]
fn partial_cmp(&self, other: &[u8]) -> Option<std::cmp::Ordering> {
self.as_slice().partial_cmp(other)
}
}
impl PartialOrd<Nibbles> for [u8] {
#[inline]
fn partial_cmp(&self, other: &Nibbles) -> Option<std::cmp::Ordering> {
self.partial_cmp(other.as_slice())
}
}
impl Borrow<[u8]> for Nibbles {
#[inline]
fn borrow(&self) -> &[u8] {
self.as_slice()
}
}
impl Nibbles {
/// Creates a new [Nibbles] instance from bytes.
pub fn from_hex<T: Into<Bytes>>(hex: T) -> Self {
Nibbles { hex_data: hex.into() }
/// Creates a new [`Nibbles`] instance from nibble bytes, without checking their validity.
#[inline]
pub fn new_unchecked<T: Into<Bytes>>(nibbles: T) -> Self {
Self(nibbles.into())
}
/// Take a byte array (slice or vector) as input and convert it into a [Nibbles] struct
/// containing the nibbles (half-bytes or 4 bits) that make up the input byte data.
/// Converts a byte slice into a [`Nibbles`] instance containing the nibbles (half-bytes or 4
/// bits) that make up the input byte data.
pub fn unpack<T: AsRef<[u8]>>(data: T) -> Self {
let mut vec = Vec::with_capacity(data.as_ref().len() * 2);
for byte in data.as_ref() {
vec.push(byte / 16);
vec.push(byte % 16);
let data = data.as_ref();
let mut nibbles = Vec::with_capacity(data.len() * 2);
for &byte in data {
nibbles.push(byte >> 4);
nibbles.push(byte & 0x0f);
}
Nibbles { hex_data: Bytes::from(vec) }
Self(nibbles.into())
}
/// Packs the nibbles stored in the struct into a byte vector.
@ -109,22 +160,14 @@ impl Nibbles {
/// If the number of nibbles is odd, the last nibble is shifted left by 4 bits and
/// added to the packed byte vector.
pub fn pack(&self) -> Vec<u8> {
let length = (self.len() + 1) / 2;
if length == 0 {
Vec::new()
} else {
self.iter()
.enumerate()
.filter_map(|(index, nibble)| {
if index % 2 == 0 {
let next_nibble = self.get(index + 1).unwrap_or(&0);
Some((*nibble << 4) + *next_nibble)
} else {
None
}
})
.collect()
let packed_len = (self.len() + 1) / 2;
let mut v = Vec::with_capacity(packed_len);
for i in 0..packed_len {
let hi = *unsafe { self.get_unchecked(i * 2) };
let lo = self.get(i * 2 + 1).copied().unwrap_or(0);
v.push((hi << 4) | lo);
}
v
}
/// Encodes a given path leaf as a compact array of bytes, where each byte represents two
@ -155,19 +198,19 @@ impl Nibbles {
/// # use reth_primitives::trie::Nibbles;
///
/// // Extension node with an even path length:
/// let nibbles = Nibbles::from_hex(vec![0x0A, 0x0B, 0x0C, 0x0D]);
/// let nibbles = Nibbles::new_unchecked(&[0x0A, 0x0B, 0x0C, 0x0D]);
/// assert_eq!(nibbles.encode_path_leaf(false), vec![0x00, 0xAB, 0xCD]);
///
/// // Extension node with an odd path length:
/// let nibbles = Nibbles::from_hex(vec![0x0A, 0x0B, 0x0C]);
/// let nibbles = Nibbles::new_unchecked(&[0x0A, 0x0B, 0x0C]);
/// assert_eq!(nibbles.encode_path_leaf(false), vec![0x1A, 0xBC]);
///
/// // Leaf node with an even path length:
/// let nibbles = Nibbles::from_hex(vec![0x0A, 0x0B, 0x0C, 0x0D]);
/// let nibbles = Nibbles::new_unchecked(&[0x0A, 0x0B, 0x0C, 0x0D]);
/// assert_eq!(nibbles.encode_path_leaf(true), vec![0x20, 0xAB, 0xCD]);
///
/// // Leaf node with an odd path length:
/// let nibbles = Nibbles::from_hex(vec![0x0A, 0x0B, 0x0C]);
/// let nibbles = Nibbles::new_unchecked(&[0x0A, 0x0B, 0x0C]);
/// assert_eq!(nibbles.encode_path_leaf(true), vec![0x3A, 0xBC]);
/// ```
pub fn encode_path_leaf(&self, is_leaf: bool) -> Vec<u8> {
@ -192,14 +235,14 @@ impl Nibbles {
}
/// Increments the nibble sequence by one.
pub fn increment(&self) -> Option<Nibbles> {
let mut incremented = self.hex_data.to_vec();
pub fn increment(&self) -> Option<Self> {
let mut incremented = self.0.to_vec();
for nibble in incremented.iter_mut().rev() {
assert!(*nibble < 0x10);
debug_assert!(*nibble < 0x10);
if *nibble < 0xf {
*nibble += 1;
return Some(Nibbles::from_hex(incremented))
return Some(Self::new_unchecked(incremented))
} else {
*nibble = 0;
}
@ -210,27 +253,37 @@ impl Nibbles {
/// The last element of the hex vector is used to determine whether the nibble sequence
/// represents a leaf or an extension node. If the last element is 0x10 (16), then it's a leaf.
#[inline]
pub fn is_leaf(&self) -> bool {
self.hex_data[self.hex_data.len() - 1] == 16
self.last() == Some(16)
}
/// Returns `true` if the current nibble sequence starts with the given prefix.
pub fn has_prefix(&self, other: &Self) -> bool {
#[inline]
pub fn has_prefix(&self, other: &[u8]) -> bool {
self.starts_with(other)
}
/// Returns the nibble at the given index.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline]
#[track_caller]
pub fn at(&self, i: usize) -> usize {
self.hex_data[i] as usize
self.0[i] as usize
}
/// Returns the last nibble of the current nibble sequence.
#[inline]
pub fn last(&self) -> Option<u8> {
self.hex_data.last().copied()
self.0.last().copied()
}
/// Returns the length of the common prefix between the current nibble sequence and the given.
pub fn common_prefix_length(&self, other: &Nibbles) -> usize {
#[inline]
pub fn common_prefix_length(&self, other: &[u8]) -> usize {
let len = std::cmp::min(self.len(), other.len());
for i in 0..len {
if self[i] != other[i] {
@ -240,34 +293,67 @@ impl Nibbles {
len
}
/// Slice the current nibbles from the given start index to the end.
pub fn slice_from(&self, index: usize) -> Nibbles {
self.slice(index, self.hex_data.len())
/// Returns a reference to the underlying [`Bytes`].
#[inline]
pub fn as_bytes(&self) -> &Bytes {
&self.0
}
/// Returns the nibbles as a byte slice.
#[inline]
pub fn as_slice(&self) -> &[u8] {
&self.0
}
/// Returns the underlying [`Bytes`].
#[inline]
pub fn into_bytes(self) -> Bytes {
self.0
}
/// Slice the current nibbles within the provided index range.
pub fn slice(&self, start: usize, end: usize) -> Nibbles {
Nibbles::from_hex(self.hex_data[start..end].to_vec())
#[inline]
pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
Self(self.0.slice(range))
}
/// Join two nibbles together.
pub fn join(&self, b: &Nibbles) -> Nibbles {
#[inline]
pub fn join(&self, b: &Self) -> Self {
let mut hex_data = Vec::with_capacity(self.len() + b.len());
hex_data.extend_from_slice(self);
hex_data.extend_from_slice(b);
Nibbles::from_hex(hex_data)
Self::new_unchecked(hex_data)
}
/// Pushes a nibble to the end of the current nibbles.
///
/// **Note**: This method re-allocates on each call.
#[inline]
pub fn push(&mut self, nibble: u8) {
self.extend([nibble]);
}
/// Extend the current nibbles with another nibbles.
///
/// **Note**: This method re-allocates on each call.
#[inline]
pub fn extend(&mut self, b: impl AsRef<[u8]>) {
let mut bytes = self.hex_data.to_vec();
let mut bytes = self.0.to_vec();
bytes.extend_from_slice(b.as_ref());
self.hex_data = bytes.into();
self.0 = bytes.into();
}
/// Truncate the current nibbles to the given length.
/// Truncates the current nibbles to the given length.
#[inline]
pub fn truncate(&mut self, len: usize) {
self.hex_data.0.truncate(len)
self.0.truncate(len);
}
/// Clears the current nibbles.
#[inline]
pub fn clear(&mut self) {
self.0.clear();
}
}
@ -279,7 +365,7 @@ mod tests {
#[test]
fn hashed_regression() {
let nibbles = Nibbles::from_hex(hex!("05010406040a040203030f010805020b050c04070003070e0909070f010b0a0805020301070c0a0902040b0f000f0006040a04050f020b090701000a0a040b"));
let nibbles = Nibbles::new_unchecked(hex!("05010406040a040203030f010805020b050c04070003070e0909070f010b0a0805020301070c0a0902040b0f000f0006040a04050f020b090701000a0a040b"));
let path = nibbles.encode_path_leaf(true);
let expected = hex!("351464a4233f1852b5c47037e997f1ba852317ca924bf0f064a45f2b9710aa4b");
assert_eq!(path, expected);
@ -295,7 +381,7 @@ mod tests {
(vec![0xa, 0xb, 0x2, 0x0], vec![0xab, 0x20]),
(vec![0xa, 0xb, 0x2, 0x7], vec![0xab, 0x27]),
] {
let nibbles = Nibbles::from_hex(input);
let nibbles = Nibbles::new_unchecked(input);
let encoded = nibbles.pack();
assert_eq!(encoded, expected);
}

View File

@ -123,8 +123,8 @@ impl BranchNodeCompact {
) -> Self {
let (state_mask, tree_mask, hash_mask) =
(state_mask.into(), tree_mask.into(), hash_mask.into());
assert!(tree_mask.is_subset_of(&state_mask));
assert!(hash_mask.is_subset_of(&state_mask));
assert!(tree_mask.is_subset_of(state_mask));
assert!(hash_mask.is_subset_of(state_mask));
assert_eq!(hash_mask.count_ones() as usize, hashes.len());
Self { state_mask, tree_mask, hash_mask, hashes, root_hash }
}

View File

@ -54,25 +54,22 @@ impl std::fmt::Debug for LeafNode<'_> {
#[cfg(test)]
mod tests {
use super::*;
use crate::hex_literal::hex;
use crate::hex;
// From manual regression test
#[test]
fn encode_leaf_node_nibble() {
let nibble = Nibbles { hex_data: hex!("0604060f").into() };
let nibble = Nibbles::new_unchecked(hex!("0604060f"));
let encoded = nibble.encode_path_leaf(true);
let expected = hex!("20646f").to_vec();
assert_eq!(encoded, expected);
assert_eq!(encoded, hex!("20646f"));
}
#[test]
fn rlp_leaf_node_roundtrip() {
let nibble = Nibbles { hex_data: hex!("0604060f").into() };
let val = hex!("76657262").to_vec();
let nibble = Nibbles::new_unchecked(hex!("0604060f"));
let val = hex!("76657262");
let leaf = LeafNode::new(&nibble, &val);
let rlp = leaf.rlp(&mut vec![]);
let expected = hex!("c98320646f8476657262").to_vec();
assert_eq!(rlp, expected);
assert_eq!(rlp, hex!("c98320646f8476657262"));
}
}

View File

@ -3,28 +3,30 @@ use alloy_rlp::EMPTY_STRING_CODE;
use std::ops::Range;
mod branch;
mod extension;
mod leaf;
pub use branch::{BranchNode, BranchNodeCompact};
pub use self::{
branch::{BranchNode, BranchNodeCompact},
extension::ExtensionNode,
leaf::LeafNode,
};
mod extension;
pub use extension::ExtensionNode;
mod leaf;
pub use leaf::LeafNode;
/// The range of valid child indexes.
pub const CHILD_INDEX_RANGE: Range<u8> = 0..16;
/// Given an RLP encoded node, returns either RLP(node) or RLP(keccak(RLP(node)))
#[inline]
fn rlp_node(rlp: &[u8]) -> Vec<u8> {
if rlp.len() < B256::len_bytes() {
rlp.to_vec()
} else {
rlp_hash(keccak256(rlp))
word_rlp(&keccak256(rlp))
}
}
/// Optimization for quick encoding of a hash as RLP
pub fn rlp_hash(hash: B256) -> Vec<u8> {
[[EMPTY_STRING_CODE + B256::len_bytes() as u8].as_slice(), hash.0.as_slice()].concat()
/// Optimization for quick encoding of a 32-byte word as RLP.
// TODO: this could return [u8; 33] but Vec is needed everywhere this function is used
#[inline]
pub fn word_rlp(word: &B256) -> Vec<u8> {
[&[EMPTY_STRING_CODE + B256::len_bytes() as u8][..], &word[..]].concat()
}

View File

@ -37,7 +37,7 @@ impl_compression_for_compact!(
Receipt,
TxType,
StorageEntry,
StoredNibbles,
Nibbles,
BranchNodeCompact,
StoredNibblesSubKey,
StorageTrieEntry,

View File

@ -36,7 +36,7 @@ use crate::{
};
use reth_primitives::{
stage::StageCheckpoint,
trie::{BranchNodeCompact, StorageTrieEntry, StoredNibbles, StoredNibblesSubKey},
trie::{BranchNodeCompact, Nibbles, StorageTrieEntry, StoredNibblesSubKey},
Account, Address, BlockHash, BlockNumber, Bytecode, Header, IntegerList, PruneCheckpoint,
PruneSegment, Receipt, StorageEntry, TransactionSignedNoHash, TxHash, TxNumber, B256,
};
@ -384,7 +384,7 @@ dupsort!(
table!(
/// Stores the current state's Merkle Patricia Tree.
( AccountsTrie ) StoredNibbles | BranchNodeCompact
( AccountsTrie ) Nibbles | BranchNodeCompact
);
dupsort!(

View File

@ -5,7 +5,7 @@ use crate::{
};
use reth_codecs::Compact;
use reth_primitives::{
trie::{StoredNibbles, StoredNibblesSubKey},
trie::{Nibbles, StoredNibblesSubKey},
Address, PruneSegment, B256,
};
@ -102,18 +102,18 @@ impl Decode for String {
}
}
impl Encode for StoredNibbles {
impl Encode for Nibbles {
type Encoded = Vec<u8>;
// Delegate to the Compact implementation
fn encode(self) -> Self::Encoded {
let mut buf = Vec::with_capacity(self.inner.len());
let mut buf = Vec::with_capacity(self.len());
self.to_compact(&mut buf);
buf
}
}
impl Decode for StoredNibbles {
impl Decode for Nibbles {
fn decode<B: AsRef<[u8]>>(value: B) -> Result<Self, DatabaseError> {
let buf = value.as_ref();
Ok(Self::from_compact(buf, buf.len()).0)

View File

@ -35,8 +35,7 @@ fn create_transactions_for_sender(
.unwrap()
.current();
let mut nonce = 0;
for tx in txs.iter_mut() {
for (nonce, tx) in txs.iter_mut().enumerate() {
// reject pre-eip1559 tx types, if there is a legacy tx, replace it with an eip1559 tx
if tx.is_legacy() || tx.is_eip2930() {
*tx = MockTransaction::eip1559();
@ -47,8 +46,7 @@ fn create_transactions_for_sender(
}
tx.set_sender(sender);
tx.set_nonce(nonce);
nonce += 1;
tx.set_nonce(nonce as u64);
}
txs

View File

@ -16,14 +16,13 @@ pub trait PrefixSetAbstraction: Default {
fn contains(&mut self, key: Nibbles) -> bool;
}
/// Abstractions used for benching
impl PrefixSetAbstraction for PrefixSetMut {
fn insert(&mut self, key: Nibbles) {
self.insert(key)
PrefixSetMut::insert(self, key)
}
fn contains(&mut self, key: Nibbles) -> bool {
PrefixSetMut::contains(self, key)
PrefixSetMut::contains(self, &key)
}
}
@ -95,12 +94,12 @@ fn generate_test_data(size: usize) -> (Vec<Nibbles>, Vec<Nibbles>, Vec<bool>) {
let mut preload = vec(vec(any::<u8>(), 32), size).new_tree(&mut runner).unwrap().current();
preload.dedup();
preload.sort();
let preload = preload.into_iter().map(|hash| Nibbles::from(&hash[..])).collect::<Vec<_>>();
let preload = preload.into_iter().map(Nibbles::new_unchecked).collect::<Vec<_>>();
let mut input = vec(vec(any::<u8>(), 0..=32), size).new_tree(&mut runner).unwrap().current();
input.dedup();
input.sort();
let input = input.into_iter().map(|bytes| Nibbles::from(&bytes[..])).collect::<Vec<_>>();
let input = input.into_iter().map(Nibbles::new_unchecked).collect::<Vec<_>>();
let expected = input
.iter()
@ -145,11 +144,11 @@ mod implementations {
fn contains(&mut self, prefix: Nibbles) -> bool {
let range = match self.last_checked.as_ref() {
// presumably never hit
Some(last) if &prefix < last => (Bound::Unbounded, Bound::Excluded(last)),
Some(last) if prefix < *last => (Bound::Unbounded, Bound::Excluded(last)),
Some(last) => (Bound::Included(last), Bound::Unbounded),
None => (Bound::Unbounded, Bound::Unbounded),
};
for key in self.keys.range(range) {
for key in self.keys.range::<Nibbles, _>(range) {
if key.has_prefix(&prefix) {
self.last_checked = Some(prefix);
return true

View File

@ -22,13 +22,14 @@ pub use loader::{LoadedPrefixSets, PrefixSetLoader};
/// # Examples
///
/// ```
/// use reth_primitives::trie::Nibbles;
/// use reth_trie::prefix_set::PrefixSetMut;
///
/// let mut prefix_set = PrefixSetMut::default();
/// prefix_set.insert(b"key1");
/// prefix_set.insert(b"key2");
///
/// assert_eq!(prefix_set.contains(b"key"), true);
/// prefix_set.insert(Nibbles::new_unchecked(&[0xa, 0xb]));
/// prefix_set.insert(Nibbles::new_unchecked(&[0xa, 0xb, 0xc]));
/// assert!(prefix_set.contains(&[0xa, 0xb]));
/// assert!(prefix_set.contains(&[0xa, 0xb, 0xc]));
/// ```
#[derive(Debug, Default, Clone)]
pub struct PrefixSetMut {
@ -49,26 +50,24 @@ where
impl PrefixSetMut {
/// Returns `true` if any of the keys in the set has the given prefix or
/// if the given prefix is a prefix of any key in the set.
pub fn contains<T: Into<Nibbles>>(&mut self, prefix: T) -> bool {
pub fn contains(&mut self, prefix: &[u8]) -> bool {
if !self.sorted {
self.keys.sort();
self.keys.dedup();
self.sorted = true;
}
let prefix = prefix.into();
while self.index > 0 && self.keys[self.index] > prefix {
while self.index > 0 && self.keys[self.index] > *prefix {
self.index -= 1;
}
for (idx, key) in self.keys[self.index..].iter().enumerate() {
if key.has_prefix(&prefix) {
if key.has_prefix(prefix) {
self.index += idx;
return true
}
if key > &prefix {
if *key > *prefix {
self.index += idx;
return false
}
@ -78,9 +77,9 @@ impl PrefixSetMut {
}
/// Inserts the given `nibbles` into the set.
pub fn insert<T: Into<Nibbles>>(&mut self, nibbles: T) {
pub fn insert(&mut self, nibbles: Nibbles) {
self.sorted = false;
self.keys.push(nibbles.into());
self.keys.push(nibbles);
}
/// Returns the number of elements in the set.
@ -159,10 +158,10 @@ mod tests {
#[test]
fn test_contains_with_multiple_inserts_and_duplicates() {
let mut prefix_set = PrefixSetMut::default();
prefix_set.insert(b"123");
prefix_set.insert(b"124");
prefix_set.insert(b"456");
prefix_set.insert(b"123"); // Duplicate
prefix_set.insert(Nibbles::new_unchecked(b"123"));
prefix_set.insert(Nibbles::new_unchecked(b"124"));
prefix_set.insert(Nibbles::new_unchecked(b"456"));
prefix_set.insert(Nibbles::new_unchecked(b"123")); // Duplicate
assert!(prefix_set.contains(b"12"));
assert!(prefix_set.contains(b"45"));

View File

@ -937,7 +937,7 @@ mod tests {
assert_eq!(account_updates.len(), 2);
let (nibbles1a, node1a) = account_updates.first().unwrap();
assert_eq!(nibbles1a.inner[..], [0xB]);
assert_eq!(nibbles1a[..], [0xB]);
assert_eq!(node1a.state_mask, TrieMask::new(0b1011));
assert_eq!(node1a.tree_mask, TrieMask::new(0b0001));
assert_eq!(node1a.hash_mask, TrieMask::new(0b1001));
@ -945,7 +945,7 @@ mod tests {
assert_eq!(node1a.hashes.len(), 2);
let (nibbles2a, node2a) = account_updates.last().unwrap();
assert_eq!(nibbles2a.inner[..], [0xB, 0x0]);
assert_eq!(nibbles2a[..], [0xB, 0x0]);
assert_eq!(node2a.state_mask, TrieMask::new(0b10001));
assert_eq!(node2a.tree_mask, TrieMask::new(0b00000));
assert_eq!(node2a.hash_mask, TrieMask::new(0b10000));
@ -963,7 +963,7 @@ mod tests {
assert_eq!(storage_updates.len(), 1);
let (nibbles3, node3) = storage_updates.first().unwrap();
assert!(nibbles3.inner.is_empty());
assert!(nibbles3.is_empty());
assert_eq!(node3.state_mask, TrieMask::new(0b1010));
assert_eq!(node3.tree_mask, TrieMask::new(0b0000));
assert_eq!(node3.hash_mask, TrieMask::new(0b0010));
@ -1004,7 +1004,7 @@ mod tests {
assert_eq!(account_updates.len(), 2);
let (nibbles1b, node1b) = account_updates.first().unwrap();
assert_eq!(nibbles1b.inner[..], [0xB]);
assert_eq!(nibbles1b[..], [0xB]);
assert_eq!(node1b.state_mask, TrieMask::new(0b1011));
assert_eq!(node1b.tree_mask, TrieMask::new(0b0001));
assert_eq!(node1b.hash_mask, TrieMask::new(0b1011));
@ -1014,7 +1014,7 @@ mod tests {
assert_eq!(node1a.hashes[1], node1b.hashes[2]);
let (nibbles2b, node2b) = account_updates.last().unwrap();
assert_eq!(nibbles2b.inner[..], [0xB, 0x0]);
assert_eq!(nibbles2b[..], [0xB, 0x0]);
assert_eq!(node2a, node2b);
tx.commit().unwrap();
let tx = factory.provider_rw().unwrap();
@ -1057,7 +1057,7 @@ mod tests {
assert_eq!(account_updates.len(), 1);
let (nibbles1c, node1c) = account_updates.first().unwrap();
assert_eq!(nibbles1c.inner[..], [0xB]);
assert_eq!(nibbles1c[..], [0xB]);
assert_eq!(node1c.state_mask, TrieMask::new(0b1011));
assert_eq!(node1c.tree_mask, TrieMask::new(0b0000));
@ -1114,7 +1114,7 @@ mod tests {
assert_eq!(account_updates.len(), 1);
let (nibbles1d, node1d) = account_updates.first().unwrap();
assert_eq!(nibbles1d.inner[..], [0xB]);
assert_eq!(nibbles1d[..], [0xB]);
assert_eq!(node1d.state_mask, TrieMask::new(0b1011));
assert_eq!(node1d.tree_mask, TrieMask::new(0b0000));
@ -1143,7 +1143,7 @@ mod tests {
.iter()
.filter_map(|entry| match entry {
(TrieKey::AccountNode(nibbles), TrieOp::Update(node)) => {
Some((nibbles.inner[..].into(), node.clone()))
Some((nibbles.clone(), node.clone()))
}
_ => None,
})
@ -1170,7 +1170,7 @@ mod tests {
let mut account_updates = HashMap::new();
for item in walker {
let (key, node) = item.unwrap();
account_updates.insert(key.inner[..].into(), node);
account_updates.insert(key, node);
}
assert_trie_updates(&account_updates);
@ -1231,7 +1231,7 @@ mod tests {
.iter()
.filter_map(|entry| match entry {
(TrieKey::StorageNode(_, nibbles), TrieOp::Update(node)) => {
Some((nibbles.inner[..].into(), node.clone()))
Some((nibbles.clone().into(), node.clone()))
}
_ => None,
})
@ -1296,11 +1296,11 @@ mod tests {
fn assert_trie_updates(account_updates: &HashMap<Nibbles, BranchNodeCompact>) {
assert_eq!(account_updates.len(), 2);
let node = account_updates.get(&vec![0x3].as_slice().into()).unwrap();
let node = account_updates.get(&[0x3][..]).unwrap();
let expected = BranchNodeCompact::new(0b0011, 0b0001, 0b0000, vec![], None);
assert_eq!(node, &expected);
let node = account_updates.get(&vec![0x3, 0x0, 0xA, 0xF].as_slice().into()).unwrap();
let node = account_updates.get(&[0x3, 0x0, 0xA, 0xF][..]).unwrap();
assert_eq!(node.state_mask, TrieMask::new(0b101100000));
assert_eq!(node.tree_mask, TrieMask::new(0b000000000));
assert_eq!(node.hash_mask, TrieMask::new(0b001000000));

View File

@ -1,7 +1,7 @@
use super::TrieCursor;
use crate::updates::TrieKey;
use reth_db::{cursor::DbCursorRO, tables, DatabaseError};
use reth_primitives::trie::{BranchNodeCompact, StoredNibbles};
use reth_primitives::trie::{BranchNodeCompact, Nibbles};
/// A cursor over the account trie.
#[derive(Debug)]
@ -18,20 +18,20 @@ impl<C> TrieCursor for AccountTrieCursor<C>
where
C: DbCursorRO<tables::AccountsTrie>,
{
type Key = StoredNibbles;
type Key = Nibbles;
fn seek_exact(
&mut self,
key: Self::Key,
) -> Result<Option<(Vec<u8>, BranchNodeCompact)>, DatabaseError> {
Ok(self.0.seek_exact(key)?.map(|value| (value.0.inner.to_vec(), value.1)))
Ok(self.0.seek_exact(key)?.map(|value| (value.0.to_vec(), value.1)))
}
fn seek(
&mut self,
key: Self::Key,
) -> Result<Option<(Vec<u8>, BranchNodeCompact)>, DatabaseError> {
Ok(self.0.seek(key)?.map(|value| (value.0.inner.to_vec(), value.1)))
Ok(self.0.seek(key)?.map(|value| (value.0.to_vec(), value.1)))
}
fn current(&mut self) -> Result<Option<TrieKey>, DatabaseError> {
@ -80,13 +80,13 @@ mod tests {
}
let db_data = cursor.walk_range(..).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(db_data[0].0.inner.to_vec(), data[0]);
assert_eq!(db_data[1].0.inner.to_vec(), data[1]);
assert_eq!(db_data[2].0.inner.to_vec(), data[2]);
assert_eq!(db_data[3].0.inner.to_vec(), data[3]);
assert_eq!(db_data[0].0.to_vec(), data[0]);
assert_eq!(db_data[1].0.to_vec(), data[1]);
assert_eq!(db_data[2].0.to_vec(), data[2]);
assert_eq!(db_data[3].0.to_vec(), data[3]);
assert_eq!(
cursor.seek(hex!("0303040f").to_vec().into()).unwrap().map(|(k, _)| k.inner.to_vec()),
cursor.seek(hex!("0303040f").to_vec().into()).unwrap().map(|(k, _)| k.to_vec()),
Some(data[1].clone())
);
}

View File

@ -38,7 +38,7 @@ where
.cursor
.seek_by_key_subkey(self.hashed_address, key.clone())?
.filter(|e| e.nibbles == key)
.map(|value| (value.nibbles.inner.to_vec(), value.node)))
.map(|value| (value.nibbles.to_vec(), value.node)))
}
fn seek(
@ -48,7 +48,7 @@ where
Ok(self
.cursor
.seek_by_key_subkey(self.hashed_address, key)?
.map(|value| (value.nibbles.inner.to_vec(), value.node)))
.map(|value| (value.nibbles.to_vec(), value.node)))
}
fn current(&mut self) -> Result<Option<TrieKey>, DatabaseError> {

View File

@ -39,14 +39,14 @@ impl From<StoredSubNode> for CursorSubNode {
Some(n) => n as i8,
None => -1,
};
Self { key: Nibbles::from_hex(value.key), nibble, node: value.node }
Self { key: Nibbles::new_unchecked(value.key), nibble, node: value.node }
}
}
impl From<CursorSubNode> for StoredSubNode {
fn from(value: CursorSubNode) -> Self {
let nibble = if value.nibble >= 0 { Some(value.nibble as u8) } else { None };
Self { key: value.key.hex_data.to_vec(), nibble, node: value.node }
Self { key: value.key.to_vec(), nibble, node: value.node }
}
}
@ -67,7 +67,7 @@ impl CursorSubNode {
pub fn full_key(&self) -> Nibbles {
let mut out = self.key.clone();
if self.nibble >= 0 {
out.extend([self.nibble as u8]);
out.push(self.nibble as u8);
}
out
}

View File

@ -5,7 +5,7 @@ use reth_db::{
transaction::{DbTx, DbTxMut},
};
use reth_primitives::{
trie::{BranchNodeCompact, Nibbles, StorageTrieEntry, StoredNibbles, StoredNibblesSubKey},
trie::{BranchNodeCompact, Nibbles, StorageTrieEntry, StoredNibblesSubKey},
B256,
};
use std::collections::{hash_map::IntoIter, HashMap};
@ -14,7 +14,7 @@ use std::collections::{hash_map::IntoIter, HashMap};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TrieKey {
/// A node in the account trie.
AccountNode(StoredNibbles),
AccountNode(Nibbles),
/// A node in the storage trie.
StorageNode(B256, StoredNibblesSubKey),
/// Storage trie of an account.
@ -78,9 +78,11 @@ impl TrieUpdates {
/// Extend the updates with account trie updates.
pub fn extend_with_account_updates(&mut self, updates: HashMap<Nibbles, BranchNodeCompact>) {
self.extend(updates.into_iter().map(|(nibbles, node)| {
(TrieKey::AccountNode(nibbles.hex_data.to_vec().into()), TrieOp::Update(node))
}));
self.extend(
updates
.into_iter()
.map(|(nibbles, node)| (TrieKey::AccountNode(nibbles), TrieOp::Update(node))),
);
}
/// Extend the updates with storage trie updates.
@ -90,10 +92,7 @@ impl TrieUpdates {
updates: HashMap<Nibbles, BranchNodeCompact>,
) {
self.extend(updates.into_iter().map(|(nibbles, node)| {
(
TrieKey::StorageNode(hashed_address, nibbles.hex_data.to_vec().into()),
TrieOp::Update(node),
)
(TrieKey::StorageNode(hashed_address, nibbles.into()), TrieOp::Update(node))
}));
}
@ -122,7 +121,7 @@ impl TrieUpdates {
}
}
TrieOp::Update(node) => {
if !nibbles.inner.is_empty() {
if !nibbles.is_empty() {
account_trie_cursor.upsert(nibbles, node)?;
}
}
@ -136,7 +135,7 @@ impl TrieUpdates {
TrieOp::Update(..) => unreachable!("Cannot update full storage trie."),
},
TrieKey::StorageNode(hashed_address, nibbles) => {
if !nibbles.inner.is_empty() {
if !nibbles.is_empty() {
// Delete the old entry if it exists.
if storage_trie_cursor
.seek_by_key_subkey(hashed_address, nibbles.clone())?

View File

@ -122,16 +122,16 @@ impl<C: TrieCursor> TrieWalker<C> {
fn node(&mut self, exact: bool) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
let key = self.key().expect("key must exist");
let entry = if exact {
self.cursor.seek_exact(key.hex_data.to_vec().into())?
self.cursor.seek_exact(key.to_vec().into())?
} else {
self.cursor.seek(key.hex_data.to_vec().into())?
self.cursor.seek(key.to_vec().into())?
};
if let Some((_, node)) = &entry {
assert!(!node.state_mask.is_empty());
}
Ok(entry.map(|(k, v)| (Nibbles::from_hex(k), v)))
Ok(entry.map(|(k, v)| (Nibbles::new_unchecked(k), v)))
}
/// Consumes the next node in the trie, updating the stack.
@ -313,7 +313,7 @@ mod tests {
// We're traversing the path in lexigraphical order.
for expected in expected {
let got = walker.advance().unwrap();
assert_eq!(got.unwrap(), Nibbles::from(&expected[..]));
assert_eq!(got.unwrap(), Nibbles::new_unchecked(expected.clone()));
}
// There should be 8 paths traversed in total from 3 branches.
@ -361,26 +361,26 @@ mod tests {
// No changes
let mut cursor = TrieWalker::new(&mut trie, Default::default());
assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![]))); // root
assert_eq!(cursor.key(), Some(Nibbles::new_unchecked([]))); // root
assert!(cursor.can_skip_current_node); // due to root_hash
cursor.advance().unwrap(); // skips to the end of trie
assert_eq!(cursor.key(), None);
// We insert something that's not part of the existing trie/prefix.
let mut changed = PrefixSetMut::default();
changed.insert(&[0xF, 0x1]);
changed.insert(Nibbles::new_unchecked([0xF, 0x1]));
let mut cursor = TrieWalker::new(&mut trie, changed.freeze());
// Root node
assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![])));
assert_eq!(cursor.key(), Some(Nibbles::new_unchecked([])));
// Should not be able to skip state due to the changed values
assert!(!cursor.can_skip_current_node);
cursor.advance().unwrap();
assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![0x2])));
assert_eq!(cursor.key(), Some(Nibbles::new_unchecked([0x2])));
cursor.advance().unwrap();
assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![0x2, 0x1])));
assert_eq!(cursor.key(), Some(Nibbles::new_unchecked([0x2, 0x1])));
cursor.advance().unwrap();
assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![0x4])));
assert_eq!(cursor.key(), Some(Nibbles::new_unchecked([0x4])));
cursor.advance().unwrap();
assert_eq!(cursor.key(), None); // the end of trie

View File

@ -113,7 +113,7 @@ HashedStorage {
U256 StorageValue
}
AccountsTrie {
StoredNibbles Nibbles "PK"
Nibbles "PK"
BranchNodeCompact Node
}
StoragesTrie {