Merge pull request #21 from zcash/always_rewind_unless_empty
Rewinds are no longer blocked by existing witnesses.
This commit is contained in:
commit
5707d6ac09
|
@ -10,3 +10,5 @@ cc aa498d80d63c58720a913f2edcc6ba19da462f7a824f5be906e63c7ca1566076 # shrinks to
|
||||||
cc 7cebce1664b804d55e259c466ebcc140c4a3708f4ec49ed865d6009736b0a568 # shrinks to ops = [Append("d"), Checkpoint, Witness, Unwitness("d"), Rewind, Unwitness("d")]
|
cc 7cebce1664b804d55e259c466ebcc140c4a3708f4ec49ed865d6009736b0a568 # shrinks to ops = [Append("d"), Checkpoint, Witness, Unwitness("d"), Rewind, Unwitness("d")]
|
||||||
cc 9078a200bceb14aa2f78743f545dab9719db29ddbbb5f1f67fae01805da90f4b # shrinks to ops = [Append("a"), Unwitness("a"), Checkpoint, Checkpoint, Rewind, Append("a"), Rewind, Append("a")]
|
cc 9078a200bceb14aa2f78743f545dab9719db29ddbbb5f1f67fae01805da90f4b # shrinks to ops = [Append("a"), Unwitness("a"), Checkpoint, Checkpoint, Rewind, Append("a"), Rewind, Append("a")]
|
||||||
cc 20e43678bbf38f4c40fdd9e6f5bbc2b8eebcb1756b599f443f3268d6cf8e9d22 # shrinks to ops = [Append("o"), Checkpoint, Witness, Checkpoint, Unwitness("o"), Rewind, Rewind]
|
cc 20e43678bbf38f4c40fdd9e6f5bbc2b8eebcb1756b599f443f3268d6cf8e9d22 # shrinks to ops = [Append("o"), Checkpoint, Witness, Checkpoint, Unwitness("o"), Rewind, Rewind]
|
||||||
|
cc 2b82b514115d3ddb4990853b0178e997632226be6b8f7c9cf07e94a3e9f41690 # shrinks to ops = [Checkpoint, Witness, Rewind, Unwitness(Position(0), "x"), Checkpoint, Rewind, Checkpoint, Unwitness(Position(0), "x"), Unwitness(Position(0), "x"), Witness, Unwitness(Position(0), "x"), Append("x"), Append("b"), Authpath(Position(0), "x"), Authpath(Position(0), "x"), Rewind, Rewind, Authpath(Position(0), "x"), Witness, Rewind, Witness, Rewind, Append("x"), Rewind, Authpath(Position(0), "x"), Checkpoint, Authpath(Position(0), "x"), Witness, Checkpoint, Witness, Checkpoint, Rewind, Unwitness(Position(0), "x"), Unwitness(Position(0), "x"), Rewind, Witness, Rewind, Authpath(Position(0), "x"), Unwitness(Position(0), "x"), Authpath(Position(0), "x"), Witness, Witness, Checkpoint, Rewind, Checkpoint]
|
||||||
|
cc 248258b4e2fd558b56611179f6c8493326a8a35bd3fd3ffb55634c14f6fe417a # shrinks to ops = [Append(SipHashable(0)), Checkpoint, Witness, Rewind, Rewind, Authpath(Position(0), SipHashable(0)), Authpath(Position(0), SipHashable(0)), Append(SipHashable(0)), Witness, Authpath(Position(0), SipHashable(0)), Unwitness(Position(0), SipHashable(0)), Unwitness(Position(0), SipHashable(0)), Authpath(Position(0), SipHashable(0)), Authpath(Position(0), SipHashable(0)), Unwitness(Position(0), SipHashable(0)), Witness, Rewind, Checkpoint, Authpath(Position(0), SipHashable(0)), Witness, Rewind, Authpath(Position(0), SipHashable(0)), Authpath(Position(0), SipHashable(0)), Unwitness(Position(0), SipHashable(0)), Rewind, Append(SipHashable(0)), Witness, Authpath(Position(1), SipHashable(0)), Witness, Witness, Unwitness(Position(3), SipHashable(0)), Append(SipHashable(0)), Checkpoint, Append(SipHashable(0)), Witness, Witness, Checkpoint, Unwitness(Position(5), SipHashable(0)), Unwitness(Position(1), SipHashable(0)), Append(SipHashable(0)), Checkpoint, Authpath(Position(2), SipHashable(0)), Witness, Append(SipHashable(0)), Unwitness(Position(6), SipHashable(0)), Authpath(Position(2), SipHashable(0)), Witness, Unwitness(Position(5), SipHashable(0)), Checkpoint, Rewind, Unwitness(Position(1), SipHashable(0)), Checkpoint, Rewind, Witness, Checkpoint, Unwitness(Position(5), SipHashable(0)), Authpath(Position(2), SipHashable(0)), Append(SipHashable(0)), Checkpoint, Append(SipHashable(0)), Checkpoint, Append(SipHashable(0)), Unwitness(Position(4), SipHashable(0)), Rewind, Append(SipHashable(7)), Checkpoint, Append(SipHashable(9)), Authpath(Position(5), SipHashable(0)), Checkpoint, Authpath(Position(10), SipHashable(7)), Rewind, Checkpoint, Rewind]
|
||||||
|
|
|
@ -1050,45 +1050,35 @@ impl<H: Hashable + Ord + Clone, const DEPTH: u8> Tree<H> for BridgeTree<H, DEPTH
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewinds the tree state to the previous checkpoint. This function will
|
/// Rewinds the tree state to the previous checkpoint. This function will
|
||||||
/// fail and return false if there is no previous checkpoint or in the event
|
/// return false and leave the tree unmodified if no checkpoints exist.
|
||||||
/// witness data would be destroyed in the process.
|
|
||||||
fn rewind(&mut self) -> bool {
|
fn rewind(&mut self) -> bool {
|
||||||
match self.checkpoints.pop() {
|
match self.checkpoints.pop() {
|
||||||
Some(mut c) => {
|
Some(mut c) => {
|
||||||
if self.saved.values().any(|saved_idx| {
|
// drop witnessed values at and above the checkpoint height;
|
||||||
c.bridges_len == 0
|
// we will re-witness if necessary.
|
||||||
|| (!c.is_witnessed && *saved_idx >= c.bridges_len - 1)
|
self.saved
|
||||||
|| (c.is_witnessed && *saved_idx >= c.bridges_len)
|
.retain(|_, saved_idx| *saved_idx + 1 < c.bridges_len);
|
||||||
}) {
|
self.bridges.truncate(c.bridges_len);
|
||||||
// there is a witnessed value at a later position, or the
|
self.saved.append(&mut c.forgotten);
|
||||||
// current position was witnessed since the checkpoint was
|
|
||||||
// created, so we restore the removed checkpoint and return
|
|
||||||
// failure
|
|
||||||
self.checkpoints.push(c);
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
self.bridges.truncate(c.bridges_len);
|
|
||||||
self.saved.append(&mut c.forgotten);
|
|
||||||
|
|
||||||
let was_duplicate_checkpoint = self
|
let was_duplicate_checkpoint = self
|
||||||
.checkpoints
|
.checkpoints
|
||||||
.last()
|
.last()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|c0| c0.bridges_len == c.bridges_len);
|
.any(|c0| c0.bridges_len == c.bridges_len);
|
||||||
let len = self.bridges.len();
|
let len = self.bridges.len();
|
||||||
if c.is_witnessed {
|
if c.is_witnessed {
|
||||||
// if the checkpointed state was witnessed, we need to
|
// if the checkpointed state was witnessed, we need to
|
||||||
// restore the witness, as the successor bridge will have
|
// restore the witness, as the successor bridge will have
|
||||||
// been removed by truncation.
|
// been removed by truncation.
|
||||||
self.witness();
|
self.witness();
|
||||||
} else if len > 0 && was_duplicate_checkpoint {
|
} else if len > 0 && was_duplicate_checkpoint {
|
||||||
// if the checkpoint was a duplicate, we need to create
|
// if the checkpoint was a duplicate, we need to create
|
||||||
// a successor so that future appends do not mutate the
|
// a successor so that future appends do not mutate the
|
||||||
// state at the tip.
|
// state at the tip.
|
||||||
self.bridges.push(self.bridges[len - 1].successor(false));
|
self.bridges.push(self.bridges[len - 1].successor(false));
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
|
@ -1198,11 +1188,11 @@ mod tests {
|
||||||
t.witness();
|
t.witness();
|
||||||
t.append(&"b".to_string());
|
t.append(&"b".to_string());
|
||||||
t.append(&"c".to_string());
|
t.append(&"c".to_string());
|
||||||
assert!(!t.rewind(), "Rewind is expected to fail.");
|
|
||||||
assert!(
|
assert!(
|
||||||
t.drop_oldest_checkpoint(),
|
t.drop_oldest_checkpoint(),
|
||||||
"Checkpoint drop is expected to succeed"
|
"Checkpoint drop is expected to succeed"
|
||||||
);
|
);
|
||||||
|
assert!(!t.rewind(), "Rewind is expected to fail.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
104
src/lib.rs
104
src/lib.rs
|
@ -270,12 +270,7 @@ pub trait Tree<H>: Frontier<H> {
|
||||||
/// that checkpoint record. If there are multiple checkpoints at a given
|
/// that checkpoint record. If there are multiple checkpoints at a given
|
||||||
/// tree state, the tree state will not be altered until all checkpoints
|
/// tree state, the tree state will not be altered until all checkpoints
|
||||||
/// at that tree state have been removed using `rewind`. This function
|
/// at that tree state have been removed using `rewind`. This function
|
||||||
/// will fail and return false if there is no previous checkpoint or in
|
/// return false and leave the tree unmodified if no checkpoints exist.
|
||||||
/// the event witness data would be destroyed in the process.
|
|
||||||
///
|
|
||||||
/// In the case that this method returns `false`, the user should have
|
|
||||||
/// explicitly called `remove_witness` for each witnessed leaf marked
|
|
||||||
/// since the last checkpoint.
|
|
||||||
fn rewind(&mut self) -> bool;
|
fn rewind(&mut self) -> bool;
|
||||||
|
|
||||||
/// Start a recording of append operations performed on a tree.
|
/// Start a recording of append operations performed on a tree.
|
||||||
|
@ -371,7 +366,6 @@ pub(crate) mod tests {
|
||||||
t.checkpoint();
|
t.checkpoint();
|
||||||
t.witness();
|
t.witness();
|
||||||
t.append(&"a".to_string());
|
t.append(&"a".to_string());
|
||||||
assert!(!t.rewind());
|
|
||||||
t.append(&"a".to_string());
|
t.append(&"a".to_string());
|
||||||
t.append(&"a".to_string());
|
t.append(&"a".to_string());
|
||||||
assert_eq!(t.root(), "aaaa____________");
|
assert_eq!(t.root(), "aaaa____________");
|
||||||
|
@ -599,17 +593,19 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
pub(crate) fn check_checkpoint_rewind<T: Tree<String>, F: Fn(usize) -> T>(new_tree: F) {
|
pub(crate) fn check_checkpoint_rewind<T: Tree<String>, F: Fn(usize) -> T>(new_tree: F) {
|
||||||
let mut t = new_tree(100);
|
let mut t = new_tree(100);
|
||||||
t.append(&"a".to_string());
|
|
||||||
t.checkpoint();
|
|
||||||
t.append(&"b".to_string());
|
|
||||||
t.witness();
|
|
||||||
assert!(!t.rewind());
|
assert!(!t.rewind());
|
||||||
|
|
||||||
|
let mut t = new_tree(100);
|
||||||
|
t.checkpoint();
|
||||||
|
assert!(t.rewind());
|
||||||
|
|
||||||
let mut t = new_tree(100);
|
let mut t = new_tree(100);
|
||||||
t.append(&"a".to_string());
|
t.append(&"a".to_string());
|
||||||
t.checkpoint();
|
t.checkpoint();
|
||||||
|
t.append(&"b".to_string());
|
||||||
t.witness();
|
t.witness();
|
||||||
assert!(!t.rewind());
|
assert!(t.rewind());
|
||||||
|
assert_eq!(Some((Position::from(0), "a".to_string())), t.current_leaf());
|
||||||
|
|
||||||
let mut t = new_tree(100);
|
let mut t = new_tree(100);
|
||||||
t.append(&"a".to_string());
|
t.append(&"a".to_string());
|
||||||
|
@ -622,7 +618,8 @@ pub(crate) mod tests {
|
||||||
t.checkpoint();
|
t.checkpoint();
|
||||||
t.witness();
|
t.witness();
|
||||||
t.append(&"a".to_string());
|
t.append(&"a".to_string());
|
||||||
assert!(!t.rewind());
|
assert!(t.rewind());
|
||||||
|
assert_eq!(Some((Position::from(0), "a".to_string())), t.current_leaf());
|
||||||
|
|
||||||
let mut t = new_tree(100);
|
let mut t = new_tree(100);
|
||||||
t.append(&"a".to_string());
|
t.append(&"a".to_string());
|
||||||
|
@ -636,6 +633,20 @@ pub(crate) mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_rewind_remove_witness<T: Tree<String>, F: Fn(usize) -> T>(new_tree: F) {
|
pub(crate) fn check_rewind_remove_witness<T: Tree<String>, F: Fn(usize) -> T>(new_tree: F) {
|
||||||
|
let mut tree = new_tree(100);
|
||||||
|
tree.append(&"e".to_string());
|
||||||
|
tree.witness();
|
||||||
|
tree.checkpoint();
|
||||||
|
assert!(tree.rewind());
|
||||||
|
assert!(tree.remove_witness(0usize.into(), &"e".to_string()));
|
||||||
|
|
||||||
|
let mut tree = new_tree(100);
|
||||||
|
tree.append(&"e".to_string());
|
||||||
|
tree.checkpoint();
|
||||||
|
tree.witness();
|
||||||
|
assert!(tree.rewind());
|
||||||
|
assert!(!tree.remove_witness(0usize.into(), &"e".to_string()));
|
||||||
|
|
||||||
let mut tree = new_tree(100);
|
let mut tree = new_tree(100);
|
||||||
tree.append(&"e".to_string());
|
tree.append(&"e".to_string());
|
||||||
tree.witness();
|
tree.witness();
|
||||||
|
@ -657,7 +668,7 @@ pub(crate) mod tests {
|
||||||
assert!(!tree.remove_witness(0usize.into(), &"a".to_string()));
|
assert!(!tree.remove_witness(0usize.into(), &"a".to_string()));
|
||||||
tree.checkpoint();
|
tree.checkpoint();
|
||||||
assert!(tree.witness().is_some());
|
assert!(tree.witness().is_some());
|
||||||
assert!(!tree.rewind());
|
assert!(tree.rewind());
|
||||||
|
|
||||||
let mut tree = new_tree(100);
|
let mut tree = new_tree(100);
|
||||||
tree.append(&"a".to_string());
|
tree.append(&"a".to_string());
|
||||||
|
@ -671,50 +682,69 @@ pub(crate) mod tests {
|
||||||
// test framework itself previously did not correctly handle
|
// test framework itself previously did not correctly handle
|
||||||
// chain state restoration.
|
// chain state restoration.
|
||||||
|
|
||||||
let ops = vec![
|
fn append(x: &str) -> Operation<String> {
|
||||||
Append("a".to_string()),
|
Append(x.to_string())
|
||||||
Unwitness(0usize.into(), "a".to_string()),
|
}
|
||||||
Checkpoint,
|
|
||||||
Witness,
|
fn unwitness(pos: usize, x: &str) -> Operation<String> {
|
||||||
Rewind,
|
Unwitness(Position::from(pos), x.to_string())
|
||||||
];
|
}
|
||||||
|
|
||||||
|
let ops = vec![append("x"), Checkpoint, Witness, Rewind, unwitness(0, "x")];
|
||||||
let result = check_operations(ops);
|
let result = check_operations(ops);
|
||||||
assert!(matches!(result, Ok(())), "Test failed: {:?}", result);
|
assert!(
|
||||||
|
matches!(result, Ok(())),
|
||||||
|
"Reference/Test mismatch: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Append("s".to_string()),
|
append("d"),
|
||||||
Witness,
|
|
||||||
Append("m".to_string()),
|
|
||||||
Checkpoint,
|
Checkpoint,
|
||||||
Unwitness(0usize.into(), "s".to_string()),
|
Witness,
|
||||||
|
unwitness(0, "d"),
|
||||||
Rewind,
|
Rewind,
|
||||||
Unwitness(0usize.into(), "s".to_string()),
|
unwitness(0, "d"),
|
||||||
];
|
];
|
||||||
let result = check_operations(ops);
|
let result = check_operations(ops);
|
||||||
assert!(matches!(result, Ok(())), "Test failed: {:?}", result);
|
assert!(
|
||||||
|
matches!(result, Ok(())),
|
||||||
|
"Reference/Test mismatch: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Append("d".to_string()),
|
append("o"),
|
||||||
Checkpoint,
|
Checkpoint,
|
||||||
Witness,
|
Witness,
|
||||||
Unwitness(0usize.into(), "d".to_string()),
|
Checkpoint,
|
||||||
|
unwitness(0, "o"),
|
||||||
|
Rewind,
|
||||||
Rewind,
|
Rewind,
|
||||||
Unwitness(0usize.into(), "d".to_string()),
|
|
||||||
];
|
];
|
||||||
let result = check_operations(ops);
|
let result = check_operations(ops);
|
||||||
assert!(matches!(result, Ok(())), "Test failed: {:?}", result);
|
assert!(
|
||||||
|
matches!(result, Ok(())),
|
||||||
|
"Reference/Test mismatch: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
Append("o".to_string()),
|
append("s"),
|
||||||
Checkpoint,
|
|
||||||
Witness,
|
Witness,
|
||||||
|
append("m"),
|
||||||
Checkpoint,
|
Checkpoint,
|
||||||
Unwitness(0usize.into(), "o".to_string()),
|
unwitness(0, "s"),
|
||||||
Rewind,
|
|
||||||
Rewind,
|
Rewind,
|
||||||
|
unwitness(0, "s"),
|
||||||
|
unwitness(0, "m"),
|
||||||
];
|
];
|
||||||
let result = check_operations(ops);
|
let result = check_operations(ops);
|
||||||
assert!(matches!(result, Ok(())), "Test failed: {:?}", result);
|
assert!(
|
||||||
|
matches!(result, Ok(())),
|
||||||
|
"Reference/Test mismatch: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
//! Sample implementation of the Tree interface.
|
||||||
use super::{Altitude, Frontier, Hashable, Position, Recording, Tree};
|
use super::{Altitude, Frontier, Hashable, Position, Recording, Tree};
|
||||||
/// Sample implementation of the Tree interface.
|
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TreeState<H: Hashable> {
|
pub struct TreeState<H: Hashable> {
|
||||||
|
@ -144,7 +143,7 @@ impl<H: Hashable + PartialEq + Clone> TreeState<H> {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CompleteTree<H: Hashable> {
|
pub struct CompleteTree<H: Hashable> {
|
||||||
tree_state: TreeState<H>,
|
tree_state: TreeState<H>,
|
||||||
checkpoints: Vec<(TreeState<H>, bool)>,
|
checkpoints: Vec<TreeState<H>>,
|
||||||
max_checkpoints: usize,
|
max_checkpoints: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,36 +227,18 @@ impl<H: Hashable + PartialEq + Clone> Tree<H> for CompleteTree<H> {
|
||||||
/// Marks the current tree state as a checkpoint if it is not already a
|
/// Marks the current tree state as a checkpoint if it is not already a
|
||||||
/// checkpoint.
|
/// checkpoint.
|
||||||
fn checkpoint(&mut self) {
|
fn checkpoint(&mut self) {
|
||||||
let is_witnessed = self
|
self.checkpoints.push(self.tree_state.clone());
|
||||||
.tree_state
|
|
||||||
.current_leaf()
|
|
||||||
.into_iter()
|
|
||||||
.any(|(p, l)| self.tree_state.is_witnessed(p, &l));
|
|
||||||
self.checkpoints
|
|
||||||
.push((self.tree_state.clone(), is_witnessed));
|
|
||||||
if self.checkpoints.len() > self.max_checkpoints {
|
if self.checkpoints.len() > self.max_checkpoints {
|
||||||
self.drop_oldest_checkpoint();
|
self.drop_oldest_checkpoint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewinds the tree state to the previous checkpoint. This function will
|
/// Rewinds the tree state to the previous checkpoint. This function will
|
||||||
/// fail and return false if there is no previous checkpoint or in the event
|
/// return false and leave the tree unmodified if no checkpoints exist.
|
||||||
/// witness data would be destroyed in the process.
|
|
||||||
fn rewind(&mut self) -> bool {
|
fn rewind(&mut self) -> bool {
|
||||||
if let Some((checkpointed_state, is_witnessed)) = self.checkpoints.pop() {
|
if let Some(checkpointed_state) = self.checkpoints.pop() {
|
||||||
// if there are any witnessed leaves in the current tree state
|
self.tree_state = checkpointed_state;
|
||||||
// that would be removed, we don't rewind
|
true
|
||||||
if self.tree_state.witnesses.iter().any(|&(pos, _)| {
|
|
||||||
let offset: usize = (pos + 1).try_into().unwrap();
|
|
||||||
offset > checkpointed_state.current_offset
|
|
||||||
|| (offset == checkpointed_state.current_offset && !is_witnessed)
|
|
||||||
}) {
|
|
||||||
self.checkpoints.push((checkpointed_state, is_witnessed));
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
self.tree_state = checkpointed_state;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue