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
|
## 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.
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue