diff --git a/Cargo.lock b/Cargo.lock index 881de09d6d..9d897c4533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4577,6 +4577,20 @@ dependencies = [ "libc", ] +[[package]] +name = "nybbles" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47dddada2357f8e7786f4f4d837db7bdddec02c7c3e5da7840d92c70390f6dc0" +dependencies = [ + "alloy-rlp", + "arbitrary", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "object" version = "0.32.1" @@ -6209,6 +6223,7 @@ dependencies = [ "itertools 0.11.0", "modular-bitfield", "num_enum 0.7.1", + "nybbles", "once_cell", "plain_hasher", "pprof", diff --git a/bin/reth/src/debug_cmd/in_memory_merkle.rs b/bin/reth/src/debug_cmd/in_memory_merkle.rs index 357da88171..4a942258c7 100644 --- a/bin/reth/src/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/debug_cmd/in_memory_merkle.rs @@ -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.len() > self.skip_node_depth.unwrap_or_default()) + matches!(in_mem.0, TrieKey::AccountNode(ref nibbles) if nibbles.0.len() > self.skip_node_depth.unwrap_or_default()) { in_mem_mismatched.push(in_mem); incremental_mismatched.push(incr); diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index 3745efc9f7..bcbfc40349 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -311,7 +311,7 @@ impl Command { "Nibbles don't match" ); if incremental.1 != clean.1 && - clean.0.len() > self.skip_node_depth.unwrap_or_default() + clean.0 .0.len() > self.skip_node_depth.unwrap_or_default() { incremental_account_mismatched.push(incremental); clean_account_mismatched.push(clean); diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index eef82c7f02..72e3f5b4b8 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -20,6 +20,7 @@ revm-primitives = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["rand", "rlp"] } alloy-rlp = { workspace = true, features = ["arrayvec"] } ethers-core = { workspace = true, default-features = false, optional = true } +nybbles = { version = "0.1", features = ["serde", "rlp"] } # crypto secp256k1 = { workspace = true, features = ["global-context", "recovery"] } @@ -86,6 +87,7 @@ arbitrary = [ "revm-primitives/arbitrary", "reth-rpc-types/arbitrary", "reth-ethereum-forks/arbitrary", + "nybbles/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive", diff --git a/crates/primitives/src/trie/mod.rs b/crates/primitives/src/trie/mod.rs index 632ce3f4cd..0081678ac4 100644 --- a/crates/primitives/src/trie/mod.rs +++ b/crates/primitives/src/trie/mod.rs @@ -21,7 +21,7 @@ mod subnode; pub use self::{ account::TrieAccount, mask::TrieMask, - nibbles::{Nibbles, StoredNibblesSubKey}, + nibbles::{Nibbles, StoredNibbles, StoredNibblesSubKey}, storage::StorageTrieEntry, subnode::StoredSubNode, }; diff --git a/crates/primitives/src/trie/nibbles.rs b/crates/primitives/src/trie/nibbles.rs index fe60d18bd3..5a46887f82 100644 --- a/crates/primitives/src/trie/nibbles.rs +++ b/crates/primitives/src/trie/nibbles.rs @@ -1,15 +1,76 @@ -use crate::Bytes; use bytes::Buf; -use derive_more::{Deref, From, Index}; +use derive_more::Deref; use reth_codecs::Compact; use serde::{Deserialize, Serialize}; -use smallvec::SmallVec; -use std::{ - borrow::Borrow, - fmt, - mem::MaybeUninit, - ops::{Bound, RangeBounds}, -}; + +pub use nybbles::Nibbles; + +/// The representation of nibbles of the merkle trie stored in the database. +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + derive_more::Index, +)] +pub struct StoredNibbles(pub Nibbles); + +impl From for StoredNibbles { + #[inline] + fn from(value: Nibbles) -> Self { + Self(value) + } +} + +impl From> for StoredNibbles { + #[inline] + fn from(value: Vec) -> Self { + Self(Nibbles::from_nibbles_unchecked(value)) + } +} + +impl PartialEq<[u8]> for StoredNibbles { + #[inline] + fn eq(&self, other: &[u8]) -> bool { + self.0.as_slice() == other + } +} + +impl PartialOrd<[u8]> for StoredNibbles { + #[inline] + fn partial_cmp(&self, other: &[u8]) -> Option { + self.0.as_slice().partial_cmp(other) + } +} + +impl core::borrow::Borrow<[u8]> for StoredNibbles { + #[inline] + fn borrow(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl Compact for StoredNibbles { + fn to_compact(self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + buf.put_slice(self.0.as_slice()); + self.0.len() + } + + fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) { + let nibbles = &buf[..len]; + buf.advance(len); + (Self(Nibbles::from_nibbles_unchecked(nibbles)), buf) + } +} /// The representation of nibbles of the merkle trie stored in the database. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Hash, Deref)] @@ -57,600 +118,3 @@ impl Compact for StoredNibblesSubKey { (Self(Nibbles::from_nibbles_unchecked(&buf[..len])), &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. -/// -/// The internal representation is a [`SmallVec`] 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. -#[derive( - Default, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Index, - From, - Deref, - serde::Serialize, - serde::Deserialize, -)] -#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -pub struct Nibbles(SmallVec<[u8; 64]>); - -// Override `SmallVec::from` since it's not specialized for `Copy` types. -impl Clone for Nibbles { - #[inline] - fn clone(&self) -> Self { - Self(SmallVec::from_slice(&self.0)) - } - - #[inline] - fn clone_from(&mut self, source: &Self) { - self.0.clone_from(&source.0); - } -} - -impl alloy_rlp::Encodable for Nibbles { - #[inline] - fn length(&self) -> usize { - alloy_rlp::Encodable::length(self.as_slice()) - } - - #[inline] - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - alloy_rlp::Encodable::encode(self.as_slice(), out) - } -} - -#[cfg(any(test, feature = "arbitrary"))] -impl proptest::arbitrary::Arbitrary for Nibbles { - type Parameters = (); - type Strategy = proptest::strategy::Map< - proptest::collection::VecStrategy>, - fn(Vec) -> Self, - >; - - #[inline] - fn arbitrary_with((): ()) -> Self::Strategy { - use proptest::prelude::*; - proptest::collection::vec(0x0..=0xf, 0..64).prop_map(Self::from_nibbles_unchecked) - } -} - -impl Compact for Nibbles { - fn to_compact(self, buf: &mut B) -> usize - where - B: bytes::BufMut + AsMut<[u8]>, - { - buf.put_slice(self.as_slice()); - self.len() - } - - fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) { - let nibbles = &buf[..len]; - buf.advance(len); - (Nibbles::from_nibbles_unchecked(nibbles), buf) - } -} - -impl fmt::Debug for Nibbles { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Nibbles").field(&crate::hex::encode(self.as_slice())).finish() - } -} - -impl From> for Nibbles { - #[inline] - fn from(value: Vec) -> Self { - Self(SmallVec::from_vec(value)) - } -} - -impl From for Vec { - #[inline] - fn from(value: Nibbles) -> Self { - value.0.into_vec() - } -} - -impl From for Bytes { - #[inline] - fn from(value: Nibbles) -> Self { - value.0.into_vec().into() - } -} - -impl PartialEq<[u8]> for Nibbles { - #[inline] - fn eq(&self, other: &[u8]) -> bool { - self.as_slice() == other - } -} - -impl PartialEq 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 { - self.as_slice().partial_cmp(other) - } -} - -impl PartialOrd for [u8] { - #[inline] - fn partial_cmp(&self, other: &Nibbles) -> Option { - self.partial_cmp(other.as_slice()) - } -} - -impl Borrow<[u8]> for Nibbles { - #[inline] - fn borrow(&self) -> &[u8] { - self.as_slice() - } -} - -impl Extend for Nibbles { - #[inline] - fn extend>(&mut self, iter: T) { - self.0.extend(iter) - } -} - -impl Nibbles { - /// Creates a new empty [`Nibbles`] instance. - #[inline] - pub const fn new() -> Self { - Self(SmallVec::new_const()) - } - - /// Creates a new [`Nibbles`] instance with the given capacity. - #[inline] - pub fn with_capacity(capacity: usize) -> Self { - Self(SmallVec::with_capacity(capacity)) - } - - /// Creates a new [`Nibbles`] instance from nibble bytes, without checking their validity. - #[inline] - pub fn from_nibbles_unchecked>(nibbles: T) -> Self { - Self(SmallVec::from_slice(nibbles.as_ref())) - } - - /// Converts a byte slice into a [`Nibbles`] instance containing the nibbles (half-bytes or 4 - /// bits) that make up the input byte data. - #[inline] - pub fn unpack>(data: T) -> Self { - let data = data.as_ref(); - if data.len() <= 32 { - // SAFETY: checked length. - unsafe { Self::unpack_stack(data) } - } else { - Self::unpack_heap(data) - } - } - - /// Unpacks on the stack. - /// - /// # Safety - /// - /// `data.len()` must be less than or equal to 32. - unsafe fn unpack_stack(data: &[u8]) -> Self { - let mut nibbles = MaybeUninit::<[u8; 64]>::uninit(); - Self::unpack_to(data, nibbles.as_mut_ptr().cast()); - let unpacked_len = data.len() * 2; - Self(SmallVec::from_buf_and_len_unchecked(nibbles, unpacked_len)) - } - - /// Unpacks on the heap. - fn unpack_heap(data: &[u8]) -> Self { - // Collect into a vec directly to avoid the smallvec overhead since we know this is going on - // the heap. - let unpacked_len = data.len() * 2; - let mut nibbles = Vec::with_capacity(unpacked_len); - // SAFETY: enough capacity. - unsafe { Self::unpack_to(data, nibbles.as_mut_ptr()) }; - // SAFETY: within capacity and `unpack_to` initialized the memory. - unsafe { nibbles.set_len(unpacked_len) }; - Self(SmallVec::from_vec(nibbles)) - } - - /// Unpacks into the given pointer. - /// - /// # Safety - /// - /// `ptr` must be valid for at least `data.len() * 2` bytes. - #[inline] - unsafe fn unpack_to(data: &[u8], ptr: *mut u8) { - for (i, &byte) in data.iter().enumerate() { - ptr.add(i * 2).write(byte >> 4); - ptr.add(i * 2 + 1).write(byte & 0x0f); - } - } - - /// Packs the nibbles stored in the struct into a byte vector. - /// - /// This method combines each pair of consecutive nibbles into a single byte, - /// effectively reducing the size of the data by a factor of two. - /// If the number of nibbles is odd, the last nibble is shifted left by 4 bits and - /// added to the packed byte vector. - #[inline] - pub fn pack(&self) -> SmallVec<[u8; 32]> { - if self.len() <= 64 { - // SAFETY: checked length. - unsafe { self.pack_stack() } - } else { - self.pack_heap() - } - } - - /// Packs on the stack. - /// - /// # Safety - /// - /// `self.len()` must be less than or equal to 32. - unsafe fn pack_stack(&self) -> SmallVec<[u8; 32]> { - let mut nibbles = MaybeUninit::<[u8; 32]>::uninit(); - self.pack_to(nibbles.as_mut_ptr().cast()); - let packed_len = (self.len() + 1) / 2; - SmallVec::from_buf_and_len_unchecked(nibbles, packed_len) - } - - /// Packs on the heap. - fn pack_heap(&self) -> SmallVec<[u8; 32]> { - // Collect into a vec directly to avoid the smallvec overhead since we know this is going on - // the heap. - let packed_len = (self.len() + 1) / 2; - let mut vec = Vec::with_capacity(packed_len); - // SAFETY: enough capacity. - unsafe { self.pack_to(vec.as_mut_ptr()) }; - // SAFETY: within capacity and `pack_to` initialized the memory. - unsafe { vec.set_len(packed_len) }; - SmallVec::from_vec(vec) - } - - /// Packs into the given pointer. - /// - /// # Safety - /// - /// `ptr` must be valid for at least `self.len() / 2 + IS_ODD` bytes. - #[inline] - unsafe fn pack_to(&self, ptr: *mut u8) { - for i in 0..self.len() / 2 { - ptr.add(i).write(self.get_byte_unchecked(i * 2)); - } - if self.len() % 2 != 0 { - let i = self.len() / 2; - ptr.add(i).write(self.last().unwrap_unchecked() << 4); - } - } - - /// Gets the byte at the given index by combining two consecutive nibbles. - /// - /// # Safety - /// - /// `i..i + 1` must be in range. - #[inline] - unsafe fn get_byte_unchecked(&self, i: usize) -> u8 { - debug_assert!(i + 1 < self.len(), "index {i}..{} out of bounds of {}", i + 1, self.len()); - let hi = *self.get_unchecked(i); - let lo = *self.get_unchecked(i + 1); - (hi << 4) | lo - } - - /// Encodes a given path leaf as a compact array of bytes, where each byte represents two - /// "nibbles" (half-bytes or 4 bits) of the original hex data, along with additional information - /// about the leaf itself. - /// - /// The method takes the following input: - /// `is_leaf`: A boolean value indicating whether the current node is a leaf node or not. - /// - /// The first byte of the encoded vector is set based on the `is_leaf` flag and the parity of - /// the hex data length (even or odd number of nibbles). - /// - If the node is an extension with even length, the header byte is `0x00`. - /// - If the node is an extension with odd length, the header byte is `0x10 + `. - /// - If the node is a leaf with even length, the header byte is `0x20`. - /// - If the node is a leaf with odd length, the header byte is `0x30 + `. - /// - /// If there is an odd number of nibbles, store the first nibble in the lower 4 bits of the - /// first byte of encoded. - /// - /// # Returns - /// - /// A vector containing the compact byte representation of the nibble sequence, including the - /// header byte. - /// - /// This vector's length is `self.len() / 2 + 1`. For stack-allocated nibbles, this is at most - /// 33 bytes, so 36 was chosen as the stack capacity to round up to the next usize-aligned - /// size. - /// - /// # Examples - /// - /// ``` - /// # use reth_primitives::trie::Nibbles; - /// - /// // Extension node with an even path length: - /// let nibbles = Nibbles::from_nibbles_unchecked(&[0x0A, 0x0B, 0x0C, 0x0D]); - /// assert_eq!(nibbles.encode_path_leaf(false)[..], [0x00, 0xAB, 0xCD]); - /// - /// // Extension node with an odd path length: - /// let nibbles = Nibbles::from_nibbles_unchecked(&[0x0A, 0x0B, 0x0C]); - /// assert_eq!(nibbles.encode_path_leaf(false)[..], [0x1A, 0xBC]); - /// - /// // Leaf node with an even path length: - /// let nibbles = Nibbles::from_nibbles_unchecked(&[0x0A, 0x0B, 0x0C, 0x0D]); - /// assert_eq!(nibbles.encode_path_leaf(true)[..], [0x20, 0xAB, 0xCD]); - /// - /// // Leaf node with an odd path length: - /// let nibbles = Nibbles::from_nibbles_unchecked(&[0x0A, 0x0B, 0x0C]); - /// assert_eq!(nibbles.encode_path_leaf(true)[..], [0x3A, 0xBC]); - /// ``` - pub fn encode_path_leaf(&self, is_leaf: bool) -> SmallVec<[u8; 36]> { - let encoded_len = self.len() / 2 + 1; - let mut encoded = SmallVec::with_capacity(encoded_len); - // SAFETY: enough capacity. - unsafe { self.encode_path_leaf_to(is_leaf, encoded.as_mut_ptr()) }; - // SAFETY: within capacity and `encode_path_leaf_to` initialized the memory. - unsafe { encoded.set_len(encoded_len) }; - encoded - } - - /// # Safety - /// - /// `ptr` must be valid for at least `self.len() / 2 + 1` bytes. - #[inline] - unsafe fn encode_path_leaf_to(&self, is_leaf: bool, ptr: *mut u8) { - let odd_nibbles = self.len() % 2 != 0; - *ptr = self.encode_path_leaf_first_byte(is_leaf, odd_nibbles); - let mut nibble_idx = if odd_nibbles { 1 } else { 0 }; - for i in 0..self.len() / 2 { - ptr.add(i + 1).write(self.get_byte_unchecked(nibble_idx)); - nibble_idx += 2; - } - } - - #[inline] - fn encode_path_leaf_first_byte(&self, is_leaf: bool, odd_nibbles: bool) -> u8 { - match (is_leaf, odd_nibbles) { - (true, true) => 0x30 | self[0], - (true, false) => 0x20, - (false, true) => 0x10 | self[0], - (false, false) => 0x00, - } - } - - /// Increments the nibble sequence by one. - pub fn increment(&self) -> Option { - let mut incremented = self.clone(); - - for nibble in incremented.0.iter_mut().rev() { - debug_assert!(*nibble <= 0xf); - if *nibble < 0xf { - *nibble += 1; - return Some(incremented) - } else { - *nibble = 0; - } - } - - None - } - - /// 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.last() == Some(16) - } - - /// Returns `true` if the current nibble sequence starts with the given prefix. - #[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[i] as usize - } - - /// Returns the first nibble of the current nibble sequence. - #[inline] - pub fn first(&self) -> Option { - self.0.first().copied() - } - - /// Returns the last nibble of the current nibble sequence. - #[inline] - pub fn last(&self) -> Option { - self.0.last().copied() - } - - /// Returns the length of the common prefix between the current nibble sequence and the given. - #[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] { - return i - } - } - len - } - - /// Returns the nibbles as a byte slice. - #[inline] - pub fn as_slice(&self) -> &[u8] { - &self.0 - } - - /// Slice the current nibbles within the provided index range. - /// - /// # Panics - /// - /// Panics if the range is out of bounds. - #[inline] - #[track_caller] - pub fn slice(&self, range: impl RangeBounds) -> Self { - let start = match range.start_bound() { - Bound::Included(&n) => n, - Bound::Excluded(&n) => n.checked_add(1).expect("out of range"), - Bound::Unbounded => 0, - }; - let end = match range.end_bound() { - Bound::Included(&n) => n.checked_add(1).expect("out of range"), - Bound::Excluded(&n) => n, - Bound::Unbounded => self.len(), - }; - Self::from_nibbles_unchecked(&self[start..end]) - } - - /// Join two nibbles together. - #[inline] - pub fn join(&self, b: &Self) -> Self { - let mut nibbles = SmallVec::with_capacity(self.len() + b.len()); - nibbles.extend_from_slice(self); - nibbles.extend_from_slice(b); - Self(nibbles) - } - - /// Pushes a nibble to the end of the current nibbles. - #[inline] - pub fn push(&mut self, nibble: u8) { - self.0.push(nibble); - } - - /// Extend the current nibbles with another nibbles. - #[inline] - pub fn extend_from_slice(&mut self, b: impl AsRef<[u8]>) { - self.0.extend_from_slice(b.as_ref()); - } - - /// Truncates the current nibbles to the given length. - #[inline] - pub fn truncate(&mut self, new_len: usize) { - self.0.truncate(new_len); - } - - /// Clears the current nibbles. - #[inline] - pub fn clear(&mut self) { - self.0.clear(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::hex; - use proptest::prelude::*; - - #[test] - fn hashed_regression() { - let nibbles = Nibbles::from_nibbles_unchecked(hex!("05010406040a040203030f010805020b050c04070003070e0909070f010b0a0805020301070c0a0902040b0f000f0006040a04050f020b090701000a0a040b")); - let path = nibbles.encode_path_leaf(true); - let expected = hex!("351464a4233f1852b5c47037e997f1ba852317ca924bf0f064a45f2b9710aa4b"); - assert_eq!(path[..], expected); - } - - #[test] - fn pack_nibbles() { - let tests = [ - (&[][..], &[][..]), - (&[0xa], &[0xa0]), - (&[0xa, 0x0], &[0xa0]), - (&[0xa, 0xb], &[0xab]), - (&[0xa, 0xb, 0x2], &[0xab, 0x20]), - (&[0xa, 0xb, 0x2, 0x0], &[0xab, 0x20]), - (&[0xa, 0xb, 0x2, 0x7], &[0xab, 0x27]), - ]; - for (input, expected) in tests { - assert!(input.iter().all(|&x| x <= 0xf)); - let nibbles = Nibbles::from_nibbles_unchecked(input); - let encoded = nibbles.pack(); - assert_eq!(&encoded[..], expected); - } - } - - #[test] - fn slice() { - const RAW: &[u8] = &hex!("05010406040a040203030f010805020b050c04070003070e0909070f010b0a0805020301070c0a0902040b0f000f0006040a04050f020b090701000a0a040b"); - - #[track_caller] - fn test_slice(range: impl RangeBounds, expected: &[u8]) { - let nibbles = Nibbles::from_nibbles_unchecked(RAW); - let sliced = nibbles.slice(range); - assert_eq!(sliced, Nibbles::from_nibbles_unchecked(expected)); - assert_eq!(sliced.as_slice(), expected); - } - - test_slice(0..0, &[]); - test_slice(0..1, &[0x05]); - test_slice(1..1, &[]); - test_slice(1..=1, &[0x01]); - test_slice(0..=1, &[0x05, 0x01]); - test_slice(0..2, &[0x05, 0x01]); - - test_slice(..0, &[]); - test_slice(..1, &[0x05]); - test_slice(..=1, &[0x05, 0x01]); - test_slice(..2, &[0x05, 0x01]); - - test_slice(.., RAW); - test_slice(..RAW.len(), RAW); - test_slice(0.., RAW); - test_slice(0..RAW.len(), RAW); - } - - proptest! { - #[test] - fn pack_unpack_roundtrip(input in any::>()) { - let nibbles = Nibbles::unpack(&input); - prop_assert!(nibbles.iter().all(|&nibble| nibble <= 0xf)); - let packed = nibbles.pack(); - prop_assert_eq!(&packed[..], input); - } - - #[test] - fn encode_path_first_byte(input in any::>()) { - prop_assume!(!input.is_empty()); - let input = Nibbles::unpack(input); - prop_assert!(input.iter().all(|&nibble| nibble <= 0xf)); - let input_is_odd = input.len() % 2 == 1; - - let compact_leaf = input.encode_path_leaf(true); - let leaf_flag = compact_leaf[0]; - // Check flag - assert_ne!(leaf_flag & 0x20, 0); - assert_eq!(input_is_odd, (leaf_flag & 0x10) != 0); - if input_is_odd { - assert_eq!(leaf_flag & 0x0f, input.first().unwrap()); - } - - - let compact_extension = input.encode_path_leaf(false); - let extension_flag = compact_extension[0]; - // Check first byte - assert_eq!(extension_flag & 0x20, 0); - assert_eq!(input_is_odd, (extension_flag & 0x10) != 0); - if input_is_odd { - assert_eq!(extension_flag & 0x0f, input.first().unwrap()); - } - } - } -} diff --git a/crates/storage/db/src/tables/codecs/compact.rs b/crates/storage/db/src/tables/codecs/compact.rs index b005cc961e..bcbdb2b1eb 100644 --- a/crates/storage/db/src/tables/codecs/compact.rs +++ b/crates/storage/db/src/tables/codecs/compact.rs @@ -9,17 +9,15 @@ use reth_primitives::{stage::StageCheckpoint, trie::*, *}; macro_rules! impl_compression_for_compact { ($($name:tt),+) => { $( - impl Compress for $name - { + impl Compress for $name { type Compressed = Vec; fn compress_to_buf>(self, buf: &mut B) { - let _ = Compact::to_compact(self, buf); + let _ = Compact::to_compact(self, buf); } } - impl Decompress for $name - { + impl Decompress for $name { fn decompress>(value: B) -> Result<$name, $crate::DatabaseError> { let value = value.as_ref(); let (obj, _) = Compact::from_compact(&value, value.len()); @@ -37,8 +35,8 @@ impl_compression_for_compact!( Receipt, TxType, StorageEntry, - Nibbles, BranchNodeCompact, + StoredNibbles, StoredNibblesSubKey, StorageTrieEntry, StoredBlockBodyIndices, diff --git a/crates/storage/db/src/tables/mod.rs b/crates/storage/db/src/tables/mod.rs index f8d4457ff6..275271f7bc 100644 --- a/crates/storage/db/src/tables/mod.rs +++ b/crates/storage/db/src/tables/mod.rs @@ -36,7 +36,7 @@ use crate::{ }; use reth_primitives::{ stage::StageCheckpoint, - trie::{BranchNodeCompact, Nibbles, StorageTrieEntry, StoredNibblesSubKey}, + trie::{BranchNodeCompact, StorageTrieEntry, StoredNibbles, 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 ) Nibbles | BranchNodeCompact + ( AccountsTrie ) StoredNibbles | BranchNodeCompact ); dupsort!( diff --git a/crates/storage/db/src/tables/models/mod.rs b/crates/storage/db/src/tables/models/mod.rs index 3fe332a60b..5b65c03f30 100644 --- a/crates/storage/db/src/tables/models/mod.rs +++ b/crates/storage/db/src/tables/models/mod.rs @@ -5,7 +5,7 @@ use crate::{ }; use reth_codecs::Compact; use reth_primitives::{ - trie::{Nibbles, StoredNibblesSubKey}, + trie::{StoredNibbles, StoredNibblesSubKey}, Address, PruneSegment, B256, }; @@ -102,18 +102,18 @@ impl Decode for String { } } -impl Encode for Nibbles { +impl Encode for StoredNibbles { type Encoded = Vec; // Delegate to the Compact implementation fn encode(self) -> Self::Encoded { - let mut buf = Vec::with_capacity(self.len()); + let mut buf = Vec::with_capacity(self.0.len()); self.to_compact(&mut buf); buf } } -impl Decode for Nibbles { +impl Decode for StoredNibbles { fn decode>(value: B) -> Result { let buf = value.as_ref(); Ok(Self::from_compact(buf, buf.len()).0) diff --git a/crates/trie/src/trie.rs b/crates/trie/src/trie.rs index ad385a65c7..b5053cbba6 100644 --- a/crates/trie/src/trie.rs +++ b/crates/trie/src/trie.rs @@ -1139,7 +1139,7 @@ mod tests { .iter() .filter_map(|entry| match entry { (TrieKey::AccountNode(nibbles), TrieOp::Update(node)) => { - Some((nibbles.clone(), node.clone())) + Some((nibbles.0.clone(), node.clone())) } _ => None, }) @@ -1163,12 +1163,13 @@ mod tests { // read the account updates from the db let mut accounts_trie = tx.tx_ref().cursor_read::().unwrap(); let walker = accounts_trie.walk(None).unwrap(); - let mut account_updates = HashMap::new(); - for item in walker { - let (key, node) = item.unwrap(); - account_updates.insert(key, node); - } - + let account_updates = walker + .into_iter() + .map(|item| { + let (key, node) = item.unwrap(); + (key.0, node) + }) + .collect(); assert_trie_updates(&account_updates); } @@ -1227,7 +1228,7 @@ mod tests { .iter() .filter_map(|entry| match entry { (TrieKey::StorageNode(_, nibbles), TrieOp::Update(node)) => { - Some((nibbles.clone().into(), node.clone())) + Some((nibbles.0.clone(), node.clone())) } _ => None, }) diff --git a/crates/trie/src/trie_cursor/account_cursor.rs b/crates/trie/src/trie_cursor/account_cursor.rs index 94e7be5901..95033fae6d 100644 --- a/crates/trie/src/trie_cursor/account_cursor.rs +++ b/crates/trie/src/trie_cursor/account_cursor.rs @@ -1,7 +1,7 @@ use super::TrieCursor; use crate::updates::TrieKey; use reth_db::{cursor::DbCursorRO, tables, DatabaseError}; -use reth_primitives::trie::{BranchNodeCompact, Nibbles}; +use reth_primitives::trie::{BranchNodeCompact, StoredNibbles}; /// A cursor over the account trie. #[derive(Debug)] @@ -18,20 +18,20 @@ impl TrieCursor for AccountTrieCursor where C: DbCursorRO, { - type Key = Nibbles; + type Key = StoredNibbles; fn seek_exact( &mut self, key: Self::Key, ) -> Result, BranchNodeCompact)>, DatabaseError> { - Ok(self.0.seek_exact(key)?.map(|value| (value.0.to_vec(), value.1))) + Ok(self.0.seek_exact(key)?.map(|value| (value.0 .0.to_vec(), value.1))) } fn seek( &mut self, key: Self::Key, ) -> Result, BranchNodeCompact)>, DatabaseError> { - Ok(self.0.seek(key)?.map(|value| (value.0.to_vec(), value.1))) + Ok(self.0.seek(key)?.map(|value| (value.0 .0.to_vec(), value.1))) } fn current(&mut self) -> Result, DatabaseError> { @@ -79,13 +79,13 @@ mod tests { } let db_data = cursor.walk_range(..).unwrap().collect::, _>>().unwrap(); - 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!(db_data[0].0 .0.to_vec(), data[0]); + assert_eq!(db_data[1].0 .0.to_vec(), data[1]); + assert_eq!(db_data[2].0 .0.to_vec(), data[2]); + assert_eq!(db_data[3].0 .0.to_vec(), data[3]); assert_eq!( - cursor.seek(hex!("0303040f").to_vec().into()).unwrap().map(|(k, _)| k.to_vec()), + cursor.seek(hex!("0303040f").to_vec().into()).unwrap().map(|(k, _)| k.0.to_vec()), Some(data[1].clone()) ); } diff --git a/crates/trie/src/updates.rs b/crates/trie/src/updates.rs index 9a3da6c542..9d7bfcbb31 100644 --- a/crates/trie/src/updates.rs +++ b/crates/trie/src/updates.rs @@ -5,7 +5,7 @@ use reth_db::{ transaction::{DbTx, DbTxMut}, }; use reth_primitives::{ - trie::{BranchNodeCompact, Nibbles, StorageTrieEntry, StoredNibblesSubKey}, + trie::{BranchNodeCompact, Nibbles, StorageTrieEntry, StoredNibbles, 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(Nibbles), + AccountNode(StoredNibbles), /// A node in the storage trie. StorageNode(B256, StoredNibblesSubKey), /// Storage trie of an account. @@ -79,9 +79,9 @@ impl TrieUpdates { /// Extend the updates with account trie updates. pub fn extend_with_account_updates(&mut self, updates: HashMap) { self.extend( - updates - .into_iter() - .map(|(nibbles, node)| (TrieKey::AccountNode(nibbles), TrieOp::Update(node))), + updates.into_iter().map(|(nibbles, node)| { + (TrieKey::AccountNode(nibbles.into()), TrieOp::Update(node)) + }), ); } @@ -121,7 +121,7 @@ impl TrieUpdates { } } TrieOp::Update(node) => { - if !nibbles.is_empty() { + if !nibbles.0.is_empty() { account_trie_cursor.upsert(nibbles, node)?; } }