diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 4340e33f1..251f47fc4 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -108,17 +108,32 @@ impl std::fmt::Debug for ProofNode { } impl Hashable for ProofNode { - fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ { + type LeadingPath<'a> + = &'a [PathComponent] + where + Self: 'a; + + type PartialPath<'a> + = &'a [PathComponent] + where + Self: 'a; + + type FullPath<'a> + = &'a [PathComponent] + where + Self: 'a; + + fn parent_prefix_path(&self) -> Self::LeadingPath<'_> { let (prefix, _) = self.key.split_at(self.partial_len); prefix } - fn partial_path(&self) -> impl IntoSplitPath + '_ { + fn partial_path(&self) -> Self::PartialPath<'_> { let (_, suffix) = self.key.split_at(self.partial_len); suffix } - fn full_path(&self) -> impl IntoSplitPath + '_ { + fn full_path(&self) -> Self::FullPath<'_> { &self.key } diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 58501298a..4eb0c6810 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -3,7 +3,6 @@ use crate::{ Children, HashType, HashableShunt, IntoSplitPath, Node, Path, PathComponent, SplitPath, - TriePath, }; use smallvec::SmallVec; @@ -153,22 +152,32 @@ impl> AsRef<[u8]> for ValueDigest { /// A node in the trie that can be hashed. pub trait Hashable: std::fmt::Debug { + /// The type of the leading path. + type LeadingPath<'a>: IntoSplitPath + 'a + where + Self: 'a; + + /// The type of the partial path. + type PartialPath<'a>: IntoSplitPath + 'a + where + Self: 'a; + + /// The type of the full path. + type FullPath<'a>: IntoSplitPath + 'a + where + Self: 'a; + /// The full path of this node's parent where each byte is a nibble. - fn parent_prefix_path(&self) -> impl IntoSplitPath + '_; + fn parent_prefix_path(&self) -> Self::LeadingPath<'_>; /// The partial path of this node where each byte is a nibble. - fn partial_path(&self) -> impl IntoSplitPath + '_; + fn partial_path(&self) -> Self::PartialPath<'_>; + /// The full path of this node including the parent's prefix where each byte is a nibble. + fn full_path(&self) -> Self::FullPath<'_>; /// The node's value or hash. fn value_digest(&self) -> Option>; /// Each element is a child's index and hash. /// Yields 0 elements if the node is a leaf. fn children(&self) -> Children>; - - /// The full path of this node including the parent's prefix where each byte is a nibble. - fn full_path(&self) -> impl IntoSplitPath + '_ { - self.parent_prefix_path() - .into_split_path() - .append(self.partial_path().into_split_path()) - } } /// A preimage of a hash. diff --git a/storage/src/hashedshunt.rs b/storage/src/hashedshunt.rs index ab7306a7e..fe5bb1d23 100644 --- a/storage/src/hashedshunt.rs +++ b/storage/src/hashedshunt.rs @@ -1,7 +1,7 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{Children, HashType, Hashable, IntoSplitPath, SplitPath, ValueDigest}; +use crate::{Children, HashType, Hashable, JoinedPath, SplitPath, ValueDigest}; /// A shunt for a hasheable trie that we can use to compute the hash of a node /// using component parts. @@ -51,14 +51,33 @@ impl std::fmt::Debug for HashableShunt<'_, P1, P2> } impl Hashable for HashableShunt<'_, P1, P2> { - fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ { + type LeadingPath<'a> + = P1 + where + Self: 'a; + + type PartialPath<'a> + = P2 + where + Self: 'a; + + type FullPath<'a> + = JoinedPath + where + Self: 'a; + + fn parent_prefix_path(&self) -> Self::LeadingPath<'_> { self.parent_prefix } - fn partial_path(&self) -> impl IntoSplitPath + '_ { + fn partial_path(&self) -> Self::PartialPath<'_> { self.partial_path } + fn full_path(&self) -> Self::FullPath<'_> { + self.parent_prefix_path().append(self.partial_path()) + } + fn value_digest(&self) -> Option> { self.value.clone() } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 0c290bb66..b48a2af25 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -66,7 +66,7 @@ pub use path::{ pub use path::{PackedBytes, PackedPathComponents, PackedPathRef}; pub use tries::{ DuplicateKeyError, HashedKeyValueTrieRoot, HashedTrieNode, IterAscending, IterDescending, - KeyValueTrieRoot, TrieEdgeIter, TrieEdgeState, TrieNode, TrieValueIter, + KeyValueTrieRoot, TrieEdgeIter, TrieEdgeState, TrieNode, TriePathIter, TrieValueIter, }; pub use u4::{TryFromIntError, U4}; diff --git a/storage/src/tries/iter.rs b/storage/src/tries/iter.rs index a874d9edc..4f863aeaa 100644 --- a/storage/src/tries/iter.rs +++ b/storage/src/tries/iter.rs @@ -1,7 +1,9 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{HashType, PathBuf, PathComponent, TrieNode, TriePath, tries::TrieEdgeState}; +use crate::{ + HashType, IntoSplitPath, PathBuf, PathComponent, SplitPath, TrieEdgeState, TrieNode, TriePath, +}; /// A marker type for [`TrieEdgeIter`] that indicates that the iterator traverses /// the trie in ascending order. @@ -33,6 +35,18 @@ pub struct TrieValueIter<'a, N: ?Sized, V: ?Sized, D> { edges: TrieEdgeIter<'a, N, V, D>, } +/// An iterator over the edges along a specified path in a key-value trie +/// terminating at the node corresponding to the path, if it exists; otherwise, +/// terminating at the deepest existing edge along the path. +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct TriePathIter<'a, P, N: ?Sized, V: ?Sized> { + needle: P, + current_path: PathBuf, + current_edge: Option>, + marker: std::marker::PhantomData, +} + #[derive(Debug)] struct Frame<'a, N: ?Sized, V: ?Sized> { node: &'a N, @@ -83,6 +97,26 @@ where } } +impl<'a, P, N, V> TriePathIter<'a, P, N, V> +where + P: SplitPath, + N: TrieNode + ?Sized, + V: AsRef<[u8]> + ?Sized, +{ + /// Creates a new iterator over the edges along the given path in the + /// specified key-value trie. + pub const fn new(root: &'a N, root_hash: Option<&'a HashType>, path: P) -> Self { + let mut this = Self { + needle: path, + current_path: PathBuf::new_const(), + current_edge: None, + marker: std::marker::PhantomData, + }; + this.current_edge = Some(TrieEdgeState::from_node(root, root_hash)); + this + } +} + /// Both iterators share this logic to descend into a node's children. /// /// The passed in `children_iter` should be an iterator over the indices into @@ -218,6 +252,45 @@ where } } +impl<'a, P, N, V> Iterator for TriePathIter<'a, P, N, V> +where + P: SplitPath, + N: TrieNode + ?Sized, + V: AsRef<[u8]> + ?Sized, +{ + type Item = (PathBuf, TrieEdgeState<'a, N>); + + fn next(&mut self) -> Option { + // qualified path to `Option::take` because rust-analyzer thinks + // `self.current_edge` is an `Iterator` and displays an error for + // `self.current_edge.take()` where `rustc` does not + let edge = Option::take(&mut self.current_edge)?; + let path = self.current_path.clone(); + + let Some(node) = edge.node() else { + // the current edge is remote, so we cannot descend further + return Some((path, edge)); + }; + + self.current_path.extend(node.partial_path().components()); + + if let (None, Some((pc, needle_suffix)), _) = node + .partial_path() + .into_split_path() + .longest_common_prefix(self.needle) + .split_first_parts() + { + // the target path continues beyond the current node, descend + // into it if there's a child along the path + self.current_path.push(pc); + self.current_edge = node.child_state(pc); + self.needle = needle_suffix; + } + + Some((path, edge)) + } +} + // auto-derived implementations would require N: Clone, V: Clone which is too much impl Clone for TrieEdgeIter<'_, N, V, D> { @@ -247,6 +320,23 @@ impl Clone for TrieValueIter<'_, N, V, D> { } } +impl Clone for TriePathIter<'_, P, N, V> { + fn clone(&self) -> Self { + Self { + needle: self.needle, + current_path: self.current_path.clone(), + current_edge: self.current_edge, + marker: std::marker::PhantomData, + } + } + + fn clone_from(&mut self, source: &Self) { + self.needle = source.needle; + self.current_path.clone_from(&source.current_path); + self.current_edge = source.current_edge; + } +} + impl Clone for Frame<'_, N, V> { fn clone(&self) -> Self { Self { diff --git a/storage/src/tries/kvp.rs b/storage/src/tries/kvp.rs index 1af0e65b5..956ff389a 100644 --- a/storage/src/tries/kvp.rs +++ b/storage/src/tries/kvp.rs @@ -4,8 +4,8 @@ #[cfg(not(feature = "branch_factor_256"))] use crate::PackedPathRef; use crate::{ - Children, HashType, Hashable, HashableShunt, HashedTrieNode, PathBuf, PathComponent, PathGuard, - SplitPath, TrieNode, TriePath, TriePathFromPackedBytes, ValueDigest, + Children, HashType, Hashable, HashableShunt, HashedTrieNode, JoinedPath, PathBuf, + PathComponent, PathGuard, SplitPath, TrieNode, TriePath, TriePathFromPackedBytes, ValueDigest, }; #[cfg(feature = "branch_factor_256")] @@ -280,7 +280,12 @@ impl<'a, T: AsRef<[u8]> + ?Sized> HashedKeyValueTrieRoot<'a, T> { } impl + ?Sized> TrieNode for KeyValueTrieRoot<'_, T> { - fn partial_path(&self) -> impl SplitPath + '_ { + type PartialPath<'a> + = PackedPathRef<'a> + where + Self: 'a; + + fn partial_path(&self) -> Self::PartialPath<'_> { self.partial_path } @@ -305,7 +310,12 @@ impl + ?Sized> TrieNode for KeyValueTrieRoot<'_, T> { } impl + ?Sized> TrieNode for HashedKeyValueTrieRoot<'_, T> { - fn partial_path(&self) -> impl SplitPath + '_ { + type PartialPath<'a> + = PackedPathRef<'a> + where + Self: 'a; + + fn partial_path(&self) -> Self::PartialPath<'_> { self.partial_path } @@ -334,15 +344,34 @@ impl + ?Sized> HashedTrieNode for HashedKeyValueTrieRoot<'_, T } } -impl + ?Sized> Hashable for HashedKeyValueTrieRoot<'_, T> { - fn parent_prefix_path(&self) -> impl crate::IntoSplitPath + '_ { - self.leading_path.as_slice() +impl<'a, T: AsRef<[u8]> + ?Sized> Hashable for HashedKeyValueTrieRoot<'a, T> { + type LeadingPath<'b> + = &'b [PathComponent] + where + Self: 'b; + + type PartialPath<'b> + = PackedPathRef<'a> + where + Self: 'b; + + type FullPath<'b> + = JoinedPath<&'b [PathComponent], PackedPathRef<'a>> + where + Self: 'b; + + fn parent_prefix_path(&self) -> Self::LeadingPath<'_> { + &self.leading_path } - fn partial_path(&self) -> impl crate::IntoSplitPath + '_ { + fn partial_path(&self) -> Self::PartialPath<'_> { self.partial_path } + fn full_path(&self) -> Self::FullPath<'_> { + self.parent_prefix_path().append(self.partial_path) + } + fn value_digest(&self) -> Option> { self.value.map(|v| ValueDigest::Value(v.as_ref())) } @@ -472,6 +501,49 @@ mod tests { bytes } + #[cfg(feature = "branch_factor_256")] + const fn ascii_components( + hex: &[u8; FROM], + ) -> [PathComponent; TO] { + #[expect(unsafe_code)] + // SAFETY: `from_ascii` will generate a compiler error if `FROM != TO * 2`, + // and `PathComponent` is `u8` newtype wrapper. + unsafe { + std::ptr::from_ref(&from_ascii::(hex)) + .cast::<[PathComponent; TO]>() + .read() + } + } + + #[cfg(not(feature = "branch_factor_256"))] + const fn ascii_components(hex: &[u8; LEN]) -> [PathComponent; LEN] { + #![expect(clippy::indexing_slicing, clippy::arithmetic_side_effects)] + + const fn from_hex_char(c: u8) -> u8 { + match c { + b'0'..=b'9' => c - b'0', + b'a'..=b'f' => c - b'a' + 10, + b'A'..=b'F' => c - b'A' + 10, + _ => panic!("invalid hex character"), + } + } + + let mut out = *hex; + let mut i = 0_usize; + while i < LEN { + out[i] = from_hex_char(out[i]); + i += 1; + } + + #[expect(unsafe_code)] + // SAFETY: we converted each byte to a value in 0..=15 above + unsafe { + std::ptr::from_ref(&out) + .cast::<[PathComponent; LEN]>() + .read() + } + } + /// A macro to select the expected hash based on the enabled cargo features. /// /// For both merkledb hash types, only a 64-character hex string (32 bytes) @@ -535,6 +607,39 @@ mod tests { }; } + macro_rules! path { + ($($pc:expr),* $(,)?) => { + [$(const { $crate::PathComponent::ALL[$pc] },)*] + }; + } + + macro_rules! cfg_alt { + ( + $cfg:meta => $enabled:block else $disabled:block + ) => { + match () { + #[cfg($cfg)] + () => $enabled + #[cfg(not($cfg))] + () => $disabled + } + }; + } + + macro_rules! ascii_path { + ($hex:expr) => { + const { + cfg_alt! { + feature = "branch_factor_256" => { + self::ascii_components::<{ $hex.len() }, { $hex.len() / 2 }>($hex) + } else { + self::ascii_components::<{ $hex.len() }>($hex) + } + } + } + }; + } + #[test_case(&[])] #[test_case(&[("a", "1")])] #[test_case(&[("a", "1"), ("b", "2")])] @@ -674,4 +779,566 @@ mod tests { assert_eq!(*root.computed(), root_hash); assert_eq!(*root.computed(), crate::Preimage::to_hash(&*root)); } + + macro_rules! long_key_pair { + ($key:expr) => { + ( + from_ascii::<{ $key.len() }, { $key.len() / 2 }>($key), + #[expect(unsafe_code)] + // SAFETY: from_ascii will produce a compiler error if $key is not valid ASCII + unsafe { + std::str::from_utf8_unchecked($key) + }, + ) + }; + } + + const LONG_KEY_SET: &[([u8; 32], &str)] = &[ + long_key_pair!(b"9e2df1e1a8198beda99bf3c002ca8347c6b523cce0fe4ca070fede084533ddda"), // depth = 3 + long_key_pair!(b"9e367d8b8d265c12f39b8a70fc71516e5bdc42ff2f76414498b3132ab1bf20d1"), // depth = 5 + long_key_pair!(b"9e36ecf376c2c57cbf812580866369ed3286dd83a4063ac145ec3c9e9de4b3a5"), // depth = 6 + long_key_pair!(b"9e36ef9105e33126cc3f42d52038cc6f611dbaae8235b5e1a10df86785915bac"), // depth = 6 + long_key_pair!(b"9e379f3b5f7f4bb72dd556dc0fe1dc5919a7f4dcd062f79693c530384acba92f"), // depth = 4 + long_key_pair!(b"9e77f62a25a9aa1836b06bb011a30f802094ef82e09325f15912a79d3e78cdde"), // depth = 3 + long_key_pair!(b"9e8ce4e24ca69d690c243aae6368d2693de30d71c2f05d51dd9b0407db2c05fe"), // depth = 3 + long_key_pair!(b"9e90247c3ee40b364e7ce377e7025acc866c7ac367d403c56e996a7225a9ab86"), // depth = 4 + long_key_pair!(b"9e9277cb39f210dfc44a61bde3d4945d7a98746d101c8eca1d77e3edf3cf705b"), // depth = 4 + long_key_pair!(b"9e9ae6b2be923b97ddaa422676faf5d7d5ed3161da3f597d5fcfb7a62977e2c8"), // depth = 4 + long_key_pair!(b"9e9d11c32010a3c62cf9623b30552fef9ed5ad8ca5bc5f6e5c67a007ea9b670b"), // depth = 4 + long_key_pair!(b"9ea24f32d07ee611e7b2f5a4d712ca013373281539a8666ea7ce610b9a7b97b9"), // depth = 3 + long_key_pair!(b"9ed6588c139b43b4563614d65f66fadd6571c11d451f3a8748696965b9dfb755"), // depth = 3 + long_key_pair!(b"9ee17f3fb0c2f08da973d1cd45c5c58e302f662926072ae363dce3fd9bb70510"), // depth = 3 + long_key_pair!(b"eb06927c08626368ef54ea9c9df813b42066bb6bd38e57cf3adcb1a73d151c96"), // depth = 3 + long_key_pair!(b"eb1420287f09faf6e7420a0076b3349bcb520d6c7071075b697d94b9c3c958bc"), // depth = 5 + long_key_pair!(b"eb14d141fd031dfa7cc2cdb7206916d0958eff25b33cb7c8b1c12704eafe6596"), // depth = 5 + long_key_pair!(b"eb1609842d46cffee7ed2aca354aa674154622235d21a5cdafbcac269450a27a"), // depth = 4 + long_key_pair!(b"eb21ca2f92bb3006fa04266aa5ce79523be99815046d5c085d2f9cd9d3d7cb02"), // depth = 3 + long_key_pair!(b"eb4172d683a2ae3ee91538a24ac3fdbef3120f88b3196a0dad11f74ffaa501f3"), // depth = 4 + long_key_pair!(b"eb4e17efc1badd021349d296a2f7b487a4876f2558f19ff705196331ff41c3cb"), // depth = 4 + long_key_pair!(b"eb5b4d35da26675cec092e942c5b1185f5b9e9dbf8f206b82f840f56642694ba"), // depth = 3 + long_key_pair!(b"eb71bdb74187a557754c15913fcac8b09d2aa584b920b48cd053c299c1f54d53"), // depth = 4 + long_key_pair!(b"eb77af42e8a3f6563da934ebe9717043efd4b0159ea3ab159da2fd6ac6c5c9bd"), // depth = 4 + long_key_pair!(b"eb83fc769ccf18bcdb3524fe4f6ecff858f42e557e45e1db09d46d9652f213ab"), // depth = 3 + long_key_pair!(b"ebaae8982c9baa21fbecf910e534c131bb8ef521ca31916f41c76db6e77e8610"), // depth = 3 + long_key_pair!(b"ebbde6c502af6a9d7b9528ca4771eeb12387a070d12339bcec4c4fbe03684e2f"), // depth = 3 + long_key_pair!(b"ebc14bc65b309689b407750345315f3462b02790f5b5502bd468f10a36c3ea4b"), // depth = 3 + long_key_pair!(b"ebd914688a5ccd5d6f7a5abb5403844b05481a61f86e0394a8d5c4ebbb3896b1"), // depth = 3 + long_key_pair!(b"ebe851e66cfdd6de7aa70e9a3388544cfbf52a488885c7a8f30b73a1ddee297e"), // depth = 3 + long_key_pair!(b"f90a98cb2c3634c6dafddb6342fb593d85fbe4c8e84eca1fdf3ef6a1c466d197"), // depth = 4 + long_key_pair!(b"f90a9ed5e87f13ddd6520b10625155a4494bc3c8ce4f1a4ce689bae4273b45a3"), // depth = 4 + long_key_pair!(b"f913f77bd5bdaeb1c25279835da3a9a6155bc5b2a250ca0a509fb236ee8f47bb"), // depth = 3 + long_key_pair!(b"f92ee120334ec8420c009c1720ff2a84be80928c68ce03b0537c5c239aa76a81"), // depth = 3 + long_key_pair!(b"f93924550802cb3ca89d00d99b0efc8d293a435212e4ab7d31e34d50208f2cb2"), // depth = 4 + long_key_pair!(b"f93e2d56543b0b66a1d6cfc246c9adf8d3fcde19cdd3ed9978cb1653417abde7"), // depth = 4 + long_key_pair!(b"f94269a17b5217e011bf1cf94c6647ed2e331628cd6ab2a2c4adb8e9e00f482d"), // depth = 4 + long_key_pair!(b"f9435a8fe5987742e08e24474278ccf689e0fe08cedab15c2fe30abf5b00ad94"), // depth = 5 + long_key_pair!(b"f9439c4a048f5dcfee0381c9a61e1d4a2a3e6e81e461ee1d9ba83d8c074cf919"), // depth = 5 + long_key_pair!(b"f94e6d76b6ef04457f79c9d1f7ea77533b1590b70974f1731ab6ab9b4d0c193e"), // depth = 4 + long_key_pair!(b"f95d29561ae7a697063d75cd321ad14a84ad27f57595e06ea9ab0fd277ba6cbb"), // depth = 4 + long_key_pair!(b"f95edeb78fb4bff225a042587ff1dba23cb7eb56f666c86722884c85e9a42616"), // depth = 4 + long_key_pair!(b"f9615793a5ca45058d25ba572cb38ea6ee886b70075b59bf24ce9631af3b0f0b"), // depth = 3 + long_key_pair!(b"f9759fbe1b46cdb0d657611b9be5404cc190ec1943bc15dd796cb10bfcd9cde7"), // depth = 3 + long_key_pair!(b"f98302fafb51fbcc620c71243a36596469844ecef76f9e54a03483d986c7309e"), // depth = 4 + long_key_pair!(b"f98710e3ed03c10a258f90c81e54d38bb64ba3fd6bb1ef900fbb06966cebb19f"), // depth = 4 + long_key_pair!(b"f992d982ccd6a9d10d4cb4afd1716015dd29e80d1972a6a5a4bdee7a530b4e67"), // depth = 3 + long_key_pair!(b"f9a614f03f2847ca35de1e2edd5f51ba9749e26196e829247bb3981a87d6c435"), // depth = 4 + long_key_pair!(b"f9adb4a95126734721f73d801416938ffe2a4b3c72d72b995465ebbfb939c592"), // depth = 4 + long_key_pair!(b"f9aeacacf382df181d159d999590426c0f461cbd61f821bcae37755280f3710c"), // depth = 4 + long_key_pair!(b"f9c222b1c4b822e2f170395fae0ba62137b49b23ee508973e1da2ad1b6046d43"), // depth = 4 + long_key_pair!(b"f9c2317e02eedb85dd790f119b9aef9041441829e14adad0803b88f2f26feb18"), // depth = 4 + long_key_pair!(b"f9c29f5cc179114beeb9bed23e34ca153a811d0498bf330ee3298a6720c3e9a5"), // depth = 4 + long_key_pair!(b"f9e188863067667acc89e4c5b952223c0011f88b583032273611c24b7e9fe3cc"), // depth = 3 + long_key_pair!(b"fa1138a84122dac254c467151f5f653b161c70018221952aa71c64d4615e7437"), // depth = 3 + long_key_pair!(b"fa225077c7e3305278560eeda2b28affc8fcfb50e7b5703a5b8ab228ef14c5c3"), // depth = 3 + long_key_pair!(b"fa3dc3442ccb6cc5e94d40178e54dcef15d9357fd88289781eaebe1239218fb5"), // depth = 3 + long_key_pair!(b"fa414ef4c7275836acc75243f51e4bcae0c2d300e5c04eddcb3d6c90b17e69d6"), // depth = 4 + long_key_pair!(b"fa440a6d6a070bf25ebae06125c7f962454d414a081e8346eec9e42fd851a488"), // depth = 5 + long_key_pair!(b"fa4485d725131d0b1e6fe516549f4c77764f9399645635f4eea0f35359be2ce8"), // depth = 5 + long_key_pair!(b"fa58df17031bab829a10bd52e2ab6f5228a45191a081130ae6283bcbeb1bba9d"), // depth = 3 + long_key_pair!(b"fa68816ea08f0eaf71f3fe5ee8cf531830c97e37069171d80e3add109b5391b8"), // depth = 3 + long_key_pair!(b"fa9a54da4b442e0de8e4750ed1c2efb3f0b453dfc126ca66e983e5abe0d86eea"), // depth = 3 + long_key_pair!(b"faadf92a924f7ee5b855cedf279998a56765b8994a4ddf4868f793d13470c980"), // depth = 4 + long_key_pair!(b"faaf320723637c35d96cac594f2c68993dee1cb876685de926b300324f615045"), // depth = 4 + long_key_pair!(b"fab36cb460c70adf7b94b668d7cefea73f7586d8f7addaa49ff57095c253ccd4"), // depth = 3 + long_key_pair!(b"fad28df88771f075a733204da373ec96a2619a1de28678dff7b0a9584667b1c8"), // depth = 3 + long_key_pair!(b"fd06d0334f0e127e2ba4982cf0d90d246997b2dcac20bdd3ff97beec06e8cb6b"), // depth = 4 + long_key_pair!(b"fd0e96768b07575f2b4c77d2e556cddfcfda408a1d7e560141fa4397918d26e0"), // depth = 4 + long_key_pair!(b"fd11ab24a03f0700219606aa8140d1147189a00db459a835a1d2ab1e40b1a60c"), // depth = 3 + long_key_pair!(b"fd242e9529f5f82b0f195320ee47ffef8bf321f630e456e8056b92e6256c6458"), // depth = 4 + long_key_pair!(b"fd2512ab6c89c7325e307d7dee5864a0e11d005d87f0e741ea96d7907f1cf49c"), // depth = 4 + long_key_pair!(b"fd2f35fc4a3bdf4410db224a3d50c65d850114cedd2f3c7ec77295ea1fa75d74"), // depth = 4 + long_key_pair!(b"fd3f4f56662a1c0ca63ebd031baa76ac19d0dd57993cd5565efd070fab0aeb06"), // depth = 3 + long_key_pair!(b"fd48cb88800879376d7015ac6757e5eb4b2c78630eda4ab5f371df4d3b88d1da"), // depth = 4 + long_key_pair!(b"fd4f53a0048add43827fb222adc145a49e3894e3fd0339b714857693dd4beedc"), // depth = 5 + long_key_pair!(b"fd4fa6f8d329df093d0d5f70862d4223f02bb1bad394021b6f57fad6bf621f2d"), // depth = 5 + long_key_pair!(b"fd5f5522f7dec0d9125fa051d651b15fb7c1804b0f1ce8dd3ed8257e15b5fe6a"), // depth = 3 + long_key_pair!(b"fd81510790c4c28ed405523efa9932e61e6f9380c53e0c7c22824c2f7595488d"), // depth = 4 + long_key_pair!(b"fd8503ab930fe253c9bf992a521a411c949a06cc1d6cecec2b870c7a9a5309ea"), // depth = 4 + long_key_pair!(b"fda7f08bb7e5bfbf86db4519f7fe0e7c2dec8e853288f2541be7d8f47820458e"), // depth = 4 + long_key_pair!(b"fdaca64ec8eb2a3f51e875f1c44cca3f22318a749b18e609e8ab988036d079cb"), // depth = 4 + long_key_pair!(b"fdda583b950fc10c2f96946bb1d949ceaa6fe718fa19c86d2ea99eeb8eaa9e13"), // depth = 3 + long_key_pair!(b"fde29b48b48655c051dd2289077385ee2ca617d7dd350fbe3742df38209a1195"), // depth = 4 + long_key_pair!(b"fdea9ef007c31c349a31df4aaeaa7ede0f0b732683f0f8b91dc241b55e75e17c"), // depth = 4 + long_key_pair!(b"fdef3f6b6f9bf08f0f1efe29f1718e5dc24bdb4812b12f7a58216326c9b38234"), // depth = 4 + long_key_pair!(b"fe0353c858f534524ede3ff8d3750167d6c9bdd34fee3f62b0e7d0c13928774c"), // depth = 4 + long_key_pair!(b"fe04306a4280ec088ffe79152371bd9b59818d30a38e4d7a4b1ebcc1dbbb152c"), // depth = 5 + long_key_pair!(b"fe04f6de6804017d6fda2084bdc57f2dd65283afc25c8e8b058f6931198e9508"), // depth = 5 + long_key_pair!(b"fe160187ba7863da61e125acde3980bdf8758b4086444f0d37415a7aeac990e1"), // depth = 3 + long_key_pair!(b"fe2988819bfef15e3e04a588cc956344faadb8f338196b15faf72b65fa503e1e"), // depth = 4 + long_key_pair!(b"fe2b9b759d7cd6a1692c2de0e3f856b8efddd597b18778cd0966e6912708c766"), // depth = 4 + long_key_pair!(b"fe79626c973f6d37e6f305a1c315452ee3315632ddc39add009835cdcebd7fbc"), // depth = 3 + long_key_pair!(b"fea484f01ce78325be9165b68b0acdf5969106ccf9f07f0cd6c76cdbf59daae9"), // depth = 4 + long_key_pair!(b"fea6525f4ca140405efe4617b68380f035455d7cdb6e0ba865ccd662c8a9f673"), // depth = 4 + long_key_pair!(b"fee788f6fae1bb133b0c60ae759a111664db6412db307afe0fad6096e67cb7e4"), // depth = 4 + long_key_pair!(b"feecd685e8775fa02103cbefe60f86a7aa345bc607645ea98811cfafd86ee18f"), // depth = 4 + long_key_pair!(b"fef73561e314d7dda4de5926ffc8b0e938cc97193c7b7011a17fd2ee591350bb"), // depth = 3 + ]; + + struct IterPathTestCaseEdge<'a> { + leading_path: &'a [PathComponent], + node_path: &'a [PathComponent], + node_value: Option<&'a str>, + expected_children: &'a [PathComponent], + } + + #[test_case( + &[("a", "1")], + "a", + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path!(0x61), + node_value: Some("1"), + expected_children: &path![], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![6, 1], + node_value: Some("1"), + expected_children: &path![], + }, + ] + } + }; + "single key, present" + )] + #[test_case( + &[("a", "1")], + "b", + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path!(0x61), + node_value: Some("1"), + expected_children: &path![], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![6, 1], + node_value: Some("1"), + expected_children: &path![], + }, + ] + } + }; + "single key, missing after" + )] + #[test_case( + &[("a", "1")], + "0", + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path!(0x61), + node_value: Some("1"), + expected_children: &path![], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![6, 1], + node_value: Some("1"), + expected_children: &path![], + }, + ] + } + }; + "single key, missing before" + )] + #[test_case( + &[("a", "1"), ("b", "2")], + "a", + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![], + node_value: None, + expected_children: &path![0x61, 0x62], + }, + IterPathTestCaseEdge { + leading_path: &path![0x61], + node_path: &path![], + node_value: Some("1"), + expected_children: &path![], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![6], + node_value: None, + expected_children: &path![1, 2], + }, + IterPathTestCaseEdge { + leading_path: &path![6, 1], + node_path: &path![], + node_value: Some("1"), + expected_children: &path![], + }, + ] + } + }; + "two disjoint keys, first present" + )] + #[test_case( + &[("a", "1"), ("b", "2")], + "b", + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![], + node_value: None, + expected_children: &path![0x61, 0x62], + }, + IterPathTestCaseEdge { + leading_path: &path![0x62], + node_path: &path![], + node_value: Some("2"), + expected_children: &path![], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![6], + node_value: None, + expected_children: &path![1, 2], + }, + IterPathTestCaseEdge { + leading_path: &path![6, 2], + node_path: &path![], + node_value: Some("2"), + expected_children: &path![], + }, + ] + } + }; + "two disjoint keys, second present" + )] + #[test_case( + &[("a", "1"), ("b", "2")], + "bad", + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![], + node_value: None, + expected_children: &path![0x61, 0x62], + }, + IterPathTestCaseEdge { + leading_path: &path![0x62], + node_path: &path![], + node_value: Some("2"), + expected_children: &path![], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![6], + node_value: None, + expected_children: &path![1, 2], + }, + IterPathTestCaseEdge { + leading_path: &path![6, 2], + node_path: &path![], + node_value: Some("2"), + expected_children: &path![], + }, + ] + } + }; + "two disjoint keys, missing within" + )] + #[test_case( + &[("a", "1"), ("b", "2")], + "0", + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![], + node_value: None, + expected_children: &path![0x61, 0x62], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![6], + node_value: None, + expected_children: &path![1, 2], + }, + ] + } + }; + "two disjoint keys, missing before" + )] + #[test_case( + &[("a", "1"), ("b", "2")], + "z", + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![], + node_value: None, + expected_children: &path![0x61, 0x62], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![6], + node_value: None, + expected_children: &path![1, 2], + }, + ] + } + }; + "two disjoint keys, missing after" + )] + #[test_case( + LONG_KEY_SET, + from_ascii::<64, 32>(b"9e36ecf376c2c57cbf812580866369ed3286dd83a4063ac145ec3c9e9de4b3a5"), + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &ascii_path!(b""), + node_path: &path![], + node_value: None, + expected_children: &path![0x9e, 0xeb, 0xf9, 0xfa, 0xfd, 0xfe], + }, + IterPathTestCaseEdge { + leading_path: &ascii_path!(b"9e"), + node_path: &path![], + node_value: None, + expected_children: &path![0x2d, 0x36, 0x37, 0x77, 0x8c, 0x90, 0x92, 0x9a, 0x9d, 0xa2, 0xd6, 0xe1], + }, + IterPathTestCaseEdge { + leading_path: &ascii_path!(b"9e36"), + node_path: &path![], + node_value: None, + expected_children: &path![0x7d, 0xec, 0xef], + }, + IterPathTestCaseEdge { + leading_path: &ascii_path!(b"9e36ec"), + node_path: &ascii_path!(b"f376c2c57cbf812580866369ed3286dd83a4063ac145ec3c9e9de4b3a5"), + node_value: Some("9e36ecf376c2c57cbf812580866369ed3286dd83a4063ac145ec3c9e9de4b3a5"), + expected_children: &path![], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![], + node_value: None, + expected_children: &path![9, 0xe, 0xf], + }, + IterPathTestCaseEdge { + leading_path: &path![9], + node_path: &path![0xe], + node_value: None, + expected_children: &path![2, 3, 7, 8, 9, 0xa, 0xd, 0xe], + }, + IterPathTestCaseEdge { + leading_path: &path![9, 0xe, 3], + node_path: &path![], + node_value: None, + expected_children: &path![6, 7], + }, + IterPathTestCaseEdge { + leading_path: &path![9, 0xe, 3, 6], + node_path: &path![], + node_value: None, + expected_children: &path![7, 0xe], + }, + IterPathTestCaseEdge { + leading_path: &path![9, 0xe, 3, 6, 0xe], + node_path: &path![], + node_value: None, + expected_children: &path![0xc, 0xf], + }, + IterPathTestCaseEdge { + leading_path: &path![9, 0xe, 3, 6, 0xe, 0xc], + node_path: &ascii_path!(b"f376c2c57cbf812580866369ed3286dd83a4063ac145ec3c9e9de4b3a5"), + node_value: Some("9e36ecf376c2c57cbf812580866369ed3286dd83a4063ac145ec3c9e9de4b3a5"), + expected_children: &path![], + }, + ] + } + }; + "many long keys, present" + )] + #[test_case( + LONG_KEY_SET, + [0_u8; 32], + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &ascii_path!(b""), + node_path: &path![], + node_value: None, + expected_children: &path![0x9e, 0xeb, 0xf9, 0xfa, 0xfd, 0xfe], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![], + node_value: None, + expected_children: &path![9, 0xe, 0xf], + }, + ] + } + }; + "many long keys, missing zero key" + )] + #[test_case( + LONG_KEY_SET, + [0xff_u8; 32], + cfg_alt! { + feature = "branch_factor_256" => { + &[ + IterPathTestCaseEdge { + leading_path: &ascii_path!(b""), + node_path: &path![], + node_value: None, + expected_children: &path![0x9e, 0xeb, 0xf9, 0xfa, 0xfd, 0xfe], + }, + ] + } else { + &[ + IterPathTestCaseEdge { + leading_path: &path![], + node_path: &path![], + node_value: None, + expected_children: &path![9, 0xe, 0xf], + }, + IterPathTestCaseEdge { + leading_path: &path![0xf], + node_path: &path![], + node_value: None, + expected_children: &path![9, 0xa, 0xd, 0xe], + }, + ] + } + }; + "many long keys, missing ones key" + )] + fn test_iter_path( + slice: &[(impl AsRef<[u8]>, &str)], + target: impl AsRef<[u8]>, + edges: &[IterPathTestCaseEdge<'_>], + ) { + let root = KeyValueTrieRoot::::from_slice(slice).unwrap().unwrap(); + let target = PackedPathRef::path_from_packed_bytes(target.as_ref()); + + let mut iter = root.iter_path(target); + for &IterPathTestCaseEdge { + leading_path, + node_path, + node_value, + expected_children, + } in edges + { + let (actual_path, actual_edge) = iter.next().expect("expected more edges"); + assert!( + actual_path.path_eq(leading_path), + "expected leading path {} got {}", + leading_path.display(), + actual_path.display(), + ); + let node = actual_edge.node().expect("expected node"); + assert_eq!( + node.value(), + node_value, + "expected node value {:?} got {:?} at path <{}:{}>", + node_value, + node.value(), + actual_path.display(), + node.partial_path.display() + ); + assert!( + node.partial_path.path_eq(node_path), + "expected node path {} got {}", + node_path.display(), + node.partial_path.display(), + ); + for (pc, child) in &node.children { + if expected_children.contains(&pc) { + assert!( + child.is_some(), + "expected child at {pc:x} at path {}", + actual_path.display() + ); + } else { + assert!( + child.is_none(), + "expected no child at {pc:x} at path {}", + actual_path.display() + ); + } + } + } + + if let Some((actual_path, actual_edge)) = iter.next() { + panic!( + "expected no more edges, got edge at path {} with node {:?}", + actual_path.display(), + actual_edge.node() + ); + } + } } diff --git a/storage/src/tries/mod.rs b/storage/src/tries/mod.rs index f39ca0d79..51934025b 100644 --- a/storage/src/tries/mod.rs +++ b/storage/src/tries/mod.rs @@ -4,9 +4,9 @@ mod iter; mod kvp; -use crate::{HashType, PathComponent, SplitPath}; +use crate::{HashType, IntoSplitPath, PathComponent, SplitPath}; -pub use self::iter::{IterAscending, IterDescending, TrieEdgeIter, TrieValueIter}; +pub use self::iter::{IterAscending, IterDescending, TrieEdgeIter, TriePathIter, TrieValueIter}; pub use self::kvp::{DuplicateKeyError, HashedKeyValueTrieRoot, KeyValueTrieRoot}; /// The state of an edge from a parent node to a child node in a trie. @@ -38,8 +38,13 @@ pub enum TrieEdgeState<'a, N: ?Sized> { /// A node in a fixed-arity radix trie. pub trait TrieNode + ?Sized> { + /// The type of path from this node's parent to this node. + type PartialPath<'a>: IntoSplitPath + 'a + where + Self: 'a; + /// The path from this node's parent to this node. - fn partial_path(&self) -> impl SplitPath + '_; + fn partial_path(&self) -> Self::PartialPath<'_>; /// The value stored at this node, if any. fn value(&self) -> Option<&V>; @@ -87,6 +92,17 @@ pub trait TrieNode + ?Sized> { } } + /// Returns an iterator over the edges along a specified path in this trie + /// terminating at the node corresponding to the path, if it exists; + /// otherwise, terminating at the deepest existing edge along the path. + /// + /// The returned iterator yields each edge along the path as a tuple where + /// the first element is full path to the edge, inclusive of the edge's + /// leading path components, and the second element is the edge state. + fn iter_path(&self, path: P) -> TriePathIter<'_, P, Self, V> { + TriePathIter::new(self, None, path) + } + /// Returns a breadth-first iterator over the edges in this trie in ascending /// order. ///