Add `shardtree` witness operation & implement property tests.
This commit is contained in:
parent
a7bb8bb749
commit
0cb1cec21f
|
@ -541,7 +541,7 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
])
|
||||
);
|
||||
|
||||
tree.append("b".to_string(), Retention::Ephemeral);
|
||||
tree.append("b".to_string(), Ephemeral);
|
||||
assert_eq!(
|
||||
tree.witness(0.into(), 0),
|
||||
Some(vec![
|
||||
|
@ -552,7 +552,7 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
])
|
||||
);
|
||||
|
||||
tree.append("c".to_string(), Retention::Marked);
|
||||
tree.append("c".to_string(), Marked);
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(2), 0),
|
||||
Some(vec![
|
||||
|
@ -563,7 +563,7 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
])
|
||||
);
|
||||
|
||||
tree.append("d".to_string(), Retention::Ephemeral);
|
||||
tree.append("d".to_string(), Ephemeral);
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(2), 0),
|
||||
Some(vec![
|
||||
|
@ -574,7 +574,7 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
])
|
||||
);
|
||||
|
||||
tree.append("e".to_string(), Retention::Ephemeral);
|
||||
tree.append("e".to_string(), Ephemeral);
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(2), 0),
|
||||
Some(vec![
|
||||
|
@ -586,12 +586,12 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
);
|
||||
|
||||
let mut tree = new_tree(100);
|
||||
tree.append("a".to_string(), Retention::Marked);
|
||||
tree.append("a".to_string(), Marked);
|
||||
for c in 'b'..'g' {
|
||||
tree.append(c.to_string(), Retention::Ephemeral);
|
||||
tree.append(c.to_string(), Ephemeral);
|
||||
}
|
||||
tree.append("g".to_string(), Retention::Marked);
|
||||
tree.append("h".to_string(), Retention::Ephemeral);
|
||||
tree.append("g".to_string(), Marked);
|
||||
tree.append("h".to_string(), Ephemeral);
|
||||
|
||||
assert_eq!(
|
||||
tree.witness(0.into(), 0),
|
||||
|
@ -604,13 +604,13 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
);
|
||||
|
||||
let mut tree = new_tree(100);
|
||||
tree.append("a".to_string(), Retention::Marked);
|
||||
tree.append("b".to_string(), Retention::Ephemeral);
|
||||
tree.append("c".to_string(), Retention::Ephemeral);
|
||||
tree.append("d".to_string(), Retention::Marked);
|
||||
tree.append("e".to_string(), Retention::Marked);
|
||||
tree.append("f".to_string(), Retention::Marked);
|
||||
tree.append("g".to_string(), Retention::Ephemeral);
|
||||
tree.append("a".to_string(), Marked);
|
||||
tree.append("b".to_string(), Ephemeral);
|
||||
tree.append("c".to_string(), Ephemeral);
|
||||
tree.append("d".to_string(), Marked);
|
||||
tree.append("e".to_string(), Marked);
|
||||
tree.append("f".to_string(), Marked);
|
||||
tree.append("g".to_string(), Ephemeral);
|
||||
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(5), 0),
|
||||
|
@ -624,10 +624,10 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
|
||||
let mut tree = new_tree(100);
|
||||
for c in 'a'..'k' {
|
||||
tree.append(c.to_string(), Retention::Ephemeral);
|
||||
assert!(tree.append(c.to_string(), Ephemeral));
|
||||
}
|
||||
tree.append('k'.to_string(), Retention::Marked);
|
||||
tree.append('l'.to_string(), Retention::Ephemeral);
|
||||
assert!(tree.append('k'.to_string(), Marked));
|
||||
assert!(tree.append('l'.to_string(), Ephemeral));
|
||||
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(10), 0),
|
||||
|
@ -642,18 +642,18 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
let mut tree = new_tree(100);
|
||||
assert!(tree.append(
|
||||
'a'.to_string(),
|
||||
Retention::Checkpoint {
|
||||
Checkpoint {
|
||||
id: 1,
|
||||
is_marked: true
|
||||
}
|
||||
));
|
||||
assert!(tree.rewind());
|
||||
for c in 'b'..'e' {
|
||||
tree.append(c.to_string(), Retention::Ephemeral);
|
||||
tree.append(c.to_string(), Ephemeral);
|
||||
}
|
||||
tree.append("e".to_string(), Retention::Marked);
|
||||
tree.append("e".to_string(), Marked);
|
||||
for c in 'f'..'i' {
|
||||
tree.append(c.to_string(), Retention::Ephemeral);
|
||||
tree.append(c.to_string(), Ephemeral);
|
||||
}
|
||||
assert_eq!(
|
||||
tree.witness(0.into(), 0),
|
||||
|
@ -666,20 +666,20 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
);
|
||||
|
||||
let mut tree = new_tree(100);
|
||||
tree.append('a'.to_string(), Retention::Ephemeral);
|
||||
tree.append('b'.to_string(), Retention::Ephemeral);
|
||||
tree.append('c'.to_string(), Retention::Marked);
|
||||
tree.append('d'.to_string(), Retention::Ephemeral);
|
||||
tree.append('e'.to_string(), Retention::Ephemeral);
|
||||
tree.append('f'.to_string(), Retention::Ephemeral);
|
||||
tree.append('a'.to_string(), Ephemeral);
|
||||
tree.append('b'.to_string(), Ephemeral);
|
||||
tree.append('c'.to_string(), Marked);
|
||||
tree.append('d'.to_string(), Ephemeral);
|
||||
tree.append('e'.to_string(), Ephemeral);
|
||||
tree.append('f'.to_string(), Ephemeral);
|
||||
assert!(tree.append(
|
||||
'g'.to_string(),
|
||||
Retention::Checkpoint {
|
||||
Checkpoint {
|
||||
id: 1,
|
||||
is_marked: true
|
||||
}
|
||||
));
|
||||
tree.append('h'.to_string(), Retention::Ephemeral);
|
||||
tree.append('h'.to_string(), Ephemeral);
|
||||
assert!(tree.rewind());
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(2), 0),
|
||||
|
@ -692,18 +692,18 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
);
|
||||
|
||||
let mut tree = new_tree(100);
|
||||
tree.append('a'.to_string(), Retention::Ephemeral);
|
||||
tree.append('b'.to_string(), Retention::Marked);
|
||||
tree.append('a'.to_string(), Ephemeral);
|
||||
tree.append('b'.to_string(), Marked);
|
||||
assert_eq!(tree.witness(Position::from(0), 0), None);
|
||||
|
||||
let mut tree = new_tree(100);
|
||||
for c in 'a'..'m' {
|
||||
tree.append(c.to_string(), Retention::Ephemeral);
|
||||
tree.append(c.to_string(), Ephemeral);
|
||||
}
|
||||
tree.append('m'.to_string(), Retention::Marked);
|
||||
tree.append('n'.to_string(), Retention::Marked);
|
||||
tree.append('o'.to_string(), Retention::Ephemeral);
|
||||
tree.append('p'.to_string(), Retention::Ephemeral);
|
||||
tree.append('m'.to_string(), Marked);
|
||||
tree.append('n'.to_string(), Marked);
|
||||
tree.append('o'.to_string(), Ephemeral);
|
||||
tree.append('p'.to_string(), Ephemeral);
|
||||
|
||||
assert_eq!(
|
||||
tree.witness(Position::from(12), 0),
|
||||
|
@ -717,9 +717,9 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
|
||||
let ops = ('a'..='l')
|
||||
.into_iter()
|
||||
.map(|c| Append(c.to_string(), Retention::Marked))
|
||||
.chain(Some(Append('m'.to_string(), Retention::Ephemeral)))
|
||||
.chain(Some(Append('n'.to_string(), Retention::Ephemeral)))
|
||||
.map(|c| Append(c.to_string(), Marked))
|
||||
.chain(Some(Append('m'.to_string(), Ephemeral)))
|
||||
.chain(Some(Append('n'.to_string(), Ephemeral)))
|
||||
.chain(Some(Witness(11usize.into(), 0)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -736,6 +736,153 @@ pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) ->
|
|||
]
|
||||
))
|
||||
);
|
||||
|
||||
let ops = vec![
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("b".to_string(), Ephemeral),
|
||||
Append("c".to_string(), Ephemeral),
|
||||
Append(
|
||||
"d".to_string(),
|
||||
Checkpoint {
|
||||
id: 1,
|
||||
is_marked: true,
|
||||
},
|
||||
),
|
||||
Append("e".to_string(), Marked),
|
||||
Operation::Checkpoint(2),
|
||||
Append(
|
||||
"f".to_string(),
|
||||
Checkpoint {
|
||||
id: 3,
|
||||
is_marked: false,
|
||||
},
|
||||
),
|
||||
Append(
|
||||
"g".to_string(),
|
||||
Checkpoint {
|
||||
id: 4,
|
||||
is_marked: false,
|
||||
},
|
||||
),
|
||||
Append(
|
||||
"h".to_string(),
|
||||
Checkpoint {
|
||||
id: 5,
|
||||
is_marked: false,
|
||||
},
|
||||
),
|
||||
Witness(3usize.into(), 5),
|
||||
];
|
||||
let mut tree = new_tree(100);
|
||||
assert_eq!(
|
||||
Operation::apply_all(&ops, &mut tree),
|
||||
Some((
|
||||
Position::from(3),
|
||||
vec![
|
||||
"c".to_string(),
|
||||
"ab".to_string(),
|
||||
"____".to_string(),
|
||||
"________".to_string()
|
||||
]
|
||||
))
|
||||
);
|
||||
let ops = vec![
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append(
|
||||
"a".to_string(),
|
||||
Checkpoint {
|
||||
id: 1,
|
||||
is_marked: true,
|
||||
},
|
||||
),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append(
|
||||
"a".to_string(),
|
||||
Checkpoint {
|
||||
id: 2,
|
||||
is_marked: false,
|
||||
},
|
||||
),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Witness(Position(3), 1),
|
||||
];
|
||||
let mut tree = new_tree(100);
|
||||
assert_eq!(
|
||||
Operation::apply_all(&ops, &mut tree),
|
||||
Some((
|
||||
Position::from(3),
|
||||
vec![
|
||||
"a".to_string(),
|
||||
"aa".to_string(),
|
||||
"aaaa".to_string(),
|
||||
"________".to_string()
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
let ops = vec![
|
||||
Append("a".to_string(), Marked),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Operation::Checkpoint(1),
|
||||
Append("a".to_string(), Marked),
|
||||
Operation::Checkpoint(2),
|
||||
Operation::Checkpoint(3),
|
||||
Append(
|
||||
"a".to_string(),
|
||||
Checkpoint {
|
||||
id: 4,
|
||||
is_marked: false,
|
||||
},
|
||||
),
|
||||
Rewind,
|
||||
Rewind,
|
||||
Witness(Position(7), 2),
|
||||
];
|
||||
let mut tree = new_tree(100);
|
||||
assert_eq!(Operation::apply_all(&ops, &mut tree), None);
|
||||
|
||||
let ops = vec![
|
||||
Append("a".to_string(), Marked),
|
||||
Append("a".to_string(), Ephemeral),
|
||||
Append(
|
||||
"a".to_string(),
|
||||
Checkpoint {
|
||||
id: 1,
|
||||
is_marked: true,
|
||||
},
|
||||
),
|
||||
Append(
|
||||
"a".to_string(),
|
||||
Checkpoint {
|
||||
id: 4,
|
||||
is_marked: false,
|
||||
},
|
||||
),
|
||||
Witness(Position(2), 2),
|
||||
];
|
||||
let mut tree = new_tree(100);
|
||||
assert_eq!(
|
||||
Operation::apply_all(&ops, &mut tree),
|
||||
Some((
|
||||
Position::from(2),
|
||||
vec![
|
||||
"_".to_string(),
|
||||
"aa".to_string(),
|
||||
"____".to_string(),
|
||||
"________".to_string()
|
||||
]
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
pub fn check_checkpoint_rewind<T: Tree<String, usize>, F: Fn(usize) -> T>(new_tree: F) {
|
||||
|
@ -821,15 +968,15 @@ pub fn check_rewind_remove_mark<T: Tree<String, usize>, F: Fn(usize) -> T>(new_t
|
|||
|
||||
// use a maximum number of checkpoints of 1
|
||||
let mut tree = new_tree(1);
|
||||
tree.append("e".to_string(), Retention::Marked);
|
||||
tree.checkpoint(1);
|
||||
assert!(tree.append("e".to_string(), Retention::Marked));
|
||||
assert!(tree.checkpoint(1));
|
||||
assert!(tree.marked_positions().contains(&0usize.into()));
|
||||
tree.append("f".to_string(), Retention::Ephemeral);
|
||||
assert!(tree.append("f".to_string(), Retention::Ephemeral));
|
||||
// simulate a spend of `e` at `f`
|
||||
assert!(tree.remove_mark(0usize.into()));
|
||||
// even though the mark has been staged for removal, it's not gone yet
|
||||
assert!(tree.marked_positions().contains(&0usize.into()));
|
||||
tree.checkpoint(2);
|
||||
assert!(tree.checkpoint(2));
|
||||
// the newest checkpoint will have caused the oldest to roll off, and
|
||||
// so the forgotten node will be unmarked
|
||||
assert!(!tree.marked_positions().contains(&0usize.into()));
|
||||
|
|
|
@ -380,7 +380,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn witness() {
|
||||
fn witnesses() {
|
||||
check_witnesses(|max_checkpoints| {
|
||||
CompleteTree::<String, usize, 4>::new(max_checkpoints, 0)
|
||||
});
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 38b4ca3c029291dfe2a6b5907c33a2e8ae7900f19c759c5db74b616ab19f6c5c # shrinks to ops = [Append(SipHashable(0), MC), Rewind, Rewind]
|
||||
cc f1a96f73b9f3ba2a2e4b5271037322150450c573b6c3a5ef34f71a540ff0fad2 # shrinks to ops = [Append("a", C), Rewind, Rewind]
|
||||
cc ad5f5d4276adea6e928376c6dc8c013745e60a8b507e6fa4e716f6c35477fe65 # shrinks to ops = [Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E), Append("a", E)]
|
||||
cc ab2690ff3cf593d2e7a78b2f56d76699e525391b87852bbf15315a1f36742f48 # shrinks to ops = [Append(SipHashable(0), C), Append(SipHashable(0), E), Append(SipHashable(0), E), Unmark(Position(0))]
|
||||
cc 1603359084b0c614a2ff9008036a5e10db9f32fb31b3333e59aaa517686e174d # shrinks to ops = [Append(SipHashable(0), Checkpoint { id: (), is_marked: false })]
|
||||
cc faaf929be4b27e652712b705e297bfe15c53767102516e56882d177ac6fc58d9 # shrinks to ops = [CurrentPosition, Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Checkpoint { id: (), is_marked: true }), Append("a", Marked), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Witness(Position(3), 5)]
|
||||
cc 8836c27ead7afb8d10092bdaeed33eb31007eaa47e3c3fca248cc00fbce772a3 # shrinks to ops = [Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral)]
|
||||
cc 2303f82f255c60d7bdd45e2d13ff526bdc1d7f5ce846a7c07b38ab7f255c0300 # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral)]
|
||||
cc cf1a33ef6df58bbf7cc199b8b1879c3a078e7784aa3edc0aba9ca03772bea5f2 # shrinks to ops = [Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Checkpoint(()), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Checkpoint(()), Rewind, Rewind, Rewind, Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Append(SipHashable(0), Checkpoint { id: (), is_marked: false }), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Checkpoint { id: (), is_marked: true }), Witness(Position(8), 2)]
|
||||
cc 544e027d994eaf7f97b1c8d9ee7b35522a64a610b1430d56d74ec947018b759d # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Checkpoint(()), Append("a", Marked), Checkpoint(()), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Rewind, Rewind, Witness(Position(7), 2)]
|
||||
cc 55d00b68a0f0a02f83ab53f18a29d16d0233153b69a01414a1622104e0eead31 # shrinks to ops = [Append("a", Marked), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Marked), Checkpoint(()), Checkpoint(()), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Witness(Position(0), 7)]
|
||||
cc 9dd966ff1ab66965c5b84153ae13f684258560cdd5e84c7deb24f724cb12aba7 # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Checkpoint { id: (), is_marked: true }), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Rewind, Rewind, Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Checkpoint(()), Witness(Position(2), 4)]
|
|
@ -1951,6 +1951,61 @@ impl<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Truncates the tree, discarding all information after the checkpoint at the specified depth.
|
||||
///
|
||||
/// This will also discard all checkpoints with depth <= the specified depth. Returns `true`
|
||||
/// if the truncation succeeds or has no effect, or `false` if no checkpoint exists at the
|
||||
/// specified depth.
|
||||
pub fn truncate_removing_checkpoint(&mut self, checkpoint_depth: usize) -> bool {
|
||||
if checkpoint_depth == 0 {
|
||||
true
|
||||
} else if self.checkpoints.len() > 1 {
|
||||
match self.checkpoint_at_depth(checkpoint_depth) {
|
||||
Some((checkpoint_id, c)) => {
|
||||
let checkpoint_id = checkpoint_id.clone();
|
||||
match c.tree_state {
|
||||
TreeState::Empty => {
|
||||
if (self
|
||||
.store
|
||||
.truncate(Address::from_parts(Self::subtree_level(), 0)))
|
||||
.is_err()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
self.checkpoints.split_off(&checkpoint_id);
|
||||
true
|
||||
}
|
||||
TreeState::AtPosition(position) => {
|
||||
let subtree_addr =
|
||||
Address::above_position(Self::subtree_level(), position);
|
||||
let replacement = self
|
||||
.store
|
||||
.get_shard(subtree_addr)
|
||||
.and_then(|s| s.truncate_to_position(position));
|
||||
match replacement {
|
||||
Some(truncated) => {
|
||||
if self.store.truncate(subtree_addr).is_err()
|
||||
|| self.store.put_shard(truncated).is_err()
|
||||
{
|
||||
false
|
||||
} else {
|
||||
self.checkpoints.split_off(&checkpoint_id);
|
||||
true
|
||||
}
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
} else {
|
||||
// do not remove the first checkpoint.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the root of any subtree of this tree rooted at the given address, with the overall
|
||||
/// tree truncated to the specified position.
|
||||
///
|
||||
|
@ -2109,6 +2164,68 @@ impl<
|
|||
|pos| self.root(Self::root_addr(), pos + 1),
|
||||
)
|
||||
}
|
||||
|
||||
/// Computes the witness for the leaf at the specified position.
|
||||
///
|
||||
/// Returns the witness as of the most recently appended leaf if `checkpoint_depth == 0`. Note
|
||||
/// that if the most recently appended leaf is also a checkpoint, this will return the same
|
||||
/// result as `checkpoint_depth == 1`.
|
||||
pub fn witness(
|
||||
&self,
|
||||
position: Position,
|
||||
checkpoint_depth: usize,
|
||||
) -> Result<Vec<H>, QueryError> {
|
||||
let max_leaf_position = self
|
||||
.max_leaf_position(checkpoint_depth)
|
||||
.and_then(|v| v.ok_or_else(|| QueryError::TreeIncomplete(vec![Self::root_addr()])))?;
|
||||
|
||||
if position > max_leaf_position {
|
||||
Err(QueryError::NotContained(Address::from_parts(
|
||||
Level::from(0),
|
||||
position.into(),
|
||||
)))
|
||||
} else {
|
||||
let subtree_addr = Address::above_position(Self::subtree_level(), position);
|
||||
|
||||
// compute the witness for the specified position up to the subtree root
|
||||
let mut witness = self.store.get_shard(subtree_addr).map_or_else(
|
||||
|| Err(QueryError::TreeIncomplete(vec![subtree_addr])),
|
||||
|subtree| subtree.witness(position, max_leaf_position + 1),
|
||||
)?;
|
||||
|
||||
// compute the remaining parts of the witness up to the root
|
||||
let root_addr = Self::root_addr();
|
||||
let mut cur_addr = subtree_addr;
|
||||
while cur_addr != root_addr {
|
||||
witness.push(self.root(cur_addr.sibling(), max_leaf_position + 1)?);
|
||||
cur_addr = cur_addr.parent();
|
||||
}
|
||||
|
||||
Ok(witness)
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a marked leaf at a position eligible to be pruned.
|
||||
///
|
||||
/// If the checkpoint associated with the specified identifier does not exist because the
|
||||
/// corresponding checkpoint would have been more than `max_checkpoints` deep, the removal
|
||||
/// is recorded as of the first existing checkpoint and the associated leaves will be pruned
|
||||
/// when that checkpoint is subsequently removed.
|
||||
pub fn remove_mark(&mut self, position: Position, as_of_checkpoint: &C) -> bool {
|
||||
if self.get_marked_leaf(position).is_some() {
|
||||
if let Some(checkpoint) = self.checkpoints.get_mut(as_of_checkpoint) {
|
||||
checkpoint.marks_removed.insert(position);
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some((_, checkpoint)) = self.checkpoints.iter_mut().next() {
|
||||
checkpoint.marks_removed.insert(position);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// We need an applicative functor for Result for this function so that we can correctly
|
||||
|
@ -2174,16 +2291,20 @@ pub mod testing {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
LocatedPrunableTree, LocatedTree, Node, PrunableTree, QueryError, ShardStore, ShardTree,
|
||||
Tree, EPHEMERAL, MARKED,
|
||||
IncompleteAt, LocatedPrunableTree, LocatedTree, Node, PrunableTree, QueryError, ShardStore,
|
||||
ShardTree, Tree, EPHEMERAL, MARKED,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use core::convert::Infallible;
|
||||
use incrementalmerkletree::{
|
||||
testing::{
|
||||
self, check_append, check_root_hashes, complete_tree::CompleteTree, CombinedTree,
|
||||
self, arb_operation, check_append, check_checkpoint_rewind, check_operations,
|
||||
check_rewind_remove_mark, check_root_hashes, check_witnesses,
|
||||
complete_tree::CompleteTree, CombinedTree, SipHashable,
|
||||
},
|
||||
Address, Hashable, Level, Position, Retention,
|
||||
};
|
||||
use proptest::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -2495,6 +2616,97 @@ mod tests {
|
|||
assert_eq!(complete.subtree.right_filled_root(), Ok("abcd".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shardtree_insertion() {
|
||||
let mut tree: ShardTree<String, usize, VecShardStore<String>, 4, 3> =
|
||||
ShardTree::new(vec![], 100, 0);
|
||||
assert_matches!(
|
||||
tree.batch_insert(
|
||||
Position::from(1),
|
||||
vec![
|
||||
("b".to_string(), Retention::Checkpoint { id: 1, is_marked: false }),
|
||||
("c".to_string(), Retention::Ephemeral),
|
||||
("d".to_string(), Retention::Marked),
|
||||
].into_iter()
|
||||
),
|
||||
Ok(Some((pos, incomplete))) if
|
||||
pos == Position::from(3) &&
|
||||
incomplete == vec![
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(0), 0),
|
||||
required_for_witness: true
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(1),
|
||||
Err(QueryError::TreeIncomplete(v)) if v == vec![Address::from_parts(Level::from(0), 0)]
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.batch_insert(
|
||||
Position::from(0),
|
||||
vec![
|
||||
("a".to_string(), Retention::Ephemeral),
|
||||
].into_iter()
|
||||
),
|
||||
Ok(Some((pos, incomplete))) if
|
||||
pos == Position::from(0) &&
|
||||
incomplete == vec![]
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(0),
|
||||
Ok(h) if h == *"abcd____________"
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(1),
|
||||
Ok(h) if h == *"ab______________"
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.batch_insert(
|
||||
Position::from(10),
|
||||
vec![
|
||||
("k".to_string(), Retention::Ephemeral),
|
||||
("l".to_string(), Retention::Checkpoint { id: 2, is_marked: false }),
|
||||
("m".to_string(), Retention::Ephemeral),
|
||||
].into_iter()
|
||||
),
|
||||
Ok(Some((pos, incomplete))) if
|
||||
pos == Position::from(12) &&
|
||||
incomplete == vec![
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(1), 4),
|
||||
required_for_witness: false
|
||||
},
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(0), 13),
|
||||
required_for_witness: false
|
||||
},
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(1), 7),
|
||||
required_for_witness: false
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(0),
|
||||
// The (0, 13) and (1, 7) incomplete subtrees are
|
||||
// not considered incomplete here because they appear
|
||||
// at the tip of the tree.
|
||||
Err(QueryError::TreeIncomplete(xs)) if xs == vec![
|
||||
Address::from_parts(Level::from(2), 1),
|
||||
Address::from_parts(Level::from(1), 4),
|
||||
]
|
||||
);
|
||||
|
||||
assert!(tree.truncate_removing_checkpoint(1));
|
||||
}
|
||||
|
||||
impl<
|
||||
H: Hashable + Ord + Clone,
|
||||
C: Clone + Ord + core::fmt::Debug,
|
||||
|
@ -2527,12 +2739,16 @@ mod tests {
|
|||
ShardTree::root_at_checkpoint(self, checkpoint_depth).ok()
|
||||
}
|
||||
|
||||
fn witness(&self, _position: Position, _checkpoint_depth: usize) -> Option<Vec<H>> {
|
||||
todo!()
|
||||
fn witness(&self, position: Position, checkpoint_depth: usize) -> Option<Vec<H>> {
|
||||
ShardTree::witness(self, position, checkpoint_depth).ok()
|
||||
}
|
||||
|
||||
fn remove_mark(&mut self, _position: Position) -> bool {
|
||||
todo!()
|
||||
fn remove_mark(&mut self, position: Position) -> bool {
|
||||
if let Some(c) = self.checkpoints.iter().rev().map(|(c, _)| c.clone()).next() {
|
||||
ShardTree::remove_mark(self, position, &c)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn checkpoint(&mut self, checkpoint_id: C) -> bool {
|
||||
|
@ -2540,7 +2756,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn rewind(&mut self) -> bool {
|
||||
todo!()
|
||||
ShardTree::truncate_removing_checkpoint(self, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2557,6 +2773,28 @@ mod tests {
|
|||
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn witnesses() {
|
||||
check_witnesses(|m| {
|
||||
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkpoint_rewind() {
|
||||
check_checkpoint_rewind(|m| {
|
||||
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewind_remove_mark() {
|
||||
check_rewind_remove_mark(|m| {
|
||||
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
|
||||
});
|
||||
}
|
||||
|
||||
// Combined tree tests
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn new_combined_tree<H: Hashable + Ord + Clone + core::fmt::Debug>(
|
||||
|
@ -2577,4 +2815,37 @@ mod tests {
|
|||
fn combined_append() {
|
||||
check_append(new_combined_tree);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combined_rewind_remove_mark() {
|
||||
check_rewind_remove_mark(new_combined_tree);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig::with_cases(100000))]
|
||||
|
||||
#[test]
|
||||
fn check_randomized_u64_ops(
|
||||
ops in proptest::collection::vec(
|
||||
arb_operation((0..32u64).prop_map(SipHashable), 0usize..100),
|
||||
1..100
|
||||
)
|
||||
) {
|
||||
let tree = new_combined_tree(100);
|
||||
let indexed_ops = ops.iter().enumerate().map(|(i, op)| op.map_checkpoint_id(|_| i)).collect::<Vec<_>>();
|
||||
check_operations(tree, &indexed_ops)?;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_randomized_str_ops(
|
||||
ops in proptest::collection::vec(
|
||||
arb_operation((97u8..123).prop_map(|c| char::from(c).to_string()), 0usize..100),
|
||||
1..100
|
||||
)
|
||||
) {
|
||||
let tree = new_combined_tree(100);
|
||||
let indexed_ops = ops.iter().enumerate().map(|(i, op)| op.map_checkpoint_id(|_| i)).collect::<Vec<_>>();
|
||||
check_operations(tree, &indexed_ops)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue