incrementalmerkletree-testing: Always rewind to a checkpoint.

The previous semantics of the `rewind` operation would remove the last
checkpoint, if any, but would not further modify the tree. However,
these semantics are error prone - if you rewind to a checkpoint, you are
not able to rewind to the same checkpoint again; also, in practice, it
doesn't make sense to shift the location of a checkpoint in the note
commitment tree. This change alters `rewind` to (a) take an explicit
checkpoint depth, with depth `0` meaning that any state added since the
last checkpoint should be discarded, and (b) only allow a rewind
operation to succeed if a checkpoint actually exists at the specified
depth.
This commit is contained in:
Kris Nuttycombe 2024-09-25 13:39:46 -06:00
parent 5bbd832930
commit 05f23d9763
3 changed files with 232 additions and 127 deletions

View File

@ -7,5 +7,32 @@ and this project adheres to Rust's notion of
## Unreleased ## Unreleased
This release includes a significant refactoring and rework of several methods
of the `incrementalmerkletree_testing::Tree` trait. Please read the notes for
this release carefully as the semantics of important methods have changed.
These changes may require changes to example tests that rely on this crate; in
particular, additional checkpoints may be required in circumstances where
rewind operations are being applied.
### Changed
- `incrementalmerkletree_testing::Tree`
- Added method `Tree::checkpoint_count`.
- `Tree::root` now takes its `checkpoint_depth` argument as `Option<usize>`
instead of `usize`. Passing `None` to this method will now compute the root
given all of the leaves in the tree; if a `Some` value is passed,
implementations of this method must treat the wrapped `usize` as a reverse
index into the checkpoints added to the tree, or return `None` if no
checkpoint exists at the specified index. This effectively modifies this
method to use zero-based indexing instead of a muddled 1-based indexing
scheme.
- `Tree::rewind` now takes an additional `checkpoint_depth` argument, which
is non-optional. Rewinding the tree may now only be performed if there is
a checkpoint at the specified depth to rewind to. This depth should be
treated as a zero-based reverse index into the checkpoints of the tree.
Rewinding no longer removes the checkpoint being rewound to; instead, it
removes the effect all state changes to the tree resulting from
operations performed since the checkpoint was created, but leaves the
checkpoint itself in place.
## [0.1.0] - 2024-09-25 ## [0.1.0] - 2024-09-25
Initial release. Initial release.

View File

