Move `testing` submodule into a separate file
Co-authored-by: Kris Nuttycombe <kris@nutty.land>
This commit is contained in:
parent
f2d2bd3719
commit
4152961f77
|
@ -23,6 +23,9 @@ pub use self::prunable::{
|
|||
|
||||
pub mod memory;
|
||||
|
||||
#[cfg(any(bench, test, feature = "test-dependencies"))]
|
||||
pub mod testing;
|
||||
|
||||
/// An enumeration of possible checkpoint locations.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum TreeState {
|
||||
|
@ -1490,402 +1493,6 @@ fn accumulate_result_with<A, B, C>(
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(bench, test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use assert_matches::assert_matches;
|
||||
use proptest::bool::weighted;
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
use proptest::sample::select;
|
||||
|
||||
use incrementalmerkletree::{testing, Hashable};
|
||||
|
||||
use super::*;
|
||||
use crate::memory::MemoryShardStore;
|
||||
|
||||
pub fn arb_retention_flags() -> impl Strategy<Value = RetentionFlags> + Clone {
|
||||
select(vec![
|
||||
RetentionFlags::EPHEMERAL,
|
||||
RetentionFlags::CHECKPOINT,
|
||||
RetentionFlags::MARKED,
|
||||
RetentionFlags::MARKED | RetentionFlags::CHECKPOINT,
|
||||
])
|
||||
}
|
||||
|
||||
pub fn arb_tree<A: Strategy + Clone + 'static, V: Strategy + 'static>(
|
||||
arb_annotation: A,
|
||||
arb_leaf: V,
|
||||
depth: u32,
|
||||
size: u32,
|
||||
) -> impl Strategy<Value = Tree<A::Value, V::Value>> + Clone
|
||||
where
|
||||
A::Value: Clone + 'static,
|
||||
V::Value: Clone + 'static,
|
||||
{
|
||||
let leaf = prop_oneof![
|
||||
Just(Tree(Node::Nil)),
|
||||
arb_leaf.prop_map(|value| Tree(Node::Leaf { value }))
|
||||
];
|
||||
|
||||
leaf.prop_recursive(depth, size, 2, move |inner| {
|
||||
(arb_annotation.clone(), inner.clone(), inner).prop_map(|(ann, left, right)| {
|
||||
Tree(if left.is_nil() && right.is_nil() {
|
||||
Node::Nil
|
||||
} else {
|
||||
Node::Parent {
|
||||
ann,
|
||||
left: Rc::new(left),
|
||||
right: Rc::new(right),
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn arb_prunable_tree<H: Strategy + Clone + 'static>(
|
||||
arb_leaf: H,
|
||||
depth: u32,
|
||||
size: u32,
|
||||
) -> impl Strategy<Value = PrunableTree<H::Value>> + Clone
|
||||
where
|
||||
H::Value: Clone + 'static,
|
||||
{
|
||||
arb_tree(
|
||||
proptest::option::of(arb_leaf.clone().prop_map(Rc::new)),
|
||||
(arb_leaf, arb_retention_flags()),
|
||||
depth,
|
||||
size,
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs a random shardtree of size up to 2^6 with shards of size 2^3. Returns the tree,
|
||||
/// along with vectors of the checkpoint and mark positions.
|
||||
pub fn arb_shardtree<H: Strategy + Clone>(
|
||||
arb_leaf: H,
|
||||
) -> impl Strategy<
|
||||
Value = (
|
||||
ShardTree<MemoryShardStore<H::Value, usize>, 6, 3>,
|
||||
Vec<Position>,
|
||||
Vec<Position>,
|
||||
),
|
||||
>
|
||||
where
|
||||
H::Value: Hashable + Clone + PartialEq,
|
||||
{
|
||||
vec(
|
||||
(arb_leaf, weighted(0.1), weighted(0.2)),
|
||||
0..=(2usize.pow(6)),
|
||||
)
|
||||
.prop_map(|leaves| {
|
||||
let mut tree = ShardTree::new(MemoryShardStore::empty(), 10);
|
||||
let mut checkpoint_positions = vec![];
|
||||
let mut marked_positions = vec![];
|
||||
tree.batch_insert(
|
||||
Position::from(0),
|
||||
leaves
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, (leaf, is_marked, is_checkpoint))| {
|
||||
(
|
||||
leaf,
|
||||
match (is_checkpoint, is_marked) {
|
||||
(false, false) => Retention::Ephemeral,
|
||||
(true, is_marked) => {
|
||||
let pos = Position::try_from(id).unwrap();
|
||||
checkpoint_positions.push(pos);
|
||||
if is_marked {
|
||||
marked_positions.push(pos);
|
||||
}
|
||||
Retention::Checkpoint { id, is_marked }
|
||||
}
|
||||
(false, true) => {
|
||||
marked_positions.push(Position::try_from(id).unwrap());
|
||||
Retention::Marked
|
||||
}
|
||||
},
|
||||
)
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
(tree, checkpoint_positions, marked_positions)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn arb_char_str() -> impl Strategy<Value = String> + Clone {
|
||||
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
(0usize..chars.len()).prop_map(move |i| chars.get(i..=i).unwrap().to_string())
|
||||
}
|
||||
|
||||
impl<
|
||||
H: Hashable + Ord + Clone + core::fmt::Debug,
|
||||
C: Clone + Ord + core::fmt::Debug,
|
||||
S: ShardStore<H = H, CheckpointId = C>,
|
||||
const DEPTH: u8,
|
||||
const SHARD_HEIGHT: u8,
|
||||
> testing::Tree<H, C> for ShardTree<S, DEPTH, SHARD_HEIGHT>
|
||||
where
|
||||
S::Error: std::fmt::Debug,
|
||||
{
|
||||
fn depth(&self) -> u8 {
|
||||
DEPTH
|
||||
}
|
||||
|
||||
fn append(&mut self, value: H, retention: Retention<C>) -> bool {
|
||||
match ShardTree::append(self, value, retention) {
|
||||
Ok(_) => true,
|
||||
Err(ShardTreeError::Insert(InsertionError::TreeFull)) => false,
|
||||
Err(other) => panic!("append failed due to error: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_position(&self) -> Option<Position> {
|
||||
match ShardTree::max_leaf_position(self, 0) {
|
||||
Ok(v) => v,
|
||||
Err(err) => panic!("current position query failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_marked_leaf(&self, position: Position) -> Option<H> {
|
||||
match ShardTree::get_marked_leaf(self, position) {
|
||||
Ok(v) => v,
|
||||
Err(err) => panic!("marked leaf query failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn marked_positions(&self) -> BTreeSet<Position> {
|
||||
match ShardTree::marked_positions(self) {
|
||||
Ok(v) => v,
|
||||
Err(err) => panic!("marked positions query failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn root(&self, checkpoint_depth: usize) -> Option<H> {
|
||||
match ShardTree::root_at_checkpoint(self, checkpoint_depth) {
|
||||
Ok(v) => Some(v),
|
||||
Err(err) => panic!("root computation failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn witness(&self, position: Position, checkpoint_depth: usize) -> Option<Vec<H>> {
|
||||
match ShardTree::witness(self, position, checkpoint_depth) {
|
||||
Ok(p) => Some(p.path_elems().to_vec()),
|
||||
Err(ShardTreeError::Query(
|
||||
QueryError::NotContained(_)
|
||||
| QueryError::TreeIncomplete(_)
|
||||
| QueryError::CheckpointPruned,
|
||||
)) => None,
|
||||
Err(err) => panic!("witness computation failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_mark(&mut self, position: Position) -> bool {
|
||||
let max_checkpoint = self
|
||||
.store
|
||||
.max_checkpoint_id()
|
||||
.unwrap_or_else(|err| panic!("checkpoint retrieval failed: {:?}", err));
|
||||
|
||||
match ShardTree::remove_mark(self, position, max_checkpoint.as_ref()) {
|
||||
Ok(result) => result,
|
||||
Err(err) => panic!("mark removal failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn checkpoint(&mut self, checkpoint_id: C) -> bool {
|
||||
ShardTree::checkpoint(self, checkpoint_id).unwrap()
|
||||
}
|
||||
|
||||
fn rewind(&mut self) -> bool {
|
||||
ShardTree::truncate_to_depth(self, 1).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_shardtree_insertion<
|
||||
E: Debug,
|
||||
S: ShardStore<H = String, CheckpointId = u32, Error = E>,
|
||||
>(
|
||||
mut tree: ShardTree<S, 4, 3>,
|
||||
) {
|
||||
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
|
||||
},
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(2), 1),
|
||||
required_for_witness: true
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(1),
|
||||
Err(ShardTreeError::Query(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(0), 13),
|
||||
required_for_witness: false
|
||||
},
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(1), 7),
|
||||
required_for_witness: false
|
||||
},
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(1), 4),
|
||||
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(ShardTreeError::Query(QueryError::TreeIncomplete(xs))) if xs == vec![
|
||||
Address::from_parts(Level::from(2), 1),
|
||||
Address::from_parts(Level::from(1), 4),
|
||||
]
|
||||
);
|
||||
|
||||
assert_matches!(tree.truncate_to_depth(1), Ok(true));
|
||||
|
||||
assert_matches!(
|
||||
tree.batch_insert(
|
||||
Position::from(4),
|
||||
('e'..'k')
|
||||
.into_iter()
|
||||
.map(|c| (c.to_string(), Retention::Ephemeral))
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(0),
|
||||
Ok(h) if h == *"abcdefghijkl____"
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(1),
|
||||
Ok(h) if h == *"ab______________"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn check_shard_sizes<E: Debug, S: ShardStore<H = String, CheckpointId = u32, Error = E>>(
|
||||
mut tree: ShardTree<S, 4, 2>,
|
||||
) {
|
||||
for c in 'a'..'p' {
|
||||
tree.append(c.to_string(), Retention::Ephemeral).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(tree.store.get_shard_roots().unwrap().len(), 4);
|
||||
assert_eq!(
|
||||
tree.store
|
||||
.get_shard(Address::from_parts(Level::from(2), 3))
|
||||
.unwrap()
|
||||
.and_then(|t| t.max_position()),
|
||||
Some(Position::from(14))
|
||||
);
|
||||
}
|
||||
|
||||
pub fn check_witness_with_pruned_subtrees<
|
||||
E: Debug,
|
||||
S: ShardStore<H = String, CheckpointId = u32, Error = E>,
|
||||
>(
|
||||
mut tree: ShardTree<S, 6, 3>,
|
||||
) {
|
||||
// introduce some roots
|
||||
let shard_root_level = Level::from(3);
|
||||
for idx in 0u64..4 {
|
||||
let root = if idx == 3 {
|
||||
"abcdefgh".to_string()
|
||||
} else {
|
||||
idx.to_string()
|
||||
};
|
||||
tree.insert(Address::from_parts(shard_root_level, idx), root)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// simulate discovery of a note
|
||||
tree.batch_insert(
|
||||
Position::from(24),
|
||||
('a'..='h').into_iter().map(|c| {
|
||||
(
|
||||
c.to_string(),
|
||||
match c {
|
||||
'c' => Retention::Marked,
|
||||
'h' => Retention::Checkpoint {
|
||||
id: 3,
|
||||
is_marked: false,
|
||||
},
|
||||
_ => Retention::Ephemeral,
|
||||
},
|
||||
)
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// construct a witness for the note
|
||||
let witness = tree.witness(Position::from(26), 0).unwrap();
|
||||
assert_eq!(
|
||||
witness.path_elems(),
|
||||
&[
|
||||
"d",
|
||||
"ab",
|
||||
"efgh",
|
||||
"2",
|
||||
"01",
|
||||
"________________________________"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
|
|
|
@ -0,0 +1,392 @@
|
|||
use assert_matches::assert_matches;
|
||||
use proptest::bool::weighted;
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
use proptest::sample::select;
|
||||
|
||||
use incrementalmerkletree::{testing, Hashable};
|
||||
|
||||
use super::*;
|
||||
use crate::memory::MemoryShardStore;
|
||||
|
||||
pub fn arb_retention_flags() -> impl Strategy<Value = RetentionFlags> + Clone {
|
||||
select(vec![
|
||||
RetentionFlags::EPHEMERAL,
|
||||
RetentionFlags::CHECKPOINT,
|
||||
RetentionFlags::MARKED,
|
||||
RetentionFlags::MARKED | RetentionFlags::CHECKPOINT,
|
||||
])
|
||||
}
|
||||
|
||||
pub fn arb_tree<A: Strategy + Clone + 'static, V: Strategy + 'static>(
|
||||
arb_annotation: A,
|
||||
arb_leaf: V,
|
||||
depth: u32,
|
||||
size: u32,
|
||||
) -> impl Strategy<Value = Tree<A::Value, V::Value>> + Clone
|
||||
where
|
||||
A::Value: Clone + 'static,
|
||||
V::Value: Clone + 'static,
|
||||
{
|
||||
let leaf = prop_oneof![
|
||||
Just(Tree(Node::Nil)),
|
||||
arb_leaf.prop_map(|value| Tree(Node::Leaf { value }))
|
||||
];
|
||||
|
||||
leaf.prop_recursive(depth, size, 2, move |inner| {
|
||||
(arb_annotation.clone(), inner.clone(), inner).prop_map(|(ann, left, right)| {
|
||||
Tree(if left.is_nil() && right.is_nil() {
|
||||
Node::Nil
|
||||
} else {
|
||||
Node::Parent {
|
||||
ann,
|
||||
left: Rc::new(left),
|
||||
right: Rc::new(right),
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn arb_prunable_tree<H: Strategy + Clone + 'static>(
|
||||
arb_leaf: H,
|
||||
depth: u32,
|
||||
size: u32,
|
||||
) -> impl Strategy<Value = PrunableTree<H::Value>> + Clone
|
||||
where
|
||||
H::Value: Clone + 'static,
|
||||
{
|
||||
arb_tree(
|
||||
proptest::option::of(arb_leaf.clone().prop_map(Rc::new)),
|
||||
(arb_leaf, arb_retention_flags()),
|
||||
depth,
|
||||
size,
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs a random shardtree of size up to 2^6 with shards of size 2^3. Returns the tree,
|
||||
/// along with vectors of the checkpoint and mark positions.
|
||||
pub fn arb_shardtree<H: Strategy + Clone>(
|
||||
arb_leaf: H,
|
||||
) -> impl Strategy<
|
||||
Value = (
|
||||
ShardTree<MemoryShardStore<H::Value, usize>, 6, 3>,
|
||||
Vec<Position>,
|
||||
Vec<Position>,
|
||||
),
|
||||
>
|
||||
where
|
||||
H::Value: Hashable + Clone + PartialEq,
|
||||
{
|
||||
vec(
|
||||
(arb_leaf, weighted(0.1), weighted(0.2)),
|
||||
0..=(2usize.pow(6)),
|
||||
)
|
||||
.prop_map(|leaves| {
|
||||
let mut tree = ShardTree::new(MemoryShardStore::empty(), 10);
|
||||
let mut checkpoint_positions = vec![];
|
||||
let mut marked_positions = vec![];
|
||||
tree.batch_insert(
|
||||
Position::from(0),
|
||||
leaves
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, (leaf, is_marked, is_checkpoint))| {
|
||||
(
|
||||
leaf,
|
||||
match (is_checkpoint, is_marked) {
|
||||
(false, false) => Retention::Ephemeral,
|
||||
(true, is_marked) => {
|
||||
let pos = Position::try_from(id).unwrap();
|
||||
checkpoint_positions.push(pos);
|
||||
if is_marked {
|
||||
marked_positions.push(pos);
|
||||
}
|
||||
Retention::Checkpoint { id, is_marked }
|
||||
}
|
||||
(false, true) => {
|
||||
marked_positions.push(Position::try_from(id).unwrap());
|
||||
Retention::Marked
|
||||
}
|
||||
},
|
||||
)
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
(tree, checkpoint_positions, marked_positions)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn arb_char_str() -> impl Strategy<Value = String> + Clone {
|
||||
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
(0usize..chars.len()).prop_map(move |i| chars.get(i..=i).unwrap().to_string())
|
||||
}
|
||||
|
||||
impl<
|
||||
H: Hashable + Ord + Clone + core::fmt::Debug,
|
||||
C: Clone + Ord + core::fmt::Debug,
|
||||
S: ShardStore<H = H, CheckpointId = C>,
|
||||
const DEPTH: u8,
|
||||
const SHARD_HEIGHT: u8,
|
||||
> testing::Tree<H, C> for ShardTree<S, DEPTH, SHARD_HEIGHT>
|
||||
where
|
||||
S::Error: std::fmt::Debug,
|
||||
{
|
||||
fn depth(&self) -> u8 {
|
||||
DEPTH
|
||||
}
|
||||
|
||||
fn append(&mut self, value: H, retention: Retention<C>) -> bool {
|
||||
match ShardTree::append(self, value, retention) {
|
||||
Ok(_) => true,
|
||||
Err(ShardTreeError::Insert(InsertionError::TreeFull)) => false,
|
||||
Err(other) => panic!("append failed due to error: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_position(&self) -> Option<Position> {
|
||||
match ShardTree::max_leaf_position(self, 0) {
|
||||
Ok(v) => v,
|
||||
Err(err) => panic!("current position query failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_marked_leaf(&self, position: Position) -> Option<H> {
|
||||
match ShardTree::get_marked_leaf(self, position) {
|
||||
Ok(v) => v,
|
||||
Err(err) => panic!("marked leaf query failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn marked_positions(&self) -> BTreeSet<Position> {
|
||||
match ShardTree::marked_positions(self) {
|
||||
Ok(v) => v,
|
||||
Err(err) => panic!("marked positions query failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn root(&self, checkpoint_depth: usize) -> Option<H> {
|
||||
match ShardTree::root_at_checkpoint(self, checkpoint_depth) {
|
||||
Ok(v) => Some(v),
|
||||
Err(err) => panic!("root computation failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn witness(&self, position: Position, checkpoint_depth: usize) -> Option<Vec<H>> {
|
||||
match ShardTree::witness(self, position, checkpoint_depth) {
|
||||
Ok(p) => Some(p.path_elems().to_vec()),
|
||||
Err(ShardTreeError::Query(
|
||||
QueryError::NotContained(_)
|
||||
| QueryError::TreeIncomplete(_)
|
||||
| QueryError::CheckpointPruned,
|
||||
)) => None,
|
||||
Err(err) => panic!("witness computation failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_mark(&mut self, position: Position) -> bool {
|
||||
let max_checkpoint = self
|
||||
.store
|
||||
.max_checkpoint_id()
|
||||
.unwrap_or_else(|err| panic!("checkpoint retrieval failed: {:?}", err));
|
||||
|
||||
match ShardTree::remove_mark(self, position, max_checkpoint.as_ref()) {
|
||||
Ok(result) => result,
|
||||
Err(err) => panic!("mark removal failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn checkpoint(&mut self, checkpoint_id: C) -> bool {
|
||||
ShardTree::checkpoint(self, checkpoint_id).unwrap()
|
||||
}
|
||||
|
||||
fn rewind(&mut self) -> bool {
|
||||
ShardTree::truncate_to_depth(self, 1).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_shardtree_insertion<
|
||||
E: Debug,
|
||||
S: ShardStore<H = String, CheckpointId = u32, Error = E>,
|
||||
>(
|
||||
mut tree: ShardTree<S, 4, 3>,
|
||||
) {
|
||||
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
|
||||
},
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(2), 1),
|
||||
required_for_witness: true
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(1),
|
||||
Err(ShardTreeError::Query(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(0), 13),
|
||||
required_for_witness: false
|
||||
},
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(1), 7),
|
||||
required_for_witness: false
|
||||
},
|
||||
IncompleteAt {
|
||||
address: Address::from_parts(Level::from(1), 4),
|
||||
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(ShardTreeError::Query(QueryError::TreeIncomplete(xs))) if xs == vec![
|
||||
Address::from_parts(Level::from(2), 1),
|
||||
Address::from_parts(Level::from(1), 4),
|
||||
]
|
||||
);
|
||||
|
||||
assert_matches!(tree.truncate_to_depth(1), Ok(true));
|
||||
|
||||
assert_matches!(
|
||||
tree.batch_insert(
|
||||
Position::from(4),
|
||||
('e'..'k')
|
||||
.into_iter()
|
||||
.map(|c| (c.to_string(), Retention::Ephemeral))
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(0),
|
||||
Ok(h) if h == *"abcdefghijkl____"
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
tree.root_at_checkpoint(1),
|
||||
Ok(h) if h == *"ab______________"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn check_shard_sizes<E: Debug, S: ShardStore<H = String, CheckpointId = u32, Error = E>>(
|
||||
mut tree: ShardTree<S, 4, 2>,
|
||||
) {
|
||||
for c in 'a'..'p' {
|
||||
tree.append(c.to_string(), Retention::Ephemeral).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(tree.store.get_shard_roots().unwrap().len(), 4);
|
||||
assert_eq!(
|
||||
tree.store
|
||||
.get_shard(Address::from_parts(Level::from(2), 3))
|
||||
.unwrap()
|
||||
.and_then(|t| t.max_position()),
|
||||
Some(Position::from(14))
|
||||
);
|
||||
}
|
||||
|
||||
pub fn check_witness_with_pruned_subtrees<
|
||||
E: Debug,
|
||||
S: ShardStore<H = String, CheckpointId = u32, Error = E>,
|
||||
>(
|
||||
mut tree: ShardTree<S, 6, 3>,
|
||||
) {
|
||||
// introduce some roots
|
||||
let shard_root_level = Level::from(3);
|
||||
for idx in 0u64..4 {
|
||||
let root = if idx == 3 {
|
||||
"abcdefgh".to_string()
|
||||
} else {
|
||||
idx.to_string()
|
||||
};
|
||||
tree.insert(Address::from_parts(shard_root_level, idx), root)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// simulate discovery of a note
|
||||
tree.batch_insert(
|
||||
Position::from(24),
|
||||
('a'..='h').into_iter().map(|c| {
|
||||
(
|
||||
c.to_string(),
|
||||
match c {
|
||||
'c' => Retention::Marked,
|
||||
'h' => Retention::Checkpoint {
|
||||
id: 3,
|
||||
is_marked: false,
|
||||
},
|
||||
_ => Retention::Ephemeral,
|
||||
},
|
||||
)
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// construct a witness for the note
|
||||
let witness = tree.witness(Position::from(26), 0).unwrap();
|
||||
assert_eq!(
|
||||
witness.path_elems(),
|
||||
&[
|
||||
"d",
|
||||
"ab",
|
||||
"efgh",
|
||||
"2",
|
||||
"01",
|
||||
"________________________________"
|
||||
]
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue