Add `shardtree` witness operation & implement property tests.

This commit is contained in:
Kris Nuttycombe 2023-01-13 08:40:57 -07:00
parent a7bb8bb749
commit 0cb1cec21f
4 changed files with 489 additions and 53 deletions

View File

@ -541,7 +541,7 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
])
);
tree.append("b".to_string(), Retention::Ephemeral);
tree.append("b".to_string(), Ephemeral);
assert_eq!(
tree.witness(0.into(), 0),
Some(vec![
@ -552,7 +552,7 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
])
);
tree.append("c".to_string(), Retention::Marked);
tree.append("c".to_string(), Marked);
assert_eq!(
tree.witness(Position::from(2), 0),
Some(vec![
@ -563,7 +563,7 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
])
);
tree.append("d".to_string(), Retention::Ephemeral);
tree.append("d".to_string(), Ephemeral);
assert_eq!(
tree.witness(Position::from(2), 0),
Some(vec![
@ -574,7 +574,7 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
])
);
tree.append("e".to_string(), Retention::Ephemeral);
tree.append("e".to_string(), Ephemeral);
assert_eq!(
tree.witness(Position::from(2), 0),
Some(vec![
@ -586,12 +586,12 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
);
let mut tree = new_tree(100);
tree.append("a".to_string(), Retention::Marked);
tree.append("a".to_string(), Marked);
for c in 'b'..'g' {
tree.append(c.to_string(), Retention::Ephemeral);
tree.append(c.to_string(), Ephemeral);
}
tree.append("g".to_string(), Retention::Marked);
tree.append("h".to_string(), Retention::Ephemeral);
tree.append("g".to_string(), Marked);
tree.append("h".to_string(), Ephemeral);
assert_eq!(
tree.witness(0.into(), 0),
@ -604,13 +604,13 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
);
let mut tree = new_tree(100);
tree.append("a".to_string(), Retention::Marked);
tree.append("b".to_string(), Retention::Ephemeral);
tree.append("c".to_string(), Retention::Ephemeral);
tree.append("d".to_string(), Retention::Marked);
tree.append("e".to_string(), Retention::Marked);
tree.append("f".to_string(), Retention::Marked);
tree.append("g".to_string(), Retention::Ephemeral);
tree.append("a".to_string(), Marked);
tree.append("b".to_string(), Ephemeral);
tree.append("c".to_string(), Ephemeral);
tree.append("d".to_string(), Marked);
tree.append("e".to_string(), Marked);
tree.append("f".to_string(), Marked);
tree.append("g".to_string(), Ephemeral);
assert_eq!(
tree.witness(Position::from(5), 0),
@ -624,10 +624,10 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
let mut tree = new_tree(100);
for c in 'a'..'k' {
tree.append(c.to_string(), Retention::Ephemeral);
assert!(tree.append(c.to_string(), Ephemeral));
}
tree.append('k'.to_string(), Retention::Marked);
tree.append('l'.to_string(), Retention::Ephemeral);
assert!(tree.append('k'.to_string(), Marked));
assert!(tree.append('l'.to_string(), Ephemeral));
assert_eq!(
tree.witness(Position::from(10), 0),
@ -642,18 +642,18 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
let mut tree = new_tree(100);
assert!(tree.append(
'a'.to_string(),
Retention::Checkpoint {
Checkpoint {
id: 1,
is_marked: true
}
));
assert!(tree.rewind());
for c in 'b'..'e' {
tree.append(c.to_string(), Retention::Ephemeral);
tree.append(c.to_string(), Ephemeral);
}
tree.append("e".to_string(), Retention::Marked);
tree.append("e".to_string(), Marked);
for c in 'f'..'i' {
tree.append(c.to_string(), Retention::Ephemeral);
tree.append(c.to_string(), Ephemeral);
}
assert_eq!(
tree.witness(0.into(), 0),
@ -666,20 +666,20 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
);
let mut tree = new_tree(100);
tree.append('a'.to_string(), Retention::Ephemeral);
tree.append('b'.to_string(), Retention::Ephemeral);
tree.append('c'.to_string(), Retention::Marked);
tree.append('d'.to_string(), Retention::Ephemeral);
tree.append('e'.to_string(), Retention::Ephemeral);
tree.append('f'.to_string(), Retention::Ephemeral);
tree.append('a'.to_string(), Ephemeral);
tree.append('b'.to_string(), Ephemeral);
tree.append('c'.to_string(), Marked);
tree.append('d'.to_string(), Ephemeral);
tree.append('e'.to_string(), Ephemeral);
tree.append('f'.to_string(), Ephemeral);
assert!(tree.append(
'g'.to_string(),
Retention::Checkpoint {
Checkpoint {
id: 1,
is_marked: true
}
));
tree.append('h'.to_string(), Retention::Ephemeral);
tree.append('h'.to_string(), Ephemeral);
assert!(tree.rewind());
assert_eq!(
tree.witness(Position::from(2), 0),
@ -692,18 +692,18 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
);
let mut tree = new_tree(100);
tree.append('a'.to_string(), Retention::Ephemeral);
tree.append('b'.to_string(), Retention::Marked);
tree.append('a'.to_string(), Ephemeral);
tree.append('b'.to_string(), Marked);
assert_eq!(tree.witness(Position::from(0), 0), None);
let mut tree = new_tree(100);
for c in 'a'..'m' {
tree.append(c.to_string(), Retention::Ephemeral);
tree.append(c.to_string(), Ephemeral);
}
tree.append('m'.to_string(), Retention::Marked);
tree.append('n'.to_string(), Retention::Marked);
tree.append('o'.to_string(), Retention::Ephemeral);
tree.append('p'.to_string(), Retention::Ephemeral);
tree.append('m'.to_string(), Marked);
tree.append('n'.to_string(), Marked);
tree.append('o'.to_string(), Ephemeral);
tree.append('p'.to_string(), Ephemeral);
assert_eq!(
tree.witness(Position::from(12), 0),
@ -717,9 +717,9 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
let ops = ('a'..='l')
.into_iter()
.map(|c| Append(c.to_string(), Retention::Marked))
.chain(Some(Append('m'.to_string(), Retention::Ephemeral)))
.chain(Some(Append('n'.to_string(), Retention::Ephemeral)))
.map(|c| Append(c.to_string(), Marked))
.chain(Some(Append('m'.to_string(), Ephemeral)))
.chain(Some(Append('n'.to_string(), Ephemeral)))
.chain(Some(Witness(11usize.into(), 0)))
.collect::<Vec<_>>();
@ -736,6 +736,153 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
]
))
);
let ops = vec![
Append("a".to_string(), Ephemeral),
Append("b".to_string(), Ephemeral),
Append("c".to_string(), Ephemeral),
Append(
"d".to_string(),
Checkpoint {
id: 1,
is_marked: true,
},
),
Append("e".to_string(), Marked),
Operation::Checkpoint(2),
Append(
"f".to_string(),
Checkpoint {
id: 3,
is_marked: false,
},
),
Append(
"g".to_string(),
Checkpoint {
id: 4,
is_marked: false,
},
),
Append(
"h".to_string(),
Checkpoint {
id: 5,
is_marked: false,
},
),
Witness(3usize.into(), 5),
];
let mut tree = new_tree(100);
assert_eq!(
Operation::apply_all(&ops, &mut tree),
Some((
Position::from(3),
vec![
"c".to_string(),
"ab".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
let ops = vec![
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Append(
"a".to_string(),
Checkpoint {
id: 1,
is_marked: true,
},
),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Append(
"a".to_string(),
Checkpoint {
id: 2,
is_marked: false,
},
),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Witness(Position(3), 1),
];
let mut tree = new_tree(100);
assert_eq!(
Operation::apply_all(&ops, &mut tree),
Some((
Position::from(3),
vec![
"a".to_string(),
"aa".to_string(),
"aaaa".to_string(),
"________".to_string()
]
))
);
let ops = vec![
Append("a".to_string(), Marked),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Append("a".to_string(), Ephemeral),
Operation::Checkpoint(1),
Append("a".to_string(), Marked),
Operation::Checkpoint(2),
Operation::Checkpoint(3),
Append(
"a".to_string(),
Checkpoint {
id: 4,
is_marked: false,
},
),
Rewind,
Rewind,
Witness(Position(7), 2),
];
let mut tree = new_tree(100);
assert_eq!(Operation::apply_all(&ops, &mut tree), None);
let ops = vec![
Append("a".to_string(), Marked),
Append("a".to_string(), Ephemeral),
Append(
"a".to_string(),
Checkpoint {
id: 1,
is_marked: true,
},
),
Append(
"a".to_string(),
Checkpoint {
id: 4,
is_marked: false,
},
),
Witness(Position(2), 2),
];
let mut tree = new_tree(100);
assert_eq!(
Operation::apply_all(&ops, &mut tree),
Some((
Position::from(2),
vec![
"_".to_string(),
"aa".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
}
pub fn check_checkpoint_rewind<T: Tree<String, usize>, F: Fn(usize) -> T>(new_tree: F) {
@ -821,15 +968,15 @@ pub fn check_rewind_remove_mark<T: Tree<String, usize>, F: Fn(usize) -> T>(new_t
// use a maximum number of checkpoints of 1
let mut tree = new_tree(1);
tree.append("e".to_string(), Retention::Marked);
tree.checkpoint(1);
assert!(tree.append("e".to_string(), Retention::Marked));
assert!(tree.checkpoint(1));
assert!(tree.marked_positions().contains(&0usize.into()));
tree.append("f".to_string(), Retention::Ephemeral);
assert!(tree.append("f".to_string(), Retention::Ephemeral));
// simulate a spend of `e` at `f`
assert!(tree.remove_mark(0usize.into()));
// even though the mark has been staged for removal, it's not gone yet
assert!(tree.marked_positions().contains(&0usize.into()));
tree.checkpoint(2);
assert!(tree.checkpoint(2));
// the newest checkpoint will have caused the oldest to roll off, and
// so the forgotten node will be unmarked
assert!(!tree.marked_positions().contains(&0usize.into()));

View File

@ -380,7 +380,7 @@ mod tests {
}
#[test]
fn witness() {
fn witnesses() {
check_witnesses(|max_checkpoints| {
CompleteTree::<String, usize, 4>::new(max_checkpoints, 0)
});

View File

@ -0,0 +1,18 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 38b4ca3c029291dfe2a6b5907c33a2e8ae7900f19c759c5db74b616ab19f6c5c # shrinks to ops = [Append(SipHashable(0), MC), Rewind, Rewind]
cc f1a96f73b9f3ba2a2e4b5271037322150450c573b6c3a5ef34f71a540ff0fad2 # shrinks to ops = [Append("a", C), Rewind, Rewind]
cc ad5f5d4276adea6e928376c6dc8c013745e60a8b507e6fa4e716f6c35477fe65 # shrinks to ops = [Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E)]
cc ab2690ff3cf593d2e7a78b2f56d76699e525391b87852bbf15315a1f36742f48 # shrinks to ops = [Append(SipHashable(0), C), Append(SipHashable(0), E), Append(SipHashable(0), E), Unmark(Position(0))]
cc 1603359084b0c614a2ff9008036a5e10db9f32fb31b3333e59aaa517686e174d # shrinks to ops = [Append(SipHashable(0), Checkpoint { id: (), is_marked: false })]
cc faaf929be4b27e652712b705e297bfe15c53767102516e56882d177ac6fc58d9 # shrinks to ops = [CurrentPosition, Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Checkpoint { id: (), is_marked: true }), Append("a", Marked), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Witness(Position(3), 5)]
cc 8836c27ead7afb8d10092bdaeed33eb31007eaa47e3c3fca248cc00fbce772a3 # shrinks to ops = [Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral)]
cc 2303f82f255c60d7bdd45e2d13ff526bdc1d7f5ce846a7c07b38ab7f255c0300 # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral)]
cc cf1a33ef6df58bbf7cc199b8b1879c3a078e7784aa3edc0aba9ca03772bea5f2 # shrinks to ops = [Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Checkpoint(()), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Checkpoint(()), Rewind, Rewind, Rewind, Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Checkpoint { id: (), is_marked: true }), Witness(Position(8), 2)]
cc 544e027d994eaf7f97b1c8d9ee7b35522a64a610b1430d56d74ec947018b759d # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Checkpoint(()), Append("a", Marked), Checkpoint(()), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Rewind, Rewind, Witness(Position(7), 2)]
cc 55d00b68a0f0a02f83ab53f18a29d16d0233153b69a01414a1622104e0eead31 # shrinks to ops = [Append("a", Marked), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Marked), Checkpoint(()), Checkpoint(()), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Witness(Position(0), 7)]
cc 9dd966ff1ab66965c5b84153ae13f684258560cdd5e84c7deb24f724cb12aba7 # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Checkpoint { id: (), is_marked: true }), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Rewind, Rewind, Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Checkpoint(()), Witness(Position(2), 4)]

View File

@ -1951,6 +1951,61 @@ impl<
Ok(())
}
/// Truncates the tree, discarding all information after the checkpoint at the specified depth.
///
/// This will also discard all checkpoints with depth <= the specified depth. Returns `true`
/// if the truncation succeeds or has no effect, or `false` if no checkpoint exists at the
/// specified depth.
pub fn truncate_removing_checkpoint(&mut self, checkpoint_depth: usize) -> bool {
if checkpoint_depth == 0 {
true
} else if self.checkpoints.len() > 1 {
match self.checkpoint_at_depth(checkpoint_depth) {
Some((checkpoint_id, c)) => {
let checkpoint_id = checkpoint_id.clone();
match c.tree_state {
TreeState::Empty => {
if (self
.store
.truncate(Address::from_parts(Self::subtree_level(), 0)))
.is_err()
{
return false;
}
self.checkpoints.split_off(&checkpoint_id);
true
}
TreeState::AtPosition(position) => {
let subtree_addr =
Address::above_position(Self::subtree_level(), position);
let replacement = self
.store
.get_shard(subtree_addr)
.and_then(|s| s.truncate_to_position(position));
match replacement {
Some(truncated) => {
if self.store.truncate(subtree_addr).is_err()
|| self.store.put_shard(truncated).is_err()
{
false
} else {
self.checkpoints.split_off(&checkpoint_id);
true
}
}
None => false,
}
}
}
}
None => false,
}
} else {
// do not remove the first checkpoint.
false
}
}
/// Computes the root of any subtree of this tree rooted at the given address, with the overall
/// tree truncated to the specified position.
///
@ -2109,6 +2164,68 @@ impl<
|pos| self.root(Self::root_addr(), pos + 1),
)
}
/// Computes the witness for the leaf at the specified position.
///
/// Returns the witness as of the most recently appended leaf if `checkpoint_depth == 0`. Note
/// that if the most recently appended leaf is also a checkpoint, this will return the same
/// result as `checkpoint_depth == 1`.
pub fn witness(
&self,
position: Position,
checkpoint_depth: usize,
) -> Result<Vec<H>, QueryError> {
let max_leaf_position = self
.max_leaf_position(checkpoint_depth)
.and_then(|v| v.ok_or_else(|| QueryError::TreeIncomplete(vec![Self::root_addr()])))?;
if position > max_leaf_position {
Err(QueryError::NotContained(Address::from_parts(
Level::from(0),
position.into(),
)))
} else {
let subtree_addr = Address::above_position(Self::subtree_level(), position);
// compute the witness for the specified position up to the subtree root
let mut witness = self.store.get_shard(subtree_addr).map_or_else(
|| Err(QueryError::TreeIncomplete(vec![subtree_addr])),
|subtree| subtree.witness(position, max_leaf_position + 1),
)?;
// compute the remaining parts of the witness up to the root
let root_addr = Self::root_addr();
let mut cur_addr = subtree_addr;
while cur_addr != root_addr {
witness.push(self.root(cur_addr.sibling(), max_leaf_position + 1)?);
cur_addr = cur_addr.parent();
}
Ok(witness)
}
}
/// Make a marked leaf at a position eligible to be pruned.
///
/// If the checkpoint associated with the specified identifier does not exist because the
/// corresponding checkpoint would have been more than `max_checkpoints` deep, the removal
/// is recorded as of the first existing checkpoint and the associated leaves will be pruned
/// when that checkpoint is subsequently removed.
pub fn remove_mark(&mut self, position: Position, as_of_checkpoint: &C) -> bool {
if self.get_marked_leaf(position).is_some() {
if let Some(checkpoint) = self.checkpoints.get_mut(as_of_checkpoint) {
checkpoint.marks_removed.insert(position);
return true;
}
if let Some((_, checkpoint)) = self.checkpoints.iter_mut().next() {
checkpoint.marks_removed.insert(position);
return true;
}
}
false
}
}
// We need an applicative functor for Result for this function so that we can correctly
@ -2174,16 +2291,20 @@ pub mod testing {
#[cfg(test)]
mod tests {
use crate::{
LocatedPrunableTree, LocatedTree, Node, PrunableTree, QueryError, ShardStore, ShardTree,
Tree, EPHEMERAL, MARKED,
IncompleteAt, LocatedPrunableTree, LocatedTree, Node, PrunableTree, QueryError, ShardStore,
ShardTree, Tree, EPHEMERAL, MARKED,
};
use assert_matches::assert_matches;
use core::convert::Infallible;
use incrementalmerkletree::{
testing::{
self, check_append, check_root_hashes, complete_tree::CompleteTree, CombinedTree,
self, arb_operation, check_append, check_checkpoint_rewind, check_operations,
check_rewind_remove_mark, check_root_hashes, check_witnesses,
complete_tree::CompleteTree, CombinedTree, SipHashable,
},
Address, Hashable, Level, Position, Retention,
};
use proptest::prelude::*;
use std::collections::BTreeSet;
use std::rc::Rc;
@ -2495,6 +2616,97 @@ mod tests {
assert_eq!(complete.subtree.right_filled_root(), Ok("abcd".to_string()));
}
#[test]
fn shardtree_insertion() {
let mut tree: ShardTree<String, usize, VecShardStore<String>, 4, 3> =
ShardTree::new(vec![], 100, 0);
assert_matches!(
tree.batch_insert(
Position::from(1),
vec![
("b".to_string(), Retention::Checkpoint { id: 1, is_marked: false }),
("c".to_string(), Retention::Ephemeral),
("d".to_string(), Retention::Marked),
].into_iter()
),
Ok(Some((pos, incomplete))) if
pos == Position::from(3) &&
incomplete == vec![
IncompleteAt {
address: Address::from_parts(Level::from(0), 0),
required_for_witness: true
}
]
);
assert_matches!(
tree.root_at_checkpoint(1),
Err(QueryError::TreeIncomplete(v)) if v == vec![Address::from_parts(Level::from(0), 0)]
);
assert_matches!(
tree.batch_insert(
Position::from(0),
vec![
("a".to_string(), Retention::Ephemeral),
].into_iter()
),
Ok(Some((pos, incomplete))) if
pos == Position::from(0) &&
incomplete == vec![]
);
assert_matches!(
tree.root_at_checkpoint(0),
Ok(h) if h == *"abcd____________"
);
assert_matches!(
tree.root_at_checkpoint(1),
Ok(h) if h == *"ab______________"
);
assert_matches!(
tree.batch_insert(
Position::from(10),
vec![
("k".to_string(), Retention::Ephemeral),
("l".to_string(), Retention::Checkpoint { id: 2, is_marked: false }),
("m".to_string(), Retention::Ephemeral),
].into_iter()
),
Ok(Some((pos, incomplete))) if
pos == Position::from(12) &&
incomplete == vec![
IncompleteAt {
address: Address::from_parts(Level::from(1), 4),
required_for_witness: false
},
IncompleteAt {
address: Address::from_parts(Level::from(0), 13),
required_for_witness: false
},
IncompleteAt {
address: Address::from_parts(Level::from(1), 7),
required_for_witness: false
},
]
);
assert_matches!(
tree.root_at_checkpoint(0),
// The (0, 13) and (1, 7) incomplete subtrees are
// not considered incomplete here because they appear
// at the tip of the tree.
Err(QueryError::TreeIncomplete(xs)) if xs == vec![
Address::from_parts(Level::from(2), 1),
Address::from_parts(Level::from(1), 4),
]
);
assert!(tree.truncate_removing_checkpoint(1));
}
impl<
H: Hashable + Ord + Clone,
C: Clone + Ord + core::fmt::Debug,
@ -2527,12 +2739,16 @@ mod tests {
ShardTree::root_at_checkpoint(self, checkpoint_depth).ok()
}
fn witness(&self, _position: Position, _checkpoint_depth: usize) -> Option<Vec<H>> {
todo!()
fn witness(&self, position: Position, checkpoint_depth: usize) -> Option<Vec<H>> {
ShardTree::witness(self, position, checkpoint_depth).ok()
}
fn remove_mark(&mut self, _position: Position) -> bool {
todo!()
fn remove_mark(&mut self, position: Position) -> bool {
if let Some(c) = self.checkpoints.iter().rev().map(|(c, _)| c.clone()).next() {
ShardTree::remove_mark(self, position, &c)
} else {
false
}
}
fn checkpoint(&mut self, checkpoint_id: C) -> bool {
@ -2540,7 +2756,7 @@ mod tests {
}
fn rewind(&mut self) -> bool {
todo!()
ShardTree::truncate_removing_checkpoint(self, 1)
}
}
@ -2557,6 +2773,28 @@ mod tests {
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
});
}
#[test]
fn witnesses() {
check_witnesses(|m| {
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
});
}
#[test]
fn checkpoint_rewind() {
check_checkpoint_rewind(|m| {
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
});
}
#[test]
fn rewind_remove_mark() {
check_rewind_remove_mark(|m| {
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
});
}
// Combined tree tests
#[allow(clippy::type_complexity)]
fn new_combined_tree<H: Hashable + Ord + Clone + core::fmt::Debug>(
@ -2577,4 +2815,37 @@ mod tests {
fn combined_append() {
check_append(new_combined_tree);
}
#[test]
fn combined_rewind_remove_mark() {
check_rewind_remove_mark(new_combined_tree);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100000))]
#[test]
fn check_randomized_u64_ops(
ops in proptest::collection::vec(
arb_operation((0..32u64).prop_map(SipHashable), 0usize..100),
1..100
)
) {
let tree = new_combined_tree(100);
let indexed_ops = ops.iter().enumerate().map(|(i, op)| op.map_checkpoint_id(|_| i)).collect::<Vec<_>>();
check_operations(tree, &indexed_ops)?;
}
#[test]
fn check_randomized_str_ops(
ops in proptest::collection::vec(
arb_operation((97u8..123).prop_map(|c| char::from(c).to_string()), 0usize..100),
1..100
)
) {
let tree = new_combined_tree(100);
let indexed_ops = ops.iter().enumerate().map(|(i, op)| op.map_checkpoint_id(|_| i)).collect::<Vec<_>>();
check_operations(tree, &indexed_ops)?;
}
}
}