Skip to content

Commit c0d037d

Browse files
committed
feat: join two key proofs into one trie
1 parent a3246b0 commit c0d037d

File tree

5 files changed

+333
-9
lines changed

5 files changed

+333
-9
lines changed

firewood/src/proofs/tests.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
#![expect(clippy::unwrap_used, clippy::indexing_slicing)]
55

66
use firewood_storage::{
7-
KeyProofTrieRoot, PackedPathRef, PathComponent, TrieNode, TriePath, TriePathFromPackedBytes,
8-
ValueDigest,
7+
KeyProofTrieRoot, KeyRangeProofTrieRoot, PackedPathRef, PathComponent, TrieNode, TriePath,
8+
TriePathFromPackedBytes, ValueDigest,
99
};
1010
use integer_encoding::VarInt;
1111
use test_case::test_case;
@@ -325,3 +325,112 @@ fn test_proof_trie_construction() {
325325
assert!(root.partial_path().is_empty());
326326
assert_eq!(root.value(), Some(&ValueDigest::Value(&[6_u8][..])));
327327
}
328+
329+
#[test]
330+
fn test_range_proof_trie_merging() {
331+
let merkle = crate::merkle::tests::init_merkle((0u8..=10).map(|k| ([k], [k])));
332+
let proof = merkle
333+
.range_proof(Some(&[2u8]), Some(&[8u8]), std::num::NonZeroUsize::new(5))
334+
.unwrap();
335+
336+
let lower_trie = KeyProofTrieRoot::new(&**proof.start_proof())
337+
.unwrap()
338+
.unwrap();
339+
let upper_trie = KeyProofTrieRoot::new(&**proof.end_proof())
340+
.unwrap()
341+
.unwrap();
342+
343+
let trie = KeyRangeProofTrieRoot::new(Some(lower_trie), Some(upper_trie))
344+
.unwrap()
345+
.unwrap();
346+
347+
let edges = trie.root().iter_edges().collect::<Vec<_>>();
348+
349+
let mut edges = edges.into_iter();
350+
let (path, edge) = edges.next().unwrap();
351+
assert!(edge.is_unhashed());
352+
353+
#[cfg(not(feature = "branch_factor_256"))]
354+
assert!(path.path_eq(&PathComponent::ALL[0]));
355+
356+
let included = [PathComponent::ALL[2], PathComponent::ALL[6]];
357+
let node = edge.node().unwrap();
358+
assert!(node.value().is_none());
359+
for &pc in &PathComponent::ALL[0..=10] {
360+
assert!(node.child_hash(pc).is_some());
361+
if included.contains(&pc) {
362+
assert!(node.child_node(pc).is_some());
363+
} else {
364+
assert!(node.child_node(pc).is_none());
365+
}
366+
}
367+
368+
let (path, edge) = edges.next().unwrap();
369+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x00_u8])));
370+
assert!(edge.is_remote());
371+
assert!(edge.hash().is_some());
372+
373+
let (path, edge) = edges.next().unwrap();
374+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x01_u8])));
375+
assert!(edge.is_remote());
376+
assert!(edge.hash().is_some());
377+
378+
let (path, edge) = edges.next().unwrap();
379+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x02_u8])));
380+
assert!(edge.is_local());
381+
assert!(edge.hash().is_some());
382+
let node = edge.node().unwrap();
383+
assert_eq!(node.value(), Some(&ValueDigest::Value(&[2_u8][..])));
384+
for pc in PathComponent::ALL {
385+
assert!(node.child_hash(pc).is_none());
386+
assert!(node.child_node(pc).is_none());
387+
}
388+
389+
let (path, edge) = edges.next().unwrap();
390+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x03_u8])));
391+
assert!(edge.is_remote());
392+
assert!(edge.hash().is_some());
393+
394+
let (path, edge) = edges.next().unwrap();
395+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x04_u8])));
396+
assert!(edge.is_remote());
397+
assert!(edge.hash().is_some());
398+
399+
let (path, edge) = edges.next().unwrap();
400+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x05_u8])));
401+
assert!(edge.is_remote());
402+
assert!(edge.hash().is_some());
403+
404+
let (path, edge) = edges.next().unwrap();
405+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x06_u8])));
406+
assert!(edge.is_local());
407+
assert!(edge.hash().is_some());
408+
let node = edge.node().unwrap();
409+
assert_eq!(node.value(), Some(&ValueDigest::Value(&[6_u8][..])));
410+
for pc in PathComponent::ALL {
411+
assert!(node.child_hash(pc).is_none());
412+
assert!(node.child_node(pc).is_none());
413+
}
414+
415+
let (path, edge) = edges.next().unwrap();
416+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x07_u8])));
417+
assert!(edge.is_remote());
418+
assert!(edge.hash().is_some());
419+
420+
let (path, edge) = edges.next().unwrap();
421+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x08_u8])));
422+
assert!(edge.is_remote());
423+
assert!(edge.hash().is_some());
424+
425+
let (path, edge) = edges.next().unwrap();
426+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x09_u8])));
427+
assert!(edge.is_remote());
428+
assert!(edge.hash().is_some());
429+
430+
let (path, edge) = edges.next().unwrap();
431+
assert!(path.path_eq(&PackedPathRef::path_from_packed_bytes(&[0x0a_u8])));
432+
assert!(edge.is_remote());
433+
assert!(edge.hash().is_some());
434+
435+
assert!(edges.next().is_none());
436+
}