@ -156,11 +156,14 @@ impl<H: Hashable, C: Clone + Ord + core::fmt::Debug, const DEPTH: u8> CompleteTr
} }
} }
// Creates a new checkpoint with the specified identifier and the given tree position; if `pos`
// is not provided, the position of the most recently appended leaf is used, or a new
// checkpoint of the empty tree is added if appropriate.
fn checkpoint(&mut self, id: C, pos: Option<Position>) { fn checkpoint(&mut self, id: C, pos: Option<Position>) {
self.checkpoints.insert( self.checkpoints.insert(
id, id,
Checkpoint::at_length(pos.map_or_else( Checkpoint::at_length(pos.map_or_else(
|| 0, || self.leaves.len(),
|p| usize::try_from(p).expect(MAX_COMPLETE_SIZE_ERROR) + 1, |p| usize::try_from(p).expect(MAX_COMPLETE_SIZE_ERROR) + 1,
)), )),
); );
@ -170,16 +173,12 @@ impl<H: Hashable, C: Clone + Ord + core::fmt::Debug, const DEPTH: u8> CompleteTr
} }
fn leaves_at_checkpoint_depth(&self, checkpoint_depth: usize) -> Option<usize> { fn leaves_at_checkpoint_depth(&self, checkpoint_depth: usize) -> Option<usize> {
if checkpoint_depth == 0 { self.checkpoints
Some(self.leaves.len()) .iter()
} else { .rev()
self.checkpoints .skip(checkpoint_depth)
.iter() .map(|(_, c)| c.leaves_len)
.rev() .next()
.skip(checkpoint_depth - 1)
.map(|(_, c)| c.leaves_len)
.next()
}
} }
/// Removes the oldest checkpoint. Returns true if successful and false if /// Removes the oldest checkpoint. Returns true if successful and false if
@ -237,21 +236,20 @@ impl<H: Hashable + PartialEq + Clone, C: Ord + Clone + core::fmt::Debug, const D
} }
} }
fn root(&self, checkpoint_depth: usize) -> Option<H> { fn root(&self, checkpoint_depth: Option<usize>) -> Option<H> {
self.leaves_at_checkpoint_depth(checkpoint_depth) checkpoint_depth.map_or_else(
.and_then(|len| root(&self.leaves[0..len], DEPTH)) || root(&self.leaves[..], DEPTH),
|depth| {
self.leaves_at_checkpoint_depth(depth)
.and_then(|len| root(&self.leaves[0..len], DEPTH))
},
)
} }
fn witness(&self, position: Position, checkpoint_depth: usize) -> Option<Vec<H>> { fn witness(&self, position: Position, checkpoint_depth: usize) -> Option<Vec<H>> {
if self.marks.contains(&position) && checkpoint_depth <= self.checkpoints.len() { if self.marks.contains(&position) {
let leaves_len = self.leaves_at_checkpoint_depth(checkpoint_depth)?; let leaves_len = self.leaves_at_checkpoint_depth(checkpoint_depth)?;
let c_idx = self.checkpoints.len() - checkpoint_depth; if u64::from(position) >= u64::try_from(leaves_len).unwrap() {
if self
.checkpoints
.iter()
.skip(c_idx)
.any(|(_, c)| c.marked.contains(&position))
{
// The requested position was marked after the checkpoint was created, so we // The requested position was marked after the checkpoint was created, so we
// cannot create a witness. // cannot create a witness.
None None
@ -299,14 +297,35 @@ impl<H: Hashable + PartialEq + Clone, C: Ord + Clone + core::fmt::Debug, const D
} }
} }
fn rewind(&mut self) -> bool { fn checkpoint_count(&self) -> usize {
if let Some((id, c)) = self.checkpoints.iter().rev().next() { self.checkpoints.len()
self.leaves.truncate(c.leaves_len); }
for pos in c.marked.iter() {
self.marks.remove(pos); fn rewind(&mut self, depth: usize) -> bool {
if self.checkpoints.len() > depth {
let mut to_delete = vec![];
for (idx, (id, c)) in self
.checkpoints
.iter_mut()
.rev()
.enumerate()
.take(depth + 1)
{
for pos in c.marked.iter() {
self.marks.remove(pos);
}
if idx < depth {
to_delete.push(id.clone());
} else {
self.leaves.truncate(c.leaves_len);
c.marked.clear();
c.forgotten.clear();
}
} }
let id = id.clone(); // needed to avoid mutable/immutable borrow conflict for cid in to_delete.iter() {
self.checkpoints.remove(&id); self.checkpoints.remove(cid);
}
true true
} else { } else {
false false
@ -334,7 +353,7 @@ mod tests {
} }
let tree = CompleteTree::<SipHashable, (), DEPTH>::new(100); let tree = CompleteTree::<SipHashable, (), DEPTH>::new(100);
assert_eq!(tree.root(0).unwrap(), expected); assert_eq!(tree.root(None), Some(expected));
} }
#[test] #[test]
@ -362,7 +381,7 @@ mod tests {
), ),
); );
assert_eq!(tree.root(0).unwrap(), expected); assert_eq!(tree.root(None), Some(expected));
} }
#[test] #[test]
@ -408,7 +427,9 @@ mod tests {
), ),
); );
assert_eq!(tree.root(0).unwrap(), expected); assert_eq!(tree.root(None), Some(expected.clone()));
tree.checkpoint((), None);
assert_eq!(tree.root(Some(0)), Some(expected.clone()));
for i in 0u64..(1 << DEPTH) { for i in 0u64..(1 << DEPTH) {
let position = Position::try_from(i).unwrap(); let position = Position::try_from(i).unwrap();

View File

@ -13,56 +13,84 @@ pub mod complete_tree;
// Traits used to permit comparison testing between tree implementations. // Traits used to permit comparison testing between tree implementations.
// //
/// A Merkle tree that supports incremental appends, marking of /// A Merkle tree that supports incremental appends, marking of leaf nodes for construction of
/// leaf nodes for construction of witnesses, checkpoints and rollbacks. /// witnesses, checkpoints and rollbacks.
pub trait Tree<H, C> { pub trait Tree<H, C> {
/// Returns the depth of the tree. /// Returns the number of levels in the tree.
fn depth(&self) -> u8; fn depth(&self) -> u8;
/// Appends a new value to the tree at the next available slot. /// Appends a new value to the tree at the next available slot. Returns true if successful and
/// Returns true if successful and false if the tree would exceed /// false if the tree would exceed the maximum allowed number of levels in the tree.
/// the maximum allowed depth.
fn append(&mut self, value: H, retention: Retention<C>) -> bool; fn append(&mut self, value: H, retention: Retention<C>) -> bool;
/// Returns the most recently appended leaf value. /// Returns the most recently appended leaf value.
fn current_position(&self) -> Option<Position>; fn current_position(&self) -> Option<Position>;
/// Returns the leaf at the specified position if the tree can produce /// Returns the leaf at the specified position if the tree can produce a witness for it.
/// a witness for it.
fn get_marked_leaf(&self, position: Position) -> Option<H>; fn get_marked_leaf(&self, position: Position) -> Option<H>;
/// Return a set of all the positions for which we have marked. /// Return a set of all the positions for which we have marked.
fn marked_positions(&self) -> BTreeSet<Position>; fn marked_positions(&self) -> BTreeSet<Position>;
/// Obtains the root of the Merkle tree at the specified checkpoint depth /// Obtains the root of the Merkle tree at the specified checkpoint depth by hashing against
/// by hashing against empty nodes up to the maximum height of the tree. /// empty nodes up to the maximum height of the tree.
/// Returns `None` if there are not enough checkpoints available to reach the ///
/// requested checkpoint depth. /// Returns `None` if a checkpoint depth is provided but there are not enough checkpoints
fn root(&self, checkpoint_depth: usize) -> Option<H>; /// available to reach the requested checkpoint depth.
///
/// ## Parameters
/// - `checkpoint_depth`: A zero-based index into the checkpoints that have been added to the
/// tree, in reverse checkpoint identifier order. If `checkpoint_depth` is `None`, the root
/// is computed over all leaves that have been added to the tree.
fn root(&self, checkpoint_depth: Option<usize>) -> Option<H>;
/// Obtains a witness for the value at the specified leaf position, as of the tree state at the /// Obtains a witness for the value at the specified leaf position, as of the tree state at the
/// given checkpoint depth. Returns `None` if there is no witness information for the requested /// given checkpoint depth.
/// position or if no checkpoint is available at the specified depth. ///
/// Returns `None` if there is no witness information for the requested position or if no
/// checkpoint is available at the specified depth.
///
/// ## Parameters
/// - `position`: The position of the leaf for which the witness is to be computed.
/// - `checkpoint_depth`: A zero-based index over the checkpoints that have been added to the
/// tree, in reverse checkpoint identifier order.
fn witness(&self, position: Position, checkpoint_depth: usize) -> Option<Vec<H>>; fn witness(&self, position: Position, checkpoint_depth: usize) -> Option<Vec<H>>;
/// Marks the value at the specified position as a value we're no longer /// Marks the value at the specified position as a value we're no longer interested in
/// interested in maintaining a mark for. Returns true if successful and /// maintaining a mark for.
/// false if we were already not maintaining a mark at this position. ///
/// Returns true if successful and false if we were already not maintaining a mark at this
/// position.
///
/// ## Parameters
/// - `position`: The position of the marked leaf.
fn remove_mark(&mut self, position: Position) -> bool; fn remove_mark(&mut self, position: Position) -> bool;
/// Creates a new checkpoint for the current tree state. /// Creates a new checkpoint for the current tree state, with the given checkpoint identifier.
/// ///
/// It is valid to have multiple checkpoints for the same tree state, and each `rewind` call /// It is valid to have multiple checkpoints for the same tree state, and each `rewind` call
/// will remove a single checkpoint. Returns `false` if the checkpoint identifier provided is /// will remove a single checkpoint. Returns `false` if the checkpoint identifier provided is
/// less than or equal to the maximum checkpoint identifier observed. /// less than or equal to the maximum checkpoint identifier among previously added checkpoints.
///
/// ## Parameters
/// - `id`: The identifier for the checkpoint being added to the tree.
fn checkpoint(&mut self, id: C) -> bool; fn checkpoint(&mut self, id: C) -> bool;
/// Rewinds the tree state to the previous checkpoint, and then removes that checkpoint record. /// Returns the number of checkpoints in the tree.
fn checkpoint_count(&self) -> usize;
/// Rewinds the tree state to the checkpoint at the specified checkpoint depth.
/// ///
/// If there are multiple checkpoints at a given tree state, the tree state will not be altered /// Returns `true` if the request was satisfied, or `false` if insufficient checkpoints were
/// until all checkpoints at that tree state have been removed using `rewind`. This function /// available to satisfy the request.
/// will return false and leave the tree unmodified if no checkpoints exist. ///
fn rewind(&mut self) -> bool; /// ## Parameters
/// - `checkpoint_depth`: A zero-based index over the checkpoints that have been added to the
/// tree, in reverse checkpoint identifier order. A checkpoint depth value of `0` removes all
/// data added to the tree after the most recently-added checkpoint. The checkpoint at the
/// specified depth is retained, but all data and metadata related to operations on the tree
/// that occurred after the checkpoint was created is discarded.
fn rewind(&mut self, checkpoint_depth: usize) -> bool;
} }
// //
@ -100,7 +128,7 @@ pub enum Operation<A, C> {
MarkedPositions, MarkedPositions,
Unmark(Position), Unmark(Position),
Checkpoint(C), Checkpoint(C),
Rewind, Rewind(usize),
Witness(Position, usize), Witness(Position, usize),
GarbageCollect, GarbageCollect,
} }
@ -137,8 +165,8 @@ impl<H: Hashable + Clone, C: Clone> Operation<H, C> {
tree.checkpoint(id.clone()); tree.checkpoint(id.clone());
None None
} }
Rewind => { Rewind(depth) => {
assert!(tree.rewind(), "rewind failed"); assert_eq!(tree.checkpoint_count() > *depth, tree.rewind(*depth));
None None
} }
Witness(p, d) => tree.witness(*p, *d).map(|xs| (*p, xs)), Witness(p, d) => tree.witness(*p, *d).map(|xs| (*p, xs)),
@ -165,7 +193,7 @@ impl<H: Hashable + Clone, C: Clone> Operation<H, C> {
MarkedPositions => MarkedPositions, MarkedPositions => MarkedPositions,
Unmark(p) => Unmark(*p), Unmark(p) => Unmark(*p),
Checkpoint(id) => Checkpoint(f(id)), Checkpoint(id) => Checkpoint(f(id)),
Rewind => Rewind, Rewind(depth) => Rewind(*depth),
Witness(p, d) => Witness(*p, *d), Witness(p, d) => Witness(*p, *d),
GarbageCollect => GarbageCollect, GarbageCollect => GarbageCollect,
} }
@ -209,7 +237,7 @@ where
pos_gen.clone().prop_map(Operation::MarkedLeaf), pos_gen.clone().prop_map(Operation::MarkedLeaf),
pos_gen.clone().prop_map(Operation::Unmark), pos_gen.clone().prop_map(Operation::Unmark),
Just(Operation::Checkpoint(())), Just(Operation::Checkpoint(())),
Just(Operation::Rewind), (0usize..10).prop_map(Operation::Rewind),
pos_gen.prop_flat_map(|i| (0usize..10).prop_map(move |depth| Operation::Witness(i, depth))), pos_gen.prop_flat_map(|i| (0usize..10).prop_map(move |depth| Operation::Witness(i, depth))),
] ]
} }
@ -225,8 +253,8 @@ pub fn apply_operation<H, C, T: Tree<H, C>>(tree: &mut T, op: Operation<H, C>) {
Checkpoint(id) => { Checkpoint(id) => {
tree.checkpoint(id); tree.checkpoint(id);
} }
Rewind => { Rewind(depth) => {
tree.rewind(); tree.rewind(depth);
} }
CurrentPosition => {} CurrentPosition => {}
Witness(_, _) => {} Witness(_, _) => {}
@ -283,40 +311,43 @@ pub fn check_operations<H: Hashable + Ord + Clone + Debug, C: Clone, T: Tree<H,
tree_checkpoints.push(tree_size); tree_checkpoints.push(tree_size);
tree.checkpoint(id.clone()); tree.checkpoint(id.clone());
} }
Rewind => { Rewind(depth) => {
if tree.rewind() { if tree.rewind(*depth) {
prop_assert!(!tree_checkpoints.is_empty()); let retained = tree_checkpoints.len() - depth;
let checkpointed_tree_size = tree_checkpoints.pop().unwrap(); if *depth > 0 {
tree_values.truncate(checkpointed_tree_size); // The last checkpoint will have been dropped, and the tree will have been
tree_size = checkpointed_tree_size; // truncated to a previous checkpoint.
tree_checkpoints.truncate(retained);
}
let checkpointed_tree_size = tree_checkpoints
.last()
.expect("at least one checkpoint must exist in order to be able to rewind");
tree_values.truncate(*checkpointed_tree_size);
tree_size = *checkpointed_tree_size;
} }
} }
Witness(position, depth) => { Witness(position, depth) => {
if let Some(path) = tree.witness(*position, *depth) { if let Some(path) = tree.witness(*position, *depth) {
let value: H = tree_values[<usize>::try_from(*position).unwrap()].clone(); let value: H = tree_values[<usize>::try_from(*position).unwrap()].clone();
let tree_root = tree.root(*depth); let tree_root = tree.root(Some(*depth)).expect(
"we must be able to compute a root anywhere we can compute a witness.",
);
let mut extended_tree_values = tree_values.clone();
// prune the tree back to the checkpointed size.
let checkpointed_tree_size =
tree_checkpoints[tree_checkpoints.len() - (depth + 1)];
extended_tree_values.truncate(checkpointed_tree_size);
if tree_checkpoints.len() >= *depth { // compute the root
let mut extended_tree_values = tree_values.clone(); let expected_root =
if *depth > 0 { complete_tree::root::<H>(&extended_tree_values, tree.depth());
// prune the tree back to the checkpointed size. prop_assert_eq!(&tree_root, &expected_root);
if let Some(checkpointed_tree_size) =
tree_checkpoints.get(tree_checkpoints.len() - depth)
{
extended_tree_values.truncate(*checkpointed_tree_size);
}
}
// compute the root prop_assert_eq!(
let expected_root = &compute_root_from_witness(value, *position, &path),
complete_tree::root::<H>(&extended_tree_values, tree.depth()); &expected_root
prop_assert_eq!(&tree_root.unwrap(), &expected_root); );
prop_assert_eq!(
&compute_root_from_witness(value, *position, &path),
&expected_root
);
}
} }
} }
GarbageCollect => {} GarbageCollect => {}
@ -382,7 +413,7 @@ impl<H: Hashable + Ord + Clone + Debug, C: Clone, I: Tree<H, C>, E: Tree<H, C>>
a a
} }
fn root(&self, checkpoint_depth: usize) -> Option<H> { fn root(&self, checkpoint_depth: Option<usize>) -> Option<H> {
let a = self.inefficient.root(checkpoint_depth); let a = self.inefficient.root(checkpoint_depth);
let b = self.efficient.root(checkpoint_depth); let b = self.efficient.root(checkpoint_depth);
assert_eq!(a, b); assert_eq!(a, b);
@ -431,9 +462,16 @@ impl<H: Hashable + Ord + Clone + Debug, C: Clone, I: Tree<H, C>, E: Tree<H, C>>
a a
} }
fn rewind(&mut self) -> bool { fn checkpoint_count(&self) -> usize {
let a = self.inefficient.rewind(); let a = self.inefficient.checkpoint_count();
let b = self.efficient.rewind(); let b = self.efficient.checkpoint_count();
assert_eq!(a, b);
a
}
fn rewind(&mut self, checkpoint_depth: usize) -> bool {
let a = self.inefficient.rewind(checkpoint_depth);
let b = self.efficient.rewind(checkpoint_depth);
assert_eq!(a, b); assert_eq!(a, b);
a a
} }
@ -478,7 +516,7 @@ impl TestCheckpoint for usize {
} }
trait TestTree<H: TestHashable, C: TestCheckpoint> { trait TestTree<H: TestHashable, C: TestCheckpoint> {
fn assert_root(&self, checkpoint_depth: usize, values: &[u64]); fn assert_root(&self, checkpoint_depth: Option<usize>, values: &[u64]);
fn assert_append(&mut self, value: u64, retention: Retention<u64>); fn assert_append(&mut self, value: u64, retention: Retention<u64>);
@ -486,7 +524,7 @@ trait TestTree<H: TestHashable, C: TestCheckpoint> {
} }
impl<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>> TestTree<H, C> for T { impl<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>> TestTree<H, C> for T {
fn assert_root(&self, checkpoint_depth: usize, values: &[u64]) { fn assert_root(&self, checkpoint_depth: Option<usize>, values: &[u64]) {
assert_eq!( assert_eq!(
self.root(checkpoint_depth).unwrap(), self.root(checkpoint_depth).unwrap(),
H::combine_all(self.depth(), values) H::combine_all(self.depth(), values)
@ -518,13 +556,13 @@ pub fn check_root_hashes<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: F
{ {
let mut tree = new_tree(100); let mut tree = new_tree(100);
tree.assert_root(0, &[]); tree.assert_root(None, &[]);
tree.assert_append(0, Ephemeral); tree.assert_append(0, Ephemeral);
tree.assert_root(0, &[0]); tree.assert_root(None, &[0]);
tree.assert_append(1, Ephemeral); tree.assert_append(1, Ephemeral);
tree.assert_root(0, &[0, 1]); tree.assert_root(None, &[0, 1]);
tree.assert_append(2, Ephemeral); tree.assert_append(2, Ephemeral);
tree.assert_root(0, &[0, 1, 2]); tree.assert_root(None, &[0, 1, 2]);
} }
{ {
@ -539,7 +577,7 @@ pub fn check_root_hashes<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: F
for _ in 0..3 { for _ in 0..3 {
t.assert_append(0, Ephemeral); t.assert_append(0, Ephemeral);
} }
t.assert_root(0, &[0, 0, 0, 0]); t.assert_root(None, &[0, 0, 0, 0]);
} }
} }
@ -584,12 +622,14 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
let mut tree = new_tree(100); let mut tree = new_tree(100);
tree.assert_append(0, Ephemeral); tree.assert_append(0, Ephemeral);
tree.assert_append(1, Marked); tree.assert_append(1, Marked);
tree.checkpoint(C::from_u64(0));
assert_eq!(tree.witness(Position::from(0), 0), None); assert_eq!(tree.witness(Position::from(0), 0), None);
} }
{ {
let mut tree = new_tree(100); let mut tree = new_tree(100);
tree.assert_append(0, Marked); tree.assert_append(0, Marked);
tree.checkpoint(C::from_u64(0));
assert_eq!( assert_eq!(
tree.witness(Position::from(0), 0), tree.witness(Position::from(0), 0),
Some(vec![ Some(vec![
@ -601,6 +641,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
); );
tree.assert_append(1, Ephemeral); tree.assert_append(1, Ephemeral);
tree.checkpoint(C::from_u64(1));
assert_eq!( assert_eq!(
tree.witness(0.into(), 0), tree.witness(0.into(), 0),
Some(vec![ Some(vec![
@ -612,6 +653,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
); );
tree.assert_append(2, Marked); tree.assert_append(2, Marked);
tree.checkpoint(C::from_u64(2));
assert_eq!( assert_eq!(
tree.witness(Position::from(2), 0), tree.witness(Position::from(2), 0),
Some(vec![ Some(vec![
@ -623,6 +665,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
); );
tree.assert_append(3, Ephemeral); tree.assert_append(3, Ephemeral);
tree.checkpoint(C::from_u64(3));
assert_eq!( assert_eq!(
tree.witness(Position::from(2), 0), tree.witness(Position::from(2), 0),
Some(vec![ Some(vec![
@ -634,6 +677,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
); );
tree.assert_append(4, Ephemeral); tree.assert_append(4, Ephemeral);
tree.checkpoint(C::from_u64(4));
assert_eq!( assert_eq!(
tree.witness(Position::from(2), 0), tree.witness(Position::from(2), 0),
Some(vec![ Some(vec![
@ -653,6 +697,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
} }
tree.assert_append(6, Marked); tree.assert_append(6, Marked);
tree.assert_append(7, Ephemeral); tree.assert_append(7, Ephemeral);
tree.checkpoint(C::from_u64(0));
assert_eq!( assert_eq!(
tree.witness(0.into(), 0), tree.witness(0.into(), 0),
@ -674,6 +719,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
tree.assert_append(4, Marked); tree.assert_append(4, Marked);
tree.assert_append(5, Marked); tree.assert_append(5, Marked);
tree.assert_append(6, Ephemeral); tree.assert_append(6, Ephemeral);
tree.checkpoint(C::from_u64(0));
assert_eq!( assert_eq!(
tree.witness(Position::from(5), 0), tree.witness(Position::from(5), 0),
@ -693,6 +739,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
} }
tree.assert_append(10, Marked); tree.assert_append(10, Marked);
tree.assert_append(11, Ephemeral); tree.assert_append(11, Ephemeral);
tree.checkpoint(C::from_u64(0));
assert_eq!( assert_eq!(
tree.witness(Position::from(10), 0), tree.witness(Position::from(10), 0),
@ -714,7 +761,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
marking: Marking::Marked, marking: Marking::Marked,
}, },
); );
assert!(tree.rewind()); assert!(tree.rewind(0));
for i in 1..4 { for i in 1..4 {
tree.assert_append(i, Ephemeral); tree.assert_append(i, Ephemeral);
} }
@ -722,6 +769,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
for i in 5..8 { for i in 5..8 {
tree.assert_append(i, Ephemeral); tree.assert_append(i, Ephemeral);
} }
tree.checkpoint(C::from_u64(2));
assert_eq!( assert_eq!(
tree.witness(0.into(), 0), tree.witness(0.into(), 0),
Some(vec![ Some(vec![
@ -749,7 +797,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
}, },
); );
tree.assert_append(7, Ephemeral); tree.assert_append(7, Ephemeral);
assert!(tree.rewind()); assert!(tree.rewind(0));
assert_eq!( assert_eq!(
tree.witness(Position::from(2), 0), tree.witness(Position::from(2), 0),
Some(vec![ Some(vec![
@ -770,6 +818,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
tree.assert_append(13, Marked); tree.assert_append(13, Marked);
tree.assert_append(14, Ephemeral); tree.assert_append(14, Ephemeral);
tree.assert_append(15, Ephemeral); tree.assert_append(15, Ephemeral);
tree.checkpoint(C::from_u64(0));
assert_eq!( assert_eq!(
tree.witness(Position::from(12), 0), tree.witness(Position::from(12), 0),
@ -786,7 +835,13 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
let ops = (0..=11) let ops = (0..=11)
.map(|i| Append(H::from_u64(i), Marked)) .map(|i| Append(H::from_u64(i), Marked))
.chain(Some(Append(H::from_u64(12), Ephemeral))) .chain(Some(Append(H::from_u64(12), Ephemeral)))
.chain(Some(Append(H::from_u64(13), Ephemeral))) .chain(Some(Append(
H::from_u64(13),
Checkpoint {
id: C::from_u64(0),
marking: Marking::None,
},
)))
.chain(Some(Witness(11u64.into(), 0))) .chain(Some(Witness(11u64.into(), 0)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -840,7 +895,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
marking: Marking::None, marking: Marking::None,
}, },
), ),
Witness(3u64.into(), 5), Witness(3u64.into(), 4),
]; ];
let mut tree = new_tree(100); let mut tree = new_tree(100);
assert_eq!( assert_eq!(
@ -881,7 +936,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
), ),
Append(H::from_u64(0), Ephemeral), Append(H::from_u64(0), Ephemeral),
Append(H::from_u64(0), Ephemeral), Append(H::from_u64(0), Ephemeral),
Witness(Position::from(3), 1), Witness(Position::from(3), 0),
]; ];
let mut tree = new_tree(100); let mut tree = new_tree(100);
assert_eq!( assert_eq!(
@ -918,8 +973,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
marking: Marking::None, marking: Marking::None,
}, },
), ),
Rewind, Rewind(2),
Rewind,
Witness(Position::from(7), 2), Witness(Position::from(7), 2),
]; ];
let mut tree = new_tree(100); let mut tree = new_tree(100);
@ -944,7 +998,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
marking: Marking::None, marking: Marking::None,
}, },
), ),
Witness(Position::from(2), 2), Witness(Position::from(2), 1),
]; ];
let mut tree = new_tree(100); let mut tree = new_tree(100);
assert_eq!( assert_eq!(
@ -966,40 +1020,43 @@ pub fn check_checkpoint_rewind<C: TestCheckpoint, T: Tree<String, C>, F: Fn(usiz
new_tree: F, new_tree: F,
) { ) {
let mut t = new_tree(100); let mut t = new_tree(100);
assert!(!t.rewind()); assert!(!t.rewind(0));
let mut t = new_tree(100); let mut t = new_tree(100);
t.assert_checkpoint(1); t.assert_checkpoint(1);
assert!(t.rewind()); assert!(t.rewind(0));
assert!(!t.rewind(1));
let mut t = new_tree(100); let mut t = new_tree(100);
t.append("a".to_string(), Retention::Ephemeral); t.append("a".to_string(), Retention::Ephemeral);
t.assert_checkpoint(1); t.assert_checkpoint(1);
t.append("b".to_string(), Retention::Marked); t.append("b".to_string(), Retention::Marked);
assert!(t.rewind()); assert_eq!(Some(Position::from(1)), t.current_position());
assert!(t.rewind(0));
assert_eq!(Some(Position::from(0)), t.current_position()); assert_eq!(Some(Position::from(0)), t.current_position());
let mut t = new_tree(100); let mut t = new_tree(100);
t.append("a".to_string(), Retention::Marked); t.append("a".to_string(), Retention::Marked);
t.assert_checkpoint(1); t.assert_checkpoint(1);
assert!(t.rewind()); assert!(t.rewind(0));
assert_eq!(Some(Position::from(0)), t.current_position());
let mut t = new_tree(100); let mut t = new_tree(100);
t.append("a".to_string(), Retention::Marked); t.append("a".to_string(), Retention::Marked);
t.assert_checkpoint(1); t.assert_checkpoint(1);
t.append("a".to_string(), Retention::Ephemeral); t.append("a".to_string(), Retention::Ephemeral);
assert!(t.rewind()); assert!(t.rewind(0));
assert_eq!(Some(Position::from(0)), t.current_position()); assert_eq!(Some(Position::from(0)), t.current_position());
let mut t = new_tree(100); let mut t = new_tree(100);
t.append("a".to_string(), Retention::Ephemeral); t.append("a".to_string(), Retention::Ephemeral);
t.assert_checkpoint(1); t.assert_checkpoint(1);
t.assert_checkpoint(2); t.assert_checkpoint(2);
assert!(t.rewind()); assert!(t.rewind(1));
t.append("b".to_string(), Retention::Ephemeral); t.append("b".to_string(), Retention::Ephemeral);
assert!(t.rewind()); assert!(t.rewind(0));
t.append("b".to_string(), Retention::Ephemeral); t.append("b".to_string(), Retention::Ephemeral);
assert_eq!(t.root(0).unwrap(), "ab______________"); assert_eq!(t.root(None).unwrap(), "ab______________");
} }
pub fn check_remove_mark<C: TestCheckpoint, T: Tree<String, C>, F: Fn(usize) -> T>(new_tree: F) { pub fn check_remove_mark<C: TestCheckpoint, T: Tree<String, C>, F: Fn(usize) -> T>(new_tree: F) {
@ -1044,7 +1101,7 @@ pub fn check_rewind_remove_mark<C: TestCheckpoint, T: Tree<String, C>, F: Fn(usi
let mut tree = new_tree(100); let mut tree = new_tree(100);
tree.append("e".to_string(), Retention::Marked); tree.append("e".to_string(), Retention::Marked);
tree.assert_checkpoint(1); tree.assert_checkpoint(1);
assert!(tree.rewind()); assert!(tree.rewind(0));
assert!(tree.remove_mark(0u64.into())); assert!(tree.remove_mark(0u64.into()));
// use a maximum number of checkpoints of 1 // use a maximum number of checkpoints of 1
@ -1071,14 +1128,14 @@ pub fn check_rewind_remove_mark<C: TestCheckpoint, T: Tree<String, C>, F: Fn(usi
vec![ vec![
append_str("x", Retention::Marked), append_str("x", Retention::Marked),
Checkpoint(C::from_u64(1)), Checkpoint(C::from_u64(1)),
Rewind, Rewind(0),
unmark(0), unmark(0),
], ],
vec![ vec![
append_str("d", Retention::Marked), append_str("d", Retention::Marked),
Checkpoint(C::from_u64(1)), Checkpoint(C::from_u64(1)),
unmark(0), unmark(0),
Rewind, Rewind(0),
unmark(0), unmark(0),
], ],
vec![ vec![
@ -1086,22 +1143,22 @@ pub fn check_rewind_remove_mark<C: TestCheckpoint, T: Tree<String, C>, F: Fn(usi
Checkpoint(C::from_u64(1)), Checkpoint(C::from_u64(1)),
Checkpoint(C::from_u64(2)), Checkpoint(C::from_u64(2)),
unmark(0), unmark(0),
Rewind, Rewind(0),
Rewind, Rewind(1),
], ],
vec![ vec![
append_str("s", Retention::Marked), append_str("s", Retention::Marked),
append_str("m", Retention::Ephemeral), append_str("m", Retention::Ephemeral),
Checkpoint(C::from_u64(1)), Checkpoint(C::from_u64(1)),
unmark(0), unmark(0),
Rewind, Rewind(0),
unmark(0), unmark(0),
unmark(0), unmark(0),
], ],
vec![ vec![
append_str("a", Retention::Marked), append_str("a", Retention::Marked),
Checkpoint(C::from_u64(1)), Checkpoint(C::from_u64(1)),
Rewind, Rewind(0),
append_str("a", Retention::Marked), append_str("a", Retention::Marked),
], ],
]; ];
@ -1194,7 +1251,7 @@ pub fn check_witness_consistency<C: TestCheckpoint, T: Tree<String, C>, F: Fn(us
Checkpoint(C::from_u64(1)), Checkpoint(C::from_u64(1)),
unmark(0), unmark(0),
Checkpoint(C::from_u64(2)), Checkpoint(C::from_u64(2)),
Rewind, Rewind(0),
append_str("b", Retention::Ephemeral), append_str("b", Retention::Ephemeral),
witness(0, 0), witness(0, 0),
], ],
@ -1202,7 +1259,7 @@ pub fn check_witness_consistency<C: TestCheckpoint, T: Tree<String, C>, F: Fn(us
append_str("a", Retention::Marked), append_str("a", Retention::Marked),
Checkpoint(C::from_u64(1)), Checkpoint(C::from_u64(1)),
Checkpoint(C::from_u64(2)), Checkpoint(C::from_u64(2)),
Rewind, Rewind(1),
append_str("a", Retention::Ephemeral), append_str("a", Retention::Ephemeral),
unmark(0), unmark(0),
witness(0, 1), witness(0, 1),