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:
parent
5bbd832930
commit
05f23d9763
|
@ -7,5 +7,32 @@ and this project adheres to Rust's notion of
|
|||
|
||||
## 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
|
||||
Initial release.
|
||||
|
|
|
@ -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>) {
|
||||
self.checkpoints.insert(
|
||||
id,
|
||||
Checkpoint::at_length(pos.map_or_else(
|
||||
|| 0,
|
||||
|| self.leaves.len(),
|
||||
|p| usize::try_from(p).expect(MAX_COMPLETE_SIZE_ERROR) + 1,
|
||||
)),
|
||||
);
|
||||
|
@ -170,17 +173,13 @@ impl<H: Hashable, C: Clone + Ord + core::fmt::Debug, const DEPTH: u8> CompleteTr
|
|||
}
|
||||
|
||||
fn leaves_at_checkpoint_depth(&self, checkpoint_depth: usize) -> Option<usize> {
|
||||
if checkpoint_depth == 0 {
|
||||
Some(self.leaves.len())
|
||||
} else {
|
||||
self.checkpoints
|
||||
.iter()
|
||||
.rev()
|
||||
.skip(checkpoint_depth - 1)
|
||||
.skip(checkpoint_depth)
|
||||
.map(|(_, c)| c.leaves_len)
|
||||
.next()
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the oldest checkpoint. Returns true if successful and false if
|
||||
/// there are fewer than `self.max_checkpoints` checkpoints.
|
||||
|
@ -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> {
|
||||
self.leaves_at_checkpoint_depth(checkpoint_depth)
|
||||
fn root(&self, checkpoint_depth: Option<usize>) -> Option<H> {
|
||||
checkpoint_depth.map_or_else(
|
||||
|| 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>> {
|
||||
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 c_idx = self.checkpoints.len() - checkpoint_depth;
|
||||
if self
|
||||
.checkpoints
|
||||
.iter()
|
||||
.skip(c_idx)
|
||||
.any(|(_, c)| c.marked.contains(&position))
|
||||
{
|
||||
if u64::from(position) >= u64::try_from(leaves_len).unwrap() {
|
||||
// The requested position was marked after the checkpoint was created, so we
|
||||
// cannot create a witness.
|
||||
None
|
||||
|
@ -299,14 +297,35 @@ impl<H: Hashable + PartialEq + Clone, C: Ord + Clone + core::fmt::Debug, const D
|
|||
}
|
||||
}
|
||||
|
||||
fn rewind(&mut self) -> bool {
|
||||
if let Some((id, c)) = self.checkpoints.iter().rev().next() {
|
||||
self.leaves.truncate(c.leaves_len);
|
||||
fn checkpoint_count(&self) -> usize {
|
||||
self.checkpoints.len()
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
let id = id.clone(); // needed to avoid mutable/immutable borrow conflict
|
||||
self.checkpoints.remove(&id);
|
||||
if idx < depth {
|
||||
to_delete.push(id.clone());
|
||||
} else {
|
||||
self.leaves.truncate(c.leaves_len);
|
||||
c.marked.clear();
|
||||
c.forgotten.clear();
|
||||
}
|
||||
}
|
||||
for cid in to_delete.iter() {
|
||||
self.checkpoints.remove(cid);
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
@ -334,7 +353,7 @@ mod tests {
|
|||
}
|
||||
|
||||
let tree = CompleteTree::<SipHashable, (), DEPTH>::new(100);
|
||||
assert_eq!(tree.root(0).unwrap(), expected);
|
||||
assert_eq!(tree.root(None), Some(expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -362,7 +381,7 @@ mod tests {
|
|||
),
|
||||
);
|
||||
|
||||
assert_eq!(tree.root(0).unwrap(), expected);
|
||||
assert_eq!(tree.root(None), Some(expected));
|
||||
}
|
||||
|
||||
#[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) {
|
||||
let position = Position::try_from(i).unwrap();
|
||||
|
|
|
@ -13,56 +13,84 @@ pub mod complete_tree;
|
|||
// Traits used to permit comparison testing between tree implementations.
|
||||
//
|
||||
|
||||
/// A Merkle tree that supports incremental appends, marking of
|
||||
/// leaf nodes for construction of witnesses, checkpoints and rollbacks.
|
||||
/// A Merkle tree that supports incremental appends, marking of leaf nodes for construction of
|
||||
/// witnesses, checkpoints and rollbacks.
|
||||
pub trait Tree<H, C> {
|
||||
/// Returns the depth of the tree.
|
||||
/// Returns the number of levels in the tree.
|
||||
fn depth(&self) -> u8;
|
||||
|
||||
/// Appends a new value to the tree at the next available slot.
|
||||
/// Returns true if successful and false if the tree would exceed
|
||||
/// the maximum allowed depth.
|
||||
/// Appends a new value to the tree at the next available slot. Returns true if successful and
|
||||
/// false if the tree would exceed the maximum allowed number of levels in the tree.
|
||||
fn append(&mut self, value: H, retention: Retention<C>) -> bool;
|
||||
|
||||
/// Returns the most recently appended leaf value.
|
||||
fn current_position(&self) -> Option<Position>;
|
||||
|
||||
/// Returns the leaf at the specified position if the tree can produce
|
||||
/// a witness for it.
|
||||
/// Returns the leaf at the specified position if the tree can produce a witness for it.
|
||||
fn get_marked_leaf(&self, position: Position) -> Option<H>;
|
||||
|
||||
/// Return a set of all the positions for which we have marked.
|
||||
fn marked_positions(&self) -> BTreeSet<Position>;
|
||||
|
||||
/// Obtains the root of the Merkle tree at the specified checkpoint depth
|
||||
/// by hashing against 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.
|
||||
fn root(&self, checkpoint_depth: usize) -> Option<H>;
|
||||
/// Obtains the root of the Merkle tree at the specified checkpoint depth by hashing against
|
||||
/// empty nodes up to the maximum height of the tree.
|
||||
///
|
||||
/// Returns `None` if a checkpoint depth is provided but there are not enough checkpoints
|
||||
/// 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
|
||||
/// given checkpoint depth. Returns `None` if there is no witness information for the requested
|
||||
/// position or if no checkpoint is available at the specified depth.
|
||||
/// given checkpoint 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>>;
|
||||
|
||||
/// Marks the value at the specified position as a value we're no longer
|
||||
/// interested in maintaining a mark for. Returns true if successful and
|
||||
/// false if we were already not maintaining a mark at this position.
|
||||
/// Marks the value at the specified position as a value we're no longer interested in
|
||||
/// maintaining a mark for.
|
||||
///
|
||||
/// 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;
|
||||
|
||||
/// 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
|
||||
/// 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;
|
||||
|
||||
/// 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
|
||||
/// until all checkpoints at that tree state have been removed using `rewind`. This function
|
||||
/// will return false and leave the tree unmodified if no checkpoints exist.
|
||||
fn rewind(&mut self) -> bool;
|
||||
/// Returns `true` if the request was satisfied, or `false` if insufficient checkpoints were
|
||||
/// available to satisfy the request.
|
||||
///
|
||||
/// ## 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,
|
||||
Unmark(Position),
|
||||
Checkpoint(C),
|
||||
Rewind,
|
||||
Rewind(usize),
|
||||
Witness(Position, usize),
|
||||
GarbageCollect,
|
||||
}
|
||||
|
@ -137,8 +165,8 @@ impl<H: Hashable + Clone, C: Clone> Operation<H, C> {
|
|||
tree.checkpoint(id.clone());
|
||||
None
|
||||
}
|
||||
Rewind => {
|
||||
assert!(tree.rewind(), "rewind failed");
|
||||
Rewind(depth) => {
|
||||
assert_eq!(tree.checkpoint_count() > *depth, tree.rewind(*depth));
|
||||
None
|
||||
}
|
||||
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,
|
||||
Unmark(p) => Unmark(*p),
|
||||
Checkpoint(id) => Checkpoint(f(id)),
|
||||
Rewind => Rewind,
|
||||
Rewind(depth) => Rewind(*depth),
|
||||
Witness(p, d) => Witness(*p, *d),
|
||||
GarbageCollect => GarbageCollect,
|
||||
}
|
||||
|
@ -209,7 +237,7 @@ where
|
|||
pos_gen.clone().prop_map(Operation::MarkedLeaf),
|
||||
pos_gen.clone().prop_map(Operation::Unmark),
|
||||
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))),
|
||||
]
|
||||
}
|
||||
|
@ -225,8 +253,8 @@ pub fn apply_operation<H, C, T: Tree<H, C>>(tree: &mut T, op: Operation<H, C>) {
|
|||
Checkpoint(id) => {
|
||||
tree.checkpoint(id);
|
||||
}
|
||||
Rewind => {
|
||||
tree.rewind();
|
||||
Rewind(depth) => {
|
||||
tree.rewind(depth);
|
||||
}
|
||||
CurrentPosition => {}
|
||||
Witness(_, _) => {}
|
||||
|
@ -283,34 +311,38 @@ pub fn check_operations<H: Hashable + Ord + Clone + Debug, C: Clone, T: Tree<H,
|
|||
tree_checkpoints.push(tree_size);
|
||||
tree.checkpoint(id.clone());
|
||||
}
|
||||
Rewind => {
|
||||
if tree.rewind() {
|
||||
prop_assert!(!tree_checkpoints.is_empty());
|
||||
let checkpointed_tree_size = tree_checkpoints.pop().unwrap();
|
||||
tree_values.truncate(checkpointed_tree_size);
|
||||
tree_size = checkpointed_tree_size;
|
||||
Rewind(depth) => {
|
||||
if tree.rewind(*depth) {
|
||||
let retained = tree_checkpoints.len() - depth;
|
||||
if *depth > 0 {
|
||||
// The last checkpoint will have been dropped, and the tree will have been
|
||||
// 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) => {
|
||||
if let Some(path) = tree.witness(*position, *depth) {
|
||||
let value: H = tree_values[<usize>::try_from(*position).unwrap()].clone();
|
||||
let tree_root = tree.root(*depth);
|
||||
|
||||
if tree_checkpoints.len() >= *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();
|
||||
if *depth > 0 {
|
||||
// prune the tree back to the checkpointed size.
|
||||
if let Some(checkpointed_tree_size) =
|
||||
tree_checkpoints.get(tree_checkpoints.len() - depth)
|
||||
{
|
||||
extended_tree_values.truncate(*checkpointed_tree_size);
|
||||
}
|
||||
}
|
||||
let checkpointed_tree_size =
|
||||
tree_checkpoints[tree_checkpoints.len() - (depth + 1)];
|
||||
extended_tree_values.truncate(checkpointed_tree_size);
|
||||
|
||||
// compute the root
|
||||
let expected_root =
|
||||
complete_tree::root::<H>(&extended_tree_values, tree.depth());
|
||||
prop_assert_eq!(&tree_root.unwrap(), &expected_root);
|
||||
prop_assert_eq!(&tree_root, &expected_root);
|
||||
|
||||
prop_assert_eq!(
|
||||
&compute_root_from_witness(value, *position, &path),
|
||||
|
@ -318,7 +350,6 @@ pub fn check_operations<H: Hashable + Ord + Clone + Debug, C: Clone, T: Tree<H,
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
GarbageCollect => {}
|
||||
}
|
||||
}
|
||||
|
@ -382,7 +413,7 @@ impl<H: Hashable + Ord + Clone + Debug, C: Clone, I: Tree<H, C>, E: Tree<H, C>>
|
|||
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 b = self.efficient.root(checkpoint_depth);
|
||||
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
|
||||
}
|
||||
|
||||
fn rewind(&mut self) -> bool {
|
||||
let a = self.inefficient.rewind();
|
||||
let b = self.efficient.rewind();
|
||||
fn checkpoint_count(&self) -> usize {
|
||||
let a = self.inefficient.checkpoint_count();
|
||||
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);
|
||||
a
|
||||
}
|
||||
|
@ -478,7 +516,7 @@ impl TestCheckpoint for usize {
|
|||
}
|
||||
|
||||
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>);
|
||||
|
||||
|
@ -486,7 +524,7 @@ trait TestTree<H: TestHashable, C: TestCheckpoint> {
|
|||
}
|
||||
|
||||
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!(
|
||||
self.root(checkpoint_depth).unwrap(),
|
||||
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);
|
||||
tree.assert_root(0, &[]);
|
||||
tree.assert_root(None, &[]);
|
||||
tree.assert_append(0, Ephemeral);
|
||||
tree.assert_root(0, &[0]);
|
||||
tree.assert_root(None, &[0]);
|
||||
tree.assert_append(1, Ephemeral);
|
||||
tree.assert_root(0, &[0, 1]);
|
||||
tree.assert_root(None, &[0, 1]);
|
||||
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 {
|
||||
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);
|
||||
tree.assert_append(0, Ephemeral);
|
||||
tree.assert_append(1, Marked);
|
||||
tree.checkpoint(C::from_u64(0));
|
||||
assert_eq!(tree.witness(Position::from(0), 0), None);
|
||||
}
|
||||
|
||||
{
|
||||
let mut tree = new_tree(100);
|
||||
tree.assert_append(0, Marked);
|
||||
tree.checkpoint(C::from_u64(0));
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(0), 0),
|
||||
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.checkpoint(C::from_u64(1));
|
||||
assert_eq!(
|
||||
tree.witness(0.into(), 0),
|
||||
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.checkpoint(C::from_u64(2));
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(2), 0),
|
||||
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.checkpoint(C::from_u64(3));
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(2), 0),
|
||||
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.checkpoint(C::from_u64(4));
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(2), 0),
|
||||
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(7, Ephemeral);
|
||||
tree.checkpoint(C::from_u64(0));
|
||||
|
||||
assert_eq!(
|
||||
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(5, Marked);
|
||||
tree.assert_append(6, Ephemeral);
|
||||
tree.checkpoint(C::from_u64(0));
|
||||
|
||||
assert_eq!(
|
||||
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(11, Ephemeral);
|
||||
tree.checkpoint(C::from_u64(0));
|
||||
|
||||
assert_eq!(
|
||||
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,
|
||||
},
|
||||
);
|
||||
assert!(tree.rewind());
|
||||
assert!(tree.rewind(0));
|
||||
for i in 1..4 {
|
||||
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 {
|
||||
tree.assert_append(i, Ephemeral);
|
||||
}
|
||||
tree.checkpoint(C::from_u64(2));
|
||||
assert_eq!(
|
||||
tree.witness(0.into(), 0),
|
||||
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);
|
||||
assert!(tree.rewind());
|
||||
assert!(tree.rewind(0));
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(2), 0),
|
||||
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(14, Ephemeral);
|
||||
tree.assert_append(15, Ephemeral);
|
||||
tree.checkpoint(C::from_u64(0));
|
||||
|
||||
assert_eq!(
|
||||
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)
|
||||
.map(|i| Append(H::from_u64(i), Marked))
|
||||
.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)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -840,7 +895,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
|
|||
marking: Marking::None,
|
||||
},
|
||||
),
|
||||
Witness(3u64.into(), 5),
|
||||
Witness(3u64.into(), 4),
|
||||
];
|
||||
let mut tree = new_tree(100);
|
||||
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),
|
||||
Witness(Position::from(3), 1),
|
||||
Witness(Position::from(3), 0),
|
||||
];
|
||||
let mut tree = new_tree(100);
|
||||
assert_eq!(
|
||||
|
@ -918,8 +973,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
|
|||
marking: Marking::None,
|
||||
},
|
||||
),
|
||||
Rewind,
|
||||
Rewind,
|
||||
Rewind(2),
|
||||
Witness(Position::from(7), 2),
|
||||
];
|
||||
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,
|
||||
},
|
||||
),
|
||||
Witness(Position::from(2), 2),
|
||||
Witness(Position::from(2), 1),
|
||||
];
|
||||
let mut tree = new_tree(100);
|
||||
assert_eq!(
|
||||
|
@ -966,40 +1020,43 @@ pub fn check_checkpoint_rewind<C: TestCheckpoint, T: Tree<String, C>, F: Fn(usiz
|
|||
new_tree: F,
|
||||
) {
|
||||
let mut t = new_tree(100);
|
||||
assert!(!t.rewind());
|
||||
assert!(!t.rewind(0));
|
||||
|
||||
let mut t = new_tree(100);
|
||||
t.assert_checkpoint(1);
|
||||
assert!(t.rewind());
|
||||
assert!(t.rewind(0));
|
||||
assert!(!t.rewind(1));
|
||||
|
||||
let mut t = new_tree(100);
|
||||
t.append("a".to_string(), Retention::Ephemeral);
|
||||
t.assert_checkpoint(1);
|
||||
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());
|
||||
|
||||
let mut t = new_tree(100);
|
||||
t.append("a".to_string(), Retention::Marked);
|
||||
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);
|
||||
t.append("a".to_string(), Retention::Marked);
|
||||
t.assert_checkpoint(1);
|
||||
t.append("a".to_string(), Retention::Ephemeral);
|
||||
assert!(t.rewind());
|
||||
assert!(t.rewind(0));
|
||||
assert_eq!(Some(Position::from(0)), t.current_position());
|
||||
|
||||
let mut t = new_tree(100);
|
||||
t.append("a".to_string(), Retention::Ephemeral);
|
||||
t.assert_checkpoint(1);
|
||||
t.assert_checkpoint(2);
|
||||
assert!(t.rewind());
|
||||
assert!(t.rewind(1));
|
||||
t.append("b".to_string(), Retention::Ephemeral);
|
||||
assert!(t.rewind());
|
||||
assert!(t.rewind(0));
|
||||
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) {
|
||||
|
@ -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);
|
||||
tree.append("e".to_string(), Retention::Marked);
|
||||
tree.assert_checkpoint(1);
|
||||
assert!(tree.rewind());
|
||||
assert!(tree.rewind(0));
|
||||
assert!(tree.remove_mark(0u64.into()));
|
||||
|
||||
// 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![
|
||||
append_str("x", Retention::Marked),
|
||||
Checkpoint(C::from_u64(1)),
|
||||
Rewind,
|
||||
Rewind(0),
|
||||
unmark(0),
|
||||
],
|
||||
vec![
|
||||
append_str("d", Retention::Marked),
|
||||
Checkpoint(C::from_u64(1)),
|
||||
unmark(0),
|
||||
Rewind,
|
||||
Rewind(0),
|
||||
unmark(0),
|
||||
],
|
||||
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(2)),
|
||||
unmark(0),
|
||||
Rewind,
|
||||
Rewind,
|
||||
Rewind(0),
|
||||
Rewind(1),
|
||||
],
|
||||
vec![
|
||||
append_str("s", Retention::Marked),
|
||||
append_str("m", Retention::Ephemeral),
|
||||
Checkpoint(C::from_u64(1)),
|
||||
unmark(0),
|
||||
Rewind,
|
||||
Rewind(0),
|
||||
unmark(0),
|
||||
unmark(0),
|
||||
],
|
||||
vec![
|
||||
append_str("a", Retention::Marked),
|
||||
Checkpoint(C::from_u64(1)),
|
||||
Rewind,
|
||||
Rewind(0),
|
||||
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)),
|
||||
unmark(0),
|
||||
Checkpoint(C::from_u64(2)),
|
||||
Rewind,
|
||||
Rewind(0),
|
||||
append_str("b", Retention::Ephemeral),
|
||||
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),
|
||||
Checkpoint(C::from_u64(1)),
|
||||
Checkpoint(C::from_u64(2)),
|
||||
Rewind,
|
||||
Rewind(1),
|
||||
append_str("a", Retention::Ephemeral),
|
||||
unmark(0),
|
||||
witness(0, 1),
|
||||
|
|
Loading…
Reference in New Issue