storage/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ pub use path::{
6666
pub use path::{PackedBytes, PackedPathComponents};
6767
pub use tries::{
6868
DuplicateKeyError, FromKeyProofError, HashedKeyValueTrieRoot, HashedTrieNode, IterAscending,
69-
IterDescending, KeyProofTrieRoot, KeyValueTrieRoot, TrieEdgeIter, TrieEdgeState, TrieNode,
70-
TriePathIter, TrieValueIter,
69+
IterDescending, KeyProofTrieRoot, KeyRangeProofTrieRoot, KeyValueTrieRoot, MergeKeyProofError,
70+
TrieEdgeIter, TrieEdgeState, TrieNode, TriePathIter, TrieValueIter,
7171
};
7272
pub use u4::{TryFromIntError, U4};
7373

storage/src/path/buf.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,20 @@ impl<'a> PathGuard<'a> {
189189
pub fn push(&mut self, component: PathComponent) {
190190
self.buf.push(component);
191191
}
192+
193+
/// A convenience method for cloning the inner path buffer.
194+
///
195+
/// This is a convenience method that avoids needing to deref the guard in
196+
/// order to correctly clone the inner buffer.
197+
///
198+
/// ```ignore
199+
/// (*guard).clone() // works, but is cumbersome
200+
/// guard.cloned() // same thing, but doesn't require the parens and deref
201+
/// ```
202+
#[must_use]
203+
pub fn cloned(&self) -> PathBuf {
204+
self.buf.clone()
205+
}
192206
}
193207

194208
impl std::ops::Deref for PathGuard<'_> {

storage/src/tries/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use crate::{HashType, IntoSplitPath, PathComponent, SplitPath};
99

1010
pub use self::iter::{IterAscending, IterDescending, TrieEdgeIter, TriePathIter, TrieValueIter};
1111
pub use self::kvp::{DuplicateKeyError, HashedKeyValueTrieRoot, KeyValueTrieRoot};
12-
pub use self::proof::{FromKeyProofError, KeyProofTrieRoot};
12+
pub use self::proof::{
13+
FromKeyProofError, KeyProofTrieRoot, KeyRangeProofTrieRoot, MergeKeyProofError,
14+
};
1315

1416
/// The state of an edge from a parent node to a child node in a trie.
1517
#[derive(Debug, PartialEq, Eq, Hash)]

0 commit comments

Comments
 (0)