Update `shardtree` to depend upon `incrementalmerkletree 0.8.0`

This commit is contained in:
Kris Nuttycombe 2024-12-11 21:29:45 -07:00
parent fb62fb6175
commit 353cf3e9ff
7 changed files with 62 additions and 40 deletions

16
Cargo.lock generated
View File

@ -101,18 +101,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "incrementalmerkletree"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45063fbc4b0a37837f6bfe0445f269d13d730ad0aa3b5a7f74aa7bf27a0f4df"
dependencies = [
"either",
"proptest",
"rand",
"rand_core",
]
[[package]] [[package]]
name = "incrementalmerkletree" name = "incrementalmerkletree"
version = "0.8.0" version = "0.8.0"
@ -128,7 +116,7 @@ dependencies = [
name = "incrementalmerkletree-testing" name = "incrementalmerkletree-testing"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"incrementalmerkletree 0.7.0", "incrementalmerkletree",
"proptest", "proptest",
] ]
@ -335,7 +323,7 @@ dependencies = [
"assert_matches", "assert_matches",
"bitflags 2.4.1", "bitflags 2.4.1",
"either", "either",
"incrementalmerkletree 0.7.0", "incrementalmerkletree",
"incrementalmerkletree-testing", "incrementalmerkletree-testing",
"proptest", "proptest",
"tempfile", "tempfile",

View File

@ -18,7 +18,7 @@ categories = ["algorithms", "data-structures"]
[workspace.dependencies] [workspace.dependencies]
# Intra-workspace dependencies # Intra-workspace dependencies
incrementalmerkletree = { version = "0.7", path = "incrementalmerkletree" } incrementalmerkletree = { version = "0.8", path = "incrementalmerkletree" }
incrementalmerkletree-testing = { version = "0.2", path = "incrementalmerkletree-testing" } incrementalmerkletree-testing = { version = "0.2", path = "incrementalmerkletree-testing" }
# Testing # Testing

View File

@ -18,6 +18,6 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[dependencies] [dependencies]
incrementalmerkletree = { version = "0.7", features = ["test-dependencies"] } incrementalmerkletree = { workspace = true, features = ["test-dependencies"] }
proptest.workspace = true proptest.workspace = true

View File

@ -20,14 +20,14 @@ rustdoc-args = ["--cfg", "docsrs"]
assert_matches = { version = "1.5", optional = true } assert_matches = { version = "1.5", optional = true }
bitflags = "2" bitflags = "2"
either = "1.8" either = "1.8"
incrementalmerkletree = "0.7" incrementalmerkletree.workspace = true
proptest = { workspace = true, optional = true } proptest = { workspace = true, optional = true }
incrementalmerkletree-testing = { workspace = true, optional = true } incrementalmerkletree-testing = { workspace = true, optional = true }
tracing = "0.1" tracing = "0.1"
[dev-dependencies] [dev-dependencies]
assert_matches = "1.5" assert_matches = "1.5"
incrementalmerkletree = { version = "0.7", features = ["test-dependencies"] } incrementalmerkletree = { workspace = true, features = ["test-dependencies"] }
incrementalmerkletree-testing.workspace = true incrementalmerkletree-testing.workspace = true
proptest.workspace = true proptest.workspace = true

View File

@ -187,7 +187,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
.tree() .tree()
.to_frontier() .to_frontier()
.take() .take()
.expect("IncrementalWitness must not be created from the empty tree."), .expect("IncrementalWitness cannot be constructed for the empty tree."),
&Retention::Marked, &Retention::Marked,
)?; )?;
@ -261,7 +261,7 @@ mod tests {
for c in 'a'..'h' { for c in 'a'..'h' {
base_tree.append(c.to_string()).unwrap(); base_tree.append(c.to_string()).unwrap();
} }
let mut witness = IncrementalWitness::from_tree(base_tree); let mut witness = IncrementalWitness::from_tree(base_tree).unwrap();
for c in 'h'..'z' { for c in 'h'..'z' {
witness.append(c.to_string()).unwrap(); witness.append(c.to_string()).unwrap();
} }
@ -309,7 +309,7 @@ mod tests {
for c in 'a'..='c' { for c in 'a'..='c' {
base_tree.append(c.to_string()).unwrap(); base_tree.append(c.to_string()).unwrap();
} }
let mut witness = IncrementalWitness::from_tree(base_tree); let mut witness = IncrementalWitness::from_tree(base_tree).unwrap();
witness.append("d".to_string()).unwrap(); witness.append("d".to_string()).unwrap();
let root_addr = Address::from_parts(Level::from(3), 0); let root_addr = Address::from_parts(Level::from(3), 0);

View File

@ -1,6 +1,7 @@
//! Helpers for working with trees that support pruning unneeded leaves and branches. //! Helpers for working with trees that support pruning unneeded leaves and branches.
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use bitflags::bitflags; use bitflags::bitflags;
@ -78,6 +79,37 @@ impl<C> From<Retention<C>> for RetentionFlags {
/// A [`Tree`] annotated with Merkle hashes. /// A [`Tree`] annotated with Merkle hashes.
pub type PrunableTree<H> = Tree<Option<Arc<H>>, (H, RetentionFlags)>; pub type PrunableTree<H> = Tree<Option<Arc<H>>, (H, RetentionFlags)>;
/// Errors that can occur when merging trees.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MergeError {
Conflict(Address),
TreeMalformed(Address),
}
impl From<MergeError> for InsertionError {
fn from(value: MergeError) -> Self {
match value {
MergeError::Conflict(addr) => InsertionError::Conflict(addr),
MergeError::TreeMalformed(addr) => InsertionError::InputMalformed(addr),
}
}
}
impl fmt::Display for MergeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
MergeError::Conflict(addr) => write!(
f,
"Inserted root conflicts with existing root at address {:?}",
addr
),
MergeError::TreeMalformed(addr) => {
write!(f, "Merge input malformed at address {:?}", addr)
}
}
}
}
impl<H: Hashable + Clone + PartialEq> PrunableTree<H> { impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
/// Returns the the value if this is a leaf. /// Returns the the value if this is a leaf.
pub fn leaf_value(&self) -> Option<&H> { pub fn leaf_value(&self) -> Option<&H> {
@ -242,14 +274,14 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
/// would cause information loss or if a conflict between root hashes occurs at a node. The /// would cause information loss or if a conflict between root hashes occurs at a node. The
/// returned error contains the address of the node where such a conflict occurred. /// returned error contains the address of the node where such a conflict occurred.
#[tracing::instrument()] #[tracing::instrument()]
pub fn merge_checked(self, root_addr: Address, other: Self) -> Result<Self, Address> { pub fn merge_checked(self, root_addr: Address, other: Self) -> Result<Self, MergeError> {
/// Pre-condition: `root_addr` must be the address of `t0` and `t1`. /// Pre-condition: `root_addr` must be the address of `t0` and `t1`.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn go<H: Hashable + Clone + PartialEq>( fn go<H: Hashable + Clone + PartialEq>(
addr: Address, addr: Address,
t0: PrunableTree<H>, t0: PrunableTree<H>,
t1: PrunableTree<H>, t1: PrunableTree<H>,
) -> Result<PrunableTree<H>, Address> { ) -> Result<PrunableTree<H>, MergeError> {
// Require that any roots the we compute will not be default-filled by picking // Require that any roots the we compute will not be default-filled by picking
// a starting valid fill point that is outside the range of leaf positions. // a starting valid fill point that is outside the range of leaf positions.
let no_default_fill = addr.position_range_end(); let no_default_fill = addr.position_range_end();
@ -261,7 +293,7 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
Ok(Tree::leaf((vl.0, vl.1 | vr.1))) Ok(Tree::leaf((vl.0, vl.1 | vr.1)))
} else { } else {
trace!(left = ?vl.0, right = ?vr.0, "Merge conflict for leaves"); trace!(left = ?vl.0, right = ?vr.0, "Merge conflict for leaves");
Err(addr) Err(MergeError::Conflict(addr))
} }
} }
(Tree(Node::Leaf { value }), parent @ Tree(Node::Parent { .. })) (Tree(Node::Leaf { value }), parent @ Tree(Node::Parent { .. }))
@ -271,7 +303,7 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
Ok(parent.reannotate_root(Some(Arc::new(value.0)))) Ok(parent.reannotate_root(Some(Arc::new(value.0))))
} else { } else {
trace!(leaf = ?value, node = ?parent_hash, "Merge conflict for leaf into node"); trace!(leaf = ?value, node = ?parent_hash, "Merge conflict for leaf into node");
Err(addr) Err(MergeError::Conflict(addr))
} }
} }
(lparent, rparent) => { (lparent, rparent) => {
@ -296,9 +328,8 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
}), }),
) = (lparent, rparent) ) = (lparent, rparent)
{ {
let (l_addr, r_addr) = addr let (l_addr, r_addr) =
.children() addr.children().ok_or(MergeError::TreeMalformed(addr))?;
.expect("The root address of a parent node must have children.");
Ok(Tree::unite( Ok(Tree::unite(
addr.level() - 1, addr.level() - 1,
lann.or(rann), lann.or(rann),
@ -310,7 +341,7 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
} }
} else { } else {
trace!(left = ?lroot, right = ?rroot, "Merge conflict for nodes"); trace!(left = ?lroot, right = ?rroot, "Merge conflict for nodes");
Err(addr) Err(MergeError::Conflict(addr))
} }
} }
} }
@ -708,7 +739,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
parent parent
.clone() .clone()
.merge_checked(root_addr, subtree.root) .merge_checked(root_addr, subtree.root)
.map_err(InsertionError::Conflict) .map_err(InsertionError::from)
.map(|tree| (tree, vec![])) .map(|tree| (tree, vec![]))
} }
Tree(Node::Parent { ann, left, right }) => { Tree(Node::Parent { ann, left, right }) => {
@ -1026,6 +1057,7 @@ mod tests {
use super::{LocatedPrunableTree, PrunableTree, RetentionFlags}; use super::{LocatedPrunableTree, PrunableTree, RetentionFlags};
use crate::{ use crate::{
error::{InsertionError, QueryError}, error::{InsertionError, QueryError},
prunable::MergeError,
testing::{arb_char_str, arb_prunable_tree}, testing::{arb_char_str, arb_prunable_tree},
tree::{ tree::{
tests::{leaf, nil, parent}, tests::{leaf, nil, parent},
@ -1122,7 +1154,7 @@ mod tests {
assert_eq!( assert_eq!(
t0.clone() t0.clone()
.merge_checked(Address::from_parts(1.into(), 0), t2.clone()), .merge_checked(Address::from_parts(1.into(), 0), t2.clone()),
Err(Address::from_parts(0.into(), 0)) Err(MergeError::Conflict(Address::from_parts(0.into(), 0)))
); );
let t3: PrunableTree<String> = parent(t0, t2); let t3: PrunableTree<String> = parent(t0, t2);

View File

@ -199,33 +199,35 @@ pub struct LocatedTree<A, V> {
impl<A, V> LocatedTree<A, V> { impl<A, V> LocatedTree<A, V> {
/// Constructs a new LocatedTree from its constituent parts. /// Constructs a new LocatedTree from its constituent parts.
/// ///
/// Returns `None` if `root_addr` is inconsistent with `root` (in particular, if the /// Returns the newly constructed error, or the address at which the provided tree extends
/// level of `root_addr` is too small to contain `tree`). /// beyond the position range of the provided root address.
pub fn from_parts(root_addr: Address, root: Tree<A, V>) -> Option<Self> { pub fn from_parts(root_addr: Address, root: Tree<A, V>) -> Result<Self, Address> {
// In order to meet various pre-conditions throughout the crate, we require that // In order to meet various pre-conditions throughout the crate, we require that
// no `Node::Parent` in `root` has a level of 0 relative to `root_addr`. // no `Node::Parent` in `root` has a level of 0 relative to `root_addr`.
fn is_consistent<A, V>(addr: Address, root: &Tree<A, V>) -> bool { fn check<A, V>(addr: Address, root: &Tree<A, V>) -> Result<(), Address> {
match (&root.0, addr.children()) { match (&root.0, addr.children()) {
// Found an inconsistency! // Found an inconsistency!
(Node::Parent { .. }, None) => false, (Node::Parent { .. }, None) => Err(addr),
// Check consistency of children recursively. // Check consistency of children recursively.
(Node::Parent { left, right, .. }, Some((l_addr, r_addr))) => { (Node::Parent { left, right, .. }, Some((l_addr, r_addr))) => {
is_consistent(l_addr, left) && is_consistent(r_addr, right) check(l_addr, left)?;
check(r_addr, right)?;
Ok(())
} }
// Leaves are technically allowed to occur at any level, so we do not // Leaves are technically allowed to occur at any level, so we do not
// require `addr` to have no children. // require `addr` to have no children.
(Node::Leaf { .. }, _) => true, (Node::Leaf { .. }, _) => Ok(()),
// Nil nodes have no information, so we cannot verify that the data it // Nil nodes have no information, so we cannot verify that the data it
// represents is consistent with `root_addr`. Instead we rely on methods // represents is consistent with `root_addr`. Instead we rely on methods
// that mutate `LocatedTree` to verify that the insertion address is not // that mutate `LocatedTree` to verify that the insertion address is not
// inconsistent with `root_addr`. // inconsistent with `root_addr`.
(Node::Nil, _) => true, (Node::Nil, _) => Ok(()),
} }
} }
is_consistent(root_addr, &root).then_some(LocatedTree { root_addr, root }) check(root_addr, &root).map(|_| LocatedTree { root_addr, root })
} }
/// Returns the root address of this tree. /// Returns the root address of this tree.