Make example tests reusable across tree implementations.

This commit is contained in:
Kris Nuttycombe 2022-02-20 11:40:12 -07:00
parent ba4fe68eb2
commit aedd865d42
3 changed files with 467 additions and 424 deletions

View File

@ -19,7 +19,7 @@ pub enum Leaf<A> {
}
impl<A> Leaf<A> {
pub fn into_value(self) -> A {
pub fn value(&self) -> &A {
match self {
Leaf::Left(a) => a,
Leaf::Right(_, a) => a,
@ -487,6 +487,16 @@ impl<H> MerkleBridge<H> {
self.prior_position
}
/// Returns the position of the most recently appended leaf.
pub fn position(&self) -> Position {
self.frontier.position()
}
/// Returns the most recently appended leaf.
pub fn current_leaf(&self) -> &H {
self.frontier.leaf().value()
}
/// Returns the fragments of authorization path data for prior bridges,
/// keyed by bridge index.
pub fn auth_fragments(&self) -> &BTreeMap<Position, AuthFragment<H>> {
@ -512,7 +522,7 @@ impl<H> MerkleBridge<H> {
}
}
impl<H: Hashable + Clone + PartialEq> MerkleBridge<H> {
impl<H: Hashable> MerkleBridge<H> {
/// Constructs a new bridge to follow this one. The
/// successor will track the information necessary to create an
/// authentication path for the leaf most recently appended to
@ -608,7 +618,10 @@ pub enum Checkpoint {
Empty,
/// Checkpoint of a particular bridge state to which it is
/// possible to rewind.
AtIndex(usize),
AtIndex {
bridge_idx: usize,
is_witnessed: bool,
},
}
impl Checkpoint {
@ -618,7 +631,7 @@ impl Checkpoint {
}
#[derive(Clone, Serialize, Deserialize)]
pub struct BridgeTree<H: Ord + Eq, const DEPTH: u8> {
pub struct BridgeTree<H: Ord, const DEPTH: u8> {
/// The ordered list of Merkle bridges representing the history
/// of the tree. There will be one bridge for each saved leaf, plus
/// the current bridge to the tip of the tree.
@ -634,7 +647,7 @@ pub struct BridgeTree<H: Ord + Eq, const DEPTH: u8> {
max_checkpoints: usize,
}
impl<H: Hashable + Ord + Eq + Debug, const DEPTH: u8> Debug for BridgeTree<H, DEPTH> {
impl<H: Hashable + Debug, const DEPTH: u8> Debug for BridgeTree<H, DEPTH> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
@ -666,7 +679,7 @@ pub enum BridgeTreeError {
CheckpointMismatch,
}
impl<H: Ord + Eq, const DEPTH: u8> BridgeTree<H, DEPTH> {
impl<H: Ord, const DEPTH: u8> BridgeTree<H, DEPTH> {
/// Removes the oldest checkpoint. Returns true if successful and false if
/// there are no checkpoints.
fn drop_oldest_checkpoint(&mut self) -> bool {
@ -704,7 +717,7 @@ impl<H: Ord + Eq, const DEPTH: u8> BridgeTree<H, DEPTH> {
}
}
impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
impl<H: Hashable, const DEPTH: u8> BridgeTree<H, DEPTH> {
pub fn new(max_checkpoints: usize) -> Self {
BridgeTree {
bridges: vec![],
@ -749,8 +762,15 @@ impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
// verify checkpoints to ensure continuity from bridge locations
} else if checkpoints.len() > max_checkpoints
|| checkpoints.iter().any(|c| match c {
Checkpoint::Empty => false,
Checkpoint::AtIndex(i) => i >= &bridges.len(),
Checkpoint::Empty => false, // empty checkpoint is always ok
Checkpoint::AtIndex {
bridge_idx,
is_witnessed,
} => {
bridge_idx >= &bridges.len()
|| (*is_witnessed
&& !saved.contains_key(bridges[*bridge_idx].current_leaf()))
}
})
{
Err(BridgeTreeError::CheckpointMismatch)
@ -774,7 +794,7 @@ impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
// a duplicate frontier might occur when we observe a previously witnessed
// value where that value was subsequently removed.
let is_duplicate_frontier =
blen > 1 && self.bridges[blen - 1].frontier == self.bridges[blen - 2].frontier;
blen > 1 && self.bridges[blen - 1].position() == self.bridges[blen - 2].position();
let save_idx = if is_duplicate_frontier {
// By saving at `blen - 2` we effectively restore the original witness.
@ -787,6 +807,7 @@ impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
};
if btype == BoundaryType::Witness {
// if we already have an entry for the leaf hash, don't update it
self.saved.entry(leaf).or_insert(save_idx);
}
@ -794,7 +815,7 @@ impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
}
}
impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> crate::Frontier<H> for BridgeTree<H, DEPTH> {
impl<H: Hashable, const DEPTH: u8> crate::Frontier<H> for BridgeTree<H, DEPTH> {
fn append(&mut self, value: &H) -> bool {
if let Some(bridge) = self.bridges.last_mut() {
if bridge.frontier.position().is_complete(Altitude(DEPTH)) {
@ -825,7 +846,7 @@ impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> crate::Frontier<H> for Bri
}
}
impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> Tree<H> for BridgeTree<H, DEPTH> {
impl<H: Hashable, const DEPTH: u8> Tree<H> for BridgeTree<H, DEPTH> {
type Recording = BridgeRecording<H, DEPTH>;
/// Returns the most recently appended leaf value.
@ -919,9 +940,14 @@ impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> Tree<H> for BridgeTree<H,
/// Marks the current tree state as a checkpoint if it is not already a
/// checkpoint.
fn checkpoint(&mut self) {
let new_checkpoint = self
.witness_internal(BoundaryType::Checkpoint)
.map_or(Checkpoint::Empty, Checkpoint::AtIndex);
let is_witnessed = self.current_leaf().map_or(false, |v| self.is_witnessed(v));
let new_checkpoint = self.witness_internal(BoundaryType::Checkpoint).map_or(
Checkpoint::Empty,
|bridge_idx| Checkpoint::AtIndex {
bridge_idx,
is_witnessed,
},
);
self.checkpoints.push(new_checkpoint);
if self.checkpoints.len() > self.max_checkpoints {
@ -934,51 +960,54 @@ impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> Tree<H> for BridgeTree<H,
/// witness data would be destroyed in the process.
fn rewind(&mut self) -> bool {
match self.checkpoints.pop() {
Some(Checkpoint::Empty) => {
Some(c @ Checkpoint::Empty) => {
if self.saved.is_empty() {
self.bridges.truncate(0);
true
} else {
self.checkpoints.push(Checkpoint::Empty);
self.checkpoints.push(c);
false
}
}
Some(Checkpoint::AtIndex(i)) => {
Some(
c @ Checkpoint::AtIndex {
bridge_idx,
is_witnessed,
},
) => {
// Do not rewind if doing so would remove a witness.
// However, if the index to which we are rewinding is
// a witnessed index, we can rewind and re-witness.
match self
.saved
.values()
.filter(|saved_idx| *saved_idx >= &i)
.filter(|saved_idx| *saved_idx >= &bridge_idx)
.max()
{
Some(saved_idx) if *saved_idx > i => {
Some(saved_idx)
if *saved_idx > bridge_idx
|| (*saved_idx == bridge_idx && !is_witnessed) =>
{
// there is a witnessed value at a later position, so
// we restore the removed checkpoint and return failure
self.checkpoints.push(Checkpoint::AtIndex(i));
self.checkpoints.push(c);
false
}
Some(saved_idx) if *saved_idx == i => {
Some(saved_idx) if *saved_idx == bridge_idx => {
// the position to which we are rewinding was previously
// witnessed, so we re-witness after truncation
self.bridges.truncate(i + 1);
self.bridges.truncate(bridge_idx + 1);
self.witness();
true
}
_ => {
// no witnesses at positions later than the checkpoint,
// so we can just truncate.
self.bridges.truncate(i + 1);
self.bridges.truncate(bridge_idx + 1);
// if the checkpoint removed was a duplicate, we need to
// restore the successor bridge so that future appends correctly
// affect the successor bridge
if self
.checkpoints
.last()
.iter()
.any(|c| **c == Checkpoint::AtIndex(i))
{
if self.checkpoints.last().iter().any(|c0| **c0 == c) {
self.witness_internal(BoundaryType::Checkpoint);
}
true
@ -1025,7 +1054,7 @@ impl<H: Hashable + Ord + Eq + Clone, const DEPTH: u8> Tree<H> for BridgeTree<H,
}
#[derive(Clone)]
pub struct BridgeRecording<H, const DEPTH: u8> {
pub struct BridgeRecording<H: Ord, const DEPTH: u8> {
bridge: Option<MerkleBridge<H>>,
}
@ -1062,21 +1091,8 @@ impl<H: Hashable + Clone + PartialEq, const DEPTH: u8> Recording<H> for BridgeRe
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::Operation;
use crate::tests::Operation::*;
use crate::{Frontier, Tree};
#[test]
fn position_altitudes() {
assert_eq!(Position(0).max_altitude(), Altitude(0));
assert_eq!(Position(1).max_altitude(), Altitude(0));
assert_eq!(Position(2).max_altitude(), Altitude(1));
assert_eq!(Position(3).max_altitude(), Altitude(1));
assert_eq!(Position(4).max_altitude(), Altitude(2));
assert_eq!(Position(7).max_altitude(), Altitude(2));
assert_eq!(Position(8).max_altitude(), Altitude(3));
}
#[test]
fn tree_depth() {
let mut tree = BridgeTree::<String, 3>::new(100);
@ -1087,7 +1103,7 @@ mod tests {
}
#[test]
fn root_hashes() {
fn bridge_root_hashes() {
let mut bridge = MerkleBridge::<String>::new("a".to_string());
assert_eq!(bridge.root(), "a_");
@ -1096,285 +1112,6 @@ mod tests {
bridge.append("c".to_string());
assert_eq!(bridge.root(), "abc_");
let mut tree = BridgeTree::<String, 4>::new(100);
assert_eq!(tree.root(), "________________");
tree.append(&"a".to_string());
assert_eq!(tree.root().len(), 16);
assert_eq!(tree.root(), "a_______________");
tree.append(&"b".to_string());
assert_eq!(tree.root(), "ab______________");
tree.append(&"c".to_string());
assert_eq!(tree.root(), "abc_____________");
}
#[test]
fn auth_paths() {
let mut tree = BridgeTree::<String, 4>::new(100);
tree.append(&"a".to_string());
tree.witness();
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"_".to_string(),
"__".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
tree.append(&"b".to_string());
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"b".to_string(),
"__".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
tree.append(&"c".to_string());
tree.witness();
assert_eq!(
tree.authentication_path(&"c".to_string()),
Some((
Position::from(2),
vec![
"_".to_string(),
"ab".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
tree.append(&"d".to_string());
assert_eq!(
tree.authentication_path(&"c".to_string()),
Some((
Position::from(2),
vec![
"d".to_string(),
"ab".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
tree.append(&"e".to_string());
assert_eq!(
tree.authentication_path(&"c".to_string()),
Some((
Position::from(2),
vec![
"d".to_string(),
"ab".to_string(),
"e___".to_string(),
"________".to_string()
]
))
);
let mut tree = BridgeTree::<String, 4>::new(100);
tree.append(&"a".to_string());
tree.witness();
for c in 'b'..'h' {
tree.append(&c.to_string());
}
tree.witness();
tree.append(&"h".to_string());
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"b".to_string(),
"cd".to_string(),
"efgh".to_string(),
"________".to_string()
]
))
);
let mut tree = BridgeTree::<String, 4>::new(100);
tree.append(&"a".to_string());
tree.witness();
tree.append(&"b".to_string());
tree.append(&"c".to_string());
tree.append(&"d".to_string());
tree.witness();
tree.append(&"e".to_string());
tree.witness();
tree.append(&"f".to_string());
tree.witness();
tree.append(&"g".to_string());
assert_eq!(
tree.authentication_path(&"f".to_string()),
Some((
Position::from(5),
vec![
"e".to_string(),
"g_".to_string(),
"abcd".to_string(),
"________".to_string()
]
))
);
let mut tree = BridgeTree::<String, 4>::new(100);
for c in 'a'..'l' {
tree.append(&c.to_string());
}
tree.witness();
tree.append(&'l'.to_string());
assert_eq!(
tree.authentication_path(&"k".to_string()),
Some((
Position::from(10),
vec![
"l".to_string(),
"ij".to_string(),
"____".to_string(),
"abcdefgh".to_string()
]
))
);
let mut tree = BridgeTree::<String, 4>::new(100);
tree.append(&'a'.to_string());
tree.witness();
tree.checkpoint();
tree.rewind();
for c in 'b'..'f' {
tree.append(&c.to_string());
}
tree.witness();
for c in 'f'..'i' {
tree.append(&c.to_string());
}
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"b".to_string(),
"cd".to_string(),
"efgh".to_string(),
"________".to_string()
]
))
);
let mut tree = BridgeTree::<String, 4>::new(100);
tree.append(&'a'.to_string());
tree.witness();
tree.remove_witness(&'a'.to_string());
tree.checkpoint();
tree.witness();
tree.rewind();
tree.checkpoint();
tree.append(&'a'.to_string());
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"a".to_string(),
"__".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
let mut tree = BridgeTree::<String, 4>::new(100);
tree.append(&'a'.to_string());
tree.append(&'b'.to_string());
tree.append(&'c'.to_string());
tree.witness();
tree.append(&'d'.to_string());
tree.append(&'e'.to_string());
tree.append(&'f'.to_string());
tree.append(&'g'.to_string());
tree.witness();
tree.checkpoint();
tree.append(&'h'.to_string());
tree.rewind();
assert_eq!(
tree.authentication_path(&"c".to_string()),
Some((
Position::from(2),
vec![
"d".to_string(),
"ab".to_string(),
"efg_".to_string(),
"________".to_string()
]
))
);
let mut tree = BridgeTree::<String, 4>::new(100);
for c in 'a'..'n' {
tree.append(&c.to_string());
}
tree.witness();
tree.append(&'n'.to_string());
tree.witness();
tree.append(&'o'.to_string());
tree.append(&'p'.to_string());
assert_eq!(
tree.authentication_path(&"m".to_string()),
Some((
Position::from(12),
vec![
"n".to_string(),
"op".to_string(),
"ijkl".to_string(),
"abcdefgh".to_string()
]
))
);
let ops = ('a'..='l')
.into_iter()
.map(|c| Append(c.to_string()))
.chain(Some(Witness))
.chain(Some(Append('m'.to_string())))
.chain(Some(Append('n'.to_string())))
.chain(Some(Authpath('l'.to_string())))
.collect::<Vec<_>>();
let mut tree = BridgeTree::<String, 4>::new(100);
assert_eq!(
Operation::apply_all(&ops, &mut tree),
Some((
Position::from(11),
vec![
"k".to_string(),
"ij".to_string(),
"mn__".to_string(),
"abcdefgh".to_string()
]
))
);
}
#[test]
@ -1389,25 +1126,6 @@ mod tests {
assert_eq!(t.drop_oldest_checkpoint(), true);
}
#[test]
fn checkpoint_rewind_0() {
let mut t = BridgeTree::<String, 6>::new(100);
t.append(&"a".to_string());
t.checkpoint();
t.append(&"b".to_string());
t.witness();
assert_eq!(t.rewind(), false);
}
#[test]
fn checkpoint_rewind_1() {
let mut t = BridgeTree::<String, 6>::new(100);
t.append(&"a".to_string());
t.checkpoint();
t.witness();
assert_eq!(t.rewind(), false);
}
#[test]
fn frontier_from_parts() {
assert!(
@ -1424,4 +1142,24 @@ mod tests {
.is_err()
);
}
#[test]
fn root_hashes() {
crate::tests::check_root_hashes(BridgeTree::<String, 4>::new);
}
#[test]
fn auth_paths() {
crate::tests::check_auth_paths(BridgeTree::<String, 4>::new);
}
#[test]
fn checkpoint_rewind() {
crate::tests::check_checkpoint_rewind(BridgeTree::<String, 4>::new);
}
#[test]
fn rewind_remove_witness() {
crate::tests::check_rewind_remove_witness(BridgeTree::<String, 4>::new);
}
}

View File

@ -190,7 +190,7 @@ impl From<usize> for Position {
/// A trait describing the operations that make a value suitable for inclusion in
/// an incremental merkle tree.
pub trait Hashable: Sized {
pub trait Hashable: Sized + Ord + Clone {
fn empty_leaf() -> Self;
fn combine(level: Altitude, a: &Self, b: &Self) -> Self;
@ -294,6 +294,351 @@ pub(crate) mod tests {
use super::sample::{lazy_root, CompleteRecording, CompleteTree};
use super::{Altitude, Frontier, Hashable, Position, Recording, Tree};
#[test]
fn position_altitudes() {
assert_eq!(Position(0).max_altitude(), Altitude(0));
assert_eq!(Position(1).max_altitude(), Altitude(0));
assert_eq!(Position(2).max_altitude(), Altitude(1));
assert_eq!(Position(3).max_altitude(), Altitude(1));
assert_eq!(Position(4).max_altitude(), Altitude(2));
assert_eq!(Position(7).max_altitude(), Altitude(2));
assert_eq!(Position(8).max_altitude(), Altitude(3));
}
//
// Types and utilities for shared example tests.
//
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct SipHashable(pub(crate) u64);
impl Hashable for SipHashable {
fn empty_leaf() -> Self {
SipHashable(0)
}
fn combine(_level: Altitude, a: &Self, b: &Self) -> Self {
let mut hasher = SipHasher::new();
hasher.write_u64(a.0);
hasher.write_u64(b.0);
SipHashable(hasher.finish())
}
}
impl Hashable for String {
fn empty_leaf() -> Self {
"_".to_string()
}
fn combine(_: Altitude, a: &Self, b: &Self) -> Self {
a.to_string() + b
}
}
//
// Shared example tests
//
pub(crate) fn check_root_hashes<T: Tree<String>, F: Fn(usize) -> T>(new_tree: F) {
let mut tree = new_tree(100);
assert_eq!(tree.root(), "________________");
tree.append(&"a".to_string());
assert_eq!(tree.root().len(), 16);
assert_eq!(tree.root(), "a_______________");
tree.append(&"b".to_string());
assert_eq!(tree.root(), "ab______________");
tree.append(&"c".to_string());
assert_eq!(tree.root(), "abc_____________");
}
pub(crate) fn check_auth_paths<T: Tree<String>, F: Fn(usize) -> T>(new_tree: F) {
let mut tree = new_tree(100);
tree.append(&"a".to_string());
tree.witness();
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"_".to_string(),
"__".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
tree.append(&"b".to_string());
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"b".to_string(),
"__".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
tree.append(&"c".to_string());
tree.witness();
assert_eq!(
tree.authentication_path(&"c".to_string()),
Some((
Position::from(2),
vec![
"_".to_string(),
"ab".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
tree.append(&"d".to_string());
assert_eq!(
tree.authentication_path(&"c".to_string()),
Some((
Position::from(2),
vec![
"d".to_string(),
"ab".to_string(),
"____".to_string(),
"________".to_string()
]
))
);
tree.append(&"e".to_string());
assert_eq!(
tree.authentication_path(&"c".to_string()),
Some((
Position::from(2),
vec![
"d".to_string(),
"ab".to_string(),
"e___".to_string(),
"________".to_string()
]
))
);
let mut tree = new_tree(100);
tree.append(&"a".to_string());
tree.witness();
for c in 'b'..'h' {
tree.append(&c.to_string());
}
tree.witness();
tree.append(&"h".to_string());
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"b".to_string(),
"cd".to_string(),
"efgh".to_string(),
"________".to_string()
]
))
);
let mut tree = new_tree(100);
tree.append(&"a".to_string());
tree.witness();
tree.append(&"b".to_string());
tree.append(&"c".to_string());
tree.append(&"d".to_string());
tree.witness();
tree.append(&"e".to_string());
tree.witness();
tree.append(&"f".to_string());
tree.witness();
tree.append(&"g".to_string());
assert_eq!(
tree.authentication_path(&"f".to_string()),
Some((
Position::from(5),
vec![
"e".to_string(),
"g_".to_string(),
"abcd".to_string(),
"________".to_string()
]
))
);
let mut tree = new_tree(100);
for c in 'a'..'l' {
tree.append(&c.to_string());
}
tree.witness();
tree.append(&'l'.to_string());
assert_eq!(
tree.authentication_path(&"k".to_string()),
Some((
Position::from(10),
vec![
"l".to_string(),
"ij".to_string(),
"____".to_string(),
"abcdefgh".to_string()
]
))
);
let mut tree = new_tree(100);
tree.append(&'a'.to_string());
tree.witness();
tree.checkpoint();
assert_eq!(tree.rewind(), true);
for c in 'b'..'f' {
tree.append(&c.to_string());
}
tree.witness();
for c in 'f'..'i' {
tree.append(&c.to_string());
}
assert_eq!(
tree.authentication_path(&"a".to_string()),
Some((
Position::zero(),
vec![
"b".to_string(),
"cd".to_string(),
"efgh".to_string(),
"________".to_string()
]
))
);
let mut tree = new_tree(100);
tree.append(&'a'.to_string());
tree.append(&'b'.to_string());
tree.append(&'c'.to_string());
tree.witness();
tree.append(&'d'.to_string());
tree.append(&'e'.to_string());
tree.append(&'f'.to_string());
tree.append(&'g'.to_string());
tree.witness();
tree.checkpoint();
tree.append(&'h'.to_string());
assert_eq!(tree.rewind(), true);
assert_eq!(
tree.authentication_path(&"c".to_string()),
Some((
Position::from(2),
vec![
"d".to_string(),
"ab".to_string(),
"efg_".to_string(),
"________".to_string()
]
))
);
let mut tree = new_tree(100);
for c in 'a'..'n' {
tree.append(&c.to_string());
}
tree.witness();
tree.append(&'n'.to_string());
tree.witness();
tree.append(&'o'.to_string());
tree.append(&'p'.to_string());
assert_eq!(
tree.authentication_path(&"m".to_string()),
Some((
Position::from(12),
vec![
"n".to_string(),
"op".to_string(),
"ijkl".to_string(),
"abcdefgh".to_string()
]
))
);
let ops = ('a'..='l')
.into_iter()
.map(|c| Append(c.to_string()))
.chain(Some(Witness))
.chain(Some(Append('m'.to_string())))
.chain(Some(Append('n'.to_string())))
.chain(Some(Authpath('l'.to_string())))
.collect::<Vec<_>>();
let mut tree = new_tree(100);
assert_eq!(
Operation::apply_all(&ops, &mut tree),
Some((
Position::from(11),
vec![
"k".to_string(),
"ij".to_string(),
"mn__".to_string(),
"abcdefgh".to_string()
]
))
);
}
pub(crate) fn check_checkpoint_rewind<T: Tree<String>, F: Fn(usize) -> T>(new_tree: F) {
let mut t = new_tree(100);
t.append(&"a".to_string());
t.checkpoint();
t.append(&"b".to_string());
t.witness();
assert_eq!(t.rewind(), false);
let mut t = new_tree(100);
t.append(&"a".to_string());
t.checkpoint();
t.witness();
assert_eq!(t.rewind(), false);
let mut t = new_tree(100);
t.append(&"a".to_string());
t.witness();
t.checkpoint();
assert_eq!(t.rewind(), true);
}
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_eq!(tree.remove_witness(&"e".to_string()), true);
assert_eq!(tree.rewind(), true);
assert_eq!(tree.remove_witness(&"e".to_string()), true);
let mut tree = new_tree(100);
tree.append(&"e".to_string());
tree.witness();
assert_eq!(tree.remove_witness(&"e".to_string()), true);
tree.checkpoint();
assert_eq!(tree.rewind(), true);
assert_eq!(tree.remove_witness(&"e".to_string()), false);
}
//
// Types and utilities for cross-verification property tests
//
#[derive(Clone)]
pub struct CombinedTree<H: Hashable + Ord + Eq, const DEPTH: u8> {
inefficient: CompleteTree<H>,
@ -438,32 +783,6 @@ pub(crate) mod tests {
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct SipHashable(pub(crate) u64);
impl Hashable for SipHashable {
fn empty_leaf() -> Self {
SipHashable(0)
}
fn combine(_level: Altitude, a: &Self, b: &Self) -> Self {
let mut hasher = SipHasher::new();
hasher.write_u64(a.0);
hasher.write_u64(b.0);
SipHashable(hasher.finish())
}
}
impl Hashable for String {
fn empty_leaf() -> Self {
"_".to_string()
}
fn combine(_: Altitude, a: &Self, b: &Self) -> Self {
a.to_string() + b
}
}
#[derive(Clone, Debug)]
pub enum Operation<A> {
Append(A),
@ -607,30 +926,6 @@ pub(crate) mod tests {
})
}
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)),
1..100
)
) {
check_operations(ops)?;
}
#[test]
fn check_randomized_str_ops(
ops in proptest::collection::vec(
arb_operation((97u8..123).prop_map(|c| char::from(c).to_string())),
1..100
)
) {
check_operations::<String>(ops)?;
}
}
fn check_operations<H: Hashable + Clone + std::fmt::Debug + Eq + Ord>(
ops: Vec<Operation<H>>,
) -> Result<(), TestCaseError> {
@ -748,4 +1043,28 @@ pub(crate) mod tests {
Ok(())
}
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)),
1..100
)
) {
check_operations(ops)?;
}
#[test]
fn check_randomized_str_ops(
ops in proptest::collection::vec(
arb_operation((97u8..123).prop_map(|c| char::from(c).to_string())),
1..100
)
) {
check_operations::<String>(ops)?;
}
}
}

View File

@ -368,6 +368,16 @@ mod tests {
assert_eq!(tree.root(), expected);
}
#[test]
fn root_hashes() {
crate::tests::check_root_hashes(|max_c| CompleteTree::<String>::new(4, max_c));
}
#[test]
fn auth_paths() {
crate::tests::check_auth_paths(|max_c| CompleteTree::<String>::new(4, max_c));
}
#[test]
fn correct_auth_path() {
const DEPTH: usize = 3;
@ -405,37 +415,13 @@ mod tests {
}
}
#[test]
fn checkpoint_rewind() {
crate::tests::check_checkpoint_rewind(|max_c| CompleteTree::<String>::new(4, max_c));
}
#[test]
fn rewind_remove_witness() {
const DEPTH: usize = 3;
let mut tree = CompleteTree::<String>::new(DEPTH, 100);
tree.append(&"e".to_string());
tree.witness();
tree.checkpoint();
tree.remove_witness(&"e".to_string());
tree.rewind();
tree.append(&"g".to_string());
assert_eq!(tree.remove_witness(&"e".to_string()), true);
let mut tree = CompleteTree::<String>::new(DEPTH, 100);
tree.append(&"e".to_string());
tree.witness();
tree.remove_witness(&"e".to_string());
tree.checkpoint();
tree.rewind();
tree.append(&"g".to_string());
assert_eq!(tree.remove_witness(&"e".to_string()), false);
let mut tree = CompleteTree::<String>::new(DEPTH, 100);
tree.append(&"a".to_string());
tree.checkpoint();
tree.witness();
assert_eq!(tree.rewind(), false);
let mut tree = CompleteTree::<String>::new(DEPTH, 100);
tree.append(&"a".to_string());
tree.witness();
tree.checkpoint();
assert_eq!(tree.rewind(), true);
crate::tests::check_rewind_remove_witness(|max_c| CompleteTree::<String>::new(4, max_c));
}
}