Add ShardTree types & implement append operation.
This commit is contained in:
parent
dc5a3ed0e7
commit
ebe3efa135
|
@ -500,9 +500,37 @@ pub fn check_root_hashes<T: Tree<String, usize>, F: Fn(usize) -> T>(new_tree: F)
|
||||||
assert_eq!(t.root(0).unwrap(), "aaaa____________");
|
assert_eq!(t.root(0).unwrap(), "aaaa____________");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) -> T>(new_tree: F) {
|
/// This test expects a depth-4 tree and verifies that the tree reports itself as full after 2^4
|
||||||
|
/// appends.
|
||||||
|
pub fn check_append<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) -> T>(new_tree: F) {
|
||||||
|
use Retention::*;
|
||||||
|
|
||||||
let mut tree = new_tree(100);
|
let mut tree = new_tree(100);
|
||||||
tree.append("a".to_string(), Retention::Marked);
|
assert_eq!(tree.depth(), 4);
|
||||||
|
|
||||||
|
// 16 appends should succeed
|
||||||
|
for i in 0..16 {
|
||||||
|
assert!(tree.append(i.to_string(), Ephemeral));
|
||||||
|
assert_eq!(tree.current_position(), Some(Position::from(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 17th append should fail
|
||||||
|
assert!(!tree.append("16".to_string(), Ephemeral));
|
||||||
|
|
||||||
|
// The following checks a condition on state restoration in the case that an append fails.
|
||||||
|
// We want to ensure that a failed append does not cause a loss of information.
|
||||||
|
let ops = (0..17)
|
||||||
|
.map(|i| Append(i.to_string(), Ephemeral))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let tree = new_tree(100);
|
||||||
|
check_operations(tree, &ops).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_witnesses<T: Tree<String, usize> + std::fmt::Debug, F: Fn(usize) -> T>(new_tree: F) {
|
||||||
|
use Retention::*;
|
||||||
|
|
||||||
|
let mut tree = new_tree(100);
|
||||||
|
tree.append("a".to_string(), Marked);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tree.witness(Position::from(0), 0),
|
tree.witness(Position::from(0), 0),
|
||||||
Some(vec![
|
Some(vec![
|
||||||
|
|
|
@ -321,8 +321,8 @@ mod tests {
|
||||||
use super::CompleteTree;
|
use super::CompleteTree;
|
||||||
use crate::{
|
use crate::{
|
||||||
testing::{
|
testing::{
|
||||||
check_checkpoint_rewind, check_rewind_remove_mark, check_root_hashes, check_witnesses,
|
check_append, check_checkpoint_rewind, check_rewind_remove_mark, check_root_hashes,
|
||||||
compute_root_from_witness, SipHashable, Tree,
|
check_witnesses, compute_root_from_witness, SipHashable, Tree,
|
||||||
},
|
},
|
||||||
Hashable, Level, Position, Retention,
|
Hashable, Level, Position, Retention,
|
||||||
};
|
};
|
||||||
|
@ -367,6 +367,11 @@ mod tests {
|
||||||
assert_eq!(tree.root(0).unwrap(), expected);
|
assert_eq!(tree.root(0).unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append() {
|
||||||
|
check_append(|max_checkpoints| CompleteTree::<String, usize, 4>::new(max_checkpoints, 0));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn root_hashes() {
|
fn root_hashes() {
|
||||||
check_root_hashes(|max_checkpoints| {
|
check_root_hashes(|max_checkpoints| {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
use core::convert::Infallible;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
use core::marker::PhantomData;
|
||||||
use core::ops::{BitAnd, BitOr, Deref, Not, Range};
|
use core::ops::{BitAnd, BitOr, Deref, Not, Range};
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
@ -1436,6 +1438,383 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An enumeration of possible checkpoint locations.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum TreeState {
|
||||||
|
/// Checkpoints of the empty tree.
|
||||||
|
Empty,
|
||||||
|
/// Checkpoint at a (possibly pruned) leaf state corresponding to the
|
||||||
|
/// wrapped leaf position.
|
||||||
|
AtPosition(Position),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Checkpoint {
|
||||||
|
tree_state: TreeState,
|
||||||
|
marks_removed: BTreeSet<Position>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Checkpoint {
|
||||||
|
pub fn tree_empty() -> Self {
|
||||||
|
Checkpoint {
|
||||||
|
tree_state: TreeState::Empty,
|
||||||
|
marks_removed: BTreeSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn at_position(position: Position) -> Self {
|
||||||
|
Checkpoint {
|
||||||
|
tree_state: TreeState::AtPosition(position),
|
||||||
|
marks_removed: BTreeSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_tree_empty(&self) -> bool {
|
||||||
|
matches!(self.tree_state, TreeState::Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> Option<Position> {
|
||||||
|
match self.tree_state {
|
||||||
|
TreeState::Empty => None,
|
||||||
|
TreeState::AtPosition(pos) => Some(pos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A capability for storage of fragment subtrees of the `ShardTree` type.
|
||||||
|
///
|
||||||
|
/// All fragment subtrees must have roots at level `SHARD_HEIGHT - 1`
|
||||||
|
pub trait ShardStore<H> {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Returns the subtree at the given root address, if any such subtree exists.
|
||||||
|
fn get_shard(&self, shard_root: Address) -> Option<&LocatedPrunableTree<H>>;
|
||||||
|
|
||||||
|
/// Returns the subtree containing the maximum inserted leaf position.
|
||||||
|
fn last_shard(&self) -> Option<&LocatedPrunableTree<H>>;
|
||||||
|
|
||||||
|
/// Inserts or replaces the subtree having the same root address as the provided tree.
|
||||||
|
///
|
||||||
|
/// Implementations of this method MUST enforce the constraint that the root address
|
||||||
|
/// of the provided subtree has level `SHARD_HEIGHT - 1`.
|
||||||
|
fn put_shard(&mut self, subtree: LocatedPrunableTree<H>) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
|
/// Returns the vector of addresses corresponding to the roots of subtrees stored in this
|
||||||
|
/// store.
|
||||||
|
fn get_shard_roots(&self) -> Vec<Address>;
|
||||||
|
|
||||||
|
/// Removes subtrees from the underlying store having root addresses at indices greater
|
||||||
|
/// than or equal to that of the specified address.
|
||||||
|
///
|
||||||
|
/// Implementations of this method MUST enforce the constraint that the root address
|
||||||
|
/// provided has level `SHARD_HEIGHT - 1`.
|
||||||
|
fn truncate(&mut self, from: Address) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H> ShardStore<H> for Vec<LocatedPrunableTree<H>> {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn get_shard(&self, shard_root: Address) -> Option<&LocatedPrunableTree<H>> {
|
||||||
|
self.get(shard_root.index())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_shard(&self) -> Option<&LocatedPrunableTree<H>> {
|
||||||
|
self.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put_shard(&mut self, subtree: LocatedPrunableTree<H>) -> Result<(), Self::Error> {
|
||||||
|
let subtree_addr = subtree.root_addr;
|
||||||
|
for subtree_idx in self.last().map_or(0, |s| s.root_addr.index() + 1)..=subtree_addr.index()
|
||||||
|
{
|
||||||
|
self.push(LocatedTree {
|
||||||
|
root_addr: Address::from_parts(subtree_addr.level(), subtree_idx),
|
||||||
|
root: Tree(Node::Nil),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
self[subtree_addr.index()] = subtree;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shard_roots(&self) -> Vec<Address> {
|
||||||
|
self.iter().map(|s| s.root_addr).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(&mut self, from: Address) -> Result<(), Self::Error> {
|
||||||
|
self.truncate(from.index());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A left-dense, sparse binary Merkle tree of the specified depth, represented as a vector of
|
||||||
|
/// subtrees (shards) of the given maximum height.
|
||||||
|
///
|
||||||
|
/// This tree maintains a collection of "checkpoints" which represent positions, usually near the
|
||||||
|
/// front of the tree, that are maintained such that it's possible to truncate nodes to the right
|
||||||
|
/// of the specified position.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ShardTree<H, C: Ord, S: ShardStore<H>, const DEPTH: u8, const SHARD_HEIGHT: u8> {
|
||||||
|
/// The vector of tree shards.
|
||||||
|
store: S,
|
||||||
|
/// The maximum number of checkpoints to retain before pruning.
|
||||||
|
max_checkpoints: usize,
|
||||||
|
/// A map from position to the count of checkpoints at this position.
|
||||||
|
checkpoints: BTreeMap<C, Checkpoint>,
|
||||||
|
// /// A tree that is used to cache the known roots of subtrees in the "cap" of nodes between
|
||||||
|
// /// `SHARD_HEIGHT` and `DEPTH` that are otherwise not directly represented in the tree. This
|
||||||
|
// /// cache is automatically updated when computing roots and witnesses. Leaf nodes are empty
|
||||||
|
// /// because the annotation slot is consistently used to store the subtree hashes at each node.
|
||||||
|
// cap_cache: Tree<Option<Rc<H>>, ()>
|
||||||
|
_hash_type: PhantomData<H>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
H: Hashable + Clone + PartialEq,
|
||||||
|
C: Clone + Ord + core::fmt::Debug,
|
||||||
|
S: ShardStore<H>,
|
||||||
|
const DEPTH: u8,
|
||||||
|
const SHARD_HEIGHT: u8,
|
||||||
|
> ShardTree<H, C, S, DEPTH, SHARD_HEIGHT>
|
||||||
|
{
|
||||||
|
/// Creates a new empty tree.
|
||||||
|
pub fn new(store: S, max_checkpoints: usize, initial_checkpoint_id: C) -> Self {
|
||||||
|
Self {
|
||||||
|
store,
|
||||||
|
max_checkpoints,
|
||||||
|
checkpoints: BTreeMap::from([(initial_checkpoint_id, Checkpoint::tree_empty())]),
|
||||||
|
//cap_cache: Tree(None, ())
|
||||||
|
_hash_type: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the root address of the tree.
|
||||||
|
pub fn root_addr() -> Address {
|
||||||
|
Address::from_parts(Level::from(DEPTH), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the fixed level of subtree roots within the vector of subtrees used as this tree's
|
||||||
|
/// representation.
|
||||||
|
pub fn subtree_level() -> Level {
|
||||||
|
Level::from(SHARD_HEIGHT - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the position and checkpoint count for each checkpointed position in the tree.
|
||||||
|
pub fn checkpoints(&self) -> &BTreeMap<C, Checkpoint> {
|
||||||
|
&self.checkpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the leaf value at the specified position, if it is a marked leaf.
|
||||||
|
pub fn get_marked_leaf(&self, position: Position) -> Option<&H> {
|
||||||
|
self.store
|
||||||
|
.get_shard(Address::above_position(Self::subtree_level(), position))
|
||||||
|
.and_then(|t| t.value_at_position(position))
|
||||||
|
.and_then(|(v, r)| if r.is_marked() { Some(v) } else { None })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the positions of marked leaves in the tree.
|
||||||
|
pub fn marked_positions(&self) -> BTreeSet<Position> {
|
||||||
|
let mut result = BTreeSet::new();
|
||||||
|
for subtree_addr in &self.store.get_shard_roots() {
|
||||||
|
if let Some(subtree) = self.store.get_shard(*subtree_addr) {
|
||||||
|
result.append(&mut subtree.marked_positions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts a new root into the tree at the given address.
|
||||||
|
///
|
||||||
|
/// This will pad from the left until the tree's subtrees vector contains enough trees to reach
|
||||||
|
/// the specified address, which must be at the [`Self::subtree_level`] level. If a subtree
|
||||||
|
/// already exists at this address, its root will be annotated with the specified hash value.
|
||||||
|
///
|
||||||
|
/// This will return an error if the specified hash conflicts with any existing annotation.
|
||||||
|
pub fn put_root(&mut self, addr: Address, value: H) -> Result<(), InsertionError<S::Error>> {
|
||||||
|
let updated_subtree = match self.store.get_shard(addr) {
|
||||||
|
Some(s) if !s.root.is_nil() => s.root.node_value().map_or_else(
|
||||||
|
|| {
|
||||||
|
Ok(Some(
|
||||||
|
s.clone().reannotate_root(Some(Rc::new(value.clone()))),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
|v| {
|
||||||
|
if v == &value {
|
||||||
|
// the existing root is already correctly annotated, so no need to
|
||||||
|
// do anything
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
// the provided value conflicts with the existing root value
|
||||||
|
Err(InsertionError::Conflict(addr))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
// there is no existing subtree root, so construct a new one.
|
||||||
|
Ok(Some(LocatedTree {
|
||||||
|
root_addr: addr,
|
||||||
|
root: Tree(Node::Leaf {
|
||||||
|
value: (value, EPHEMERAL),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
if let Some(s) = updated_subtree {
|
||||||
|
self.store.put_shard(s).map_err(InsertionError::Storage)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a single value at the first available position in the tree.
|
||||||
|
///
|
||||||
|
/// Prefer to use [`Self::batch_insert`] when appending multiple values, as these operations
|
||||||
|
/// require fewer traversals of the tree than are necessary when performing multiple sequential
|
||||||
|
/// calls to [`Self::append`].
|
||||||
|
pub fn append(
|
||||||
|
&mut self,
|
||||||
|
value: H,
|
||||||
|
retention: Retention<C>,
|
||||||
|
) -> Result<(), InsertionError<S::Error>> {
|
||||||
|
if let Retention::Checkpoint { id, .. } = &retention {
|
||||||
|
if self.checkpoints.keys().last() >= Some(id) {
|
||||||
|
return Err(InsertionError::CheckpointOutOfOrder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (append_result, position, checkpoint_id) =
|
||||||
|
if let Some(subtree) = self.store.last_shard() {
|
||||||
|
if subtree.root.reduce(&is_complete) {
|
||||||
|
let addr = subtree.root_addr;
|
||||||
|
|
||||||
|
if addr.index() + 1 >= 0x1 << (SHARD_HEIGHT - 1) {
|
||||||
|
return Err(InsertionError::OutOfRange(addr.position_range()));
|
||||||
|
} else {
|
||||||
|
LocatedTree::empty(addr.next_at_level()).append(value, retention)?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subtree.append(value, retention)?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let root_addr = Address::from_parts(Self::subtree_level(), 0);
|
||||||
|
LocatedTree::empty(root_addr).append(value, retention)?
|
||||||
|
};
|
||||||
|
|
||||||
|
self.store
|
||||||
|
.put_shard(append_result)
|
||||||
|
.map_err(InsertionError::Storage)?;
|
||||||
|
if let Some(c) = checkpoint_id {
|
||||||
|
self.checkpoints
|
||||||
|
.insert(c, Checkpoint::at_position(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.prune_excess_checkpoints()
|
||||||
|
.map_err(InsertionError::Storage)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prune_excess_checkpoints(&mut self) -> Result<(), S::Error> {
|
||||||
|
if self.checkpoints.len() > self.max_checkpoints {
|
||||||
|
// Batch removals by subtree & create a list of the checkpoint identifiers that
|
||||||
|
// will be removed from the checkpoints map.
|
||||||
|
let mut checkpoints_to_delete = vec![];
|
||||||
|
let mut clear_positions: BTreeMap<Address, BTreeMap<Position, RetentionFlags>> =
|
||||||
|
BTreeMap::new();
|
||||||
|
for (cid, checkpoint) in self
|
||||||
|
.checkpoints
|
||||||
|
.iter()
|
||||||
|
.take(self.checkpoints.len() - self.max_checkpoints)
|
||||||
|
{
|
||||||
|
checkpoints_to_delete.push(cid.clone());
|
||||||
|
|
||||||
|
// clear the checkpoint leaf
|
||||||
|
if let TreeState::AtPosition(pos) = checkpoint.tree_state {
|
||||||
|
let subtree_addr = Address::above_position(Self::subtree_level(), pos);
|
||||||
|
clear_positions
|
||||||
|
.entry(subtree_addr)
|
||||||
|
.and_modify(|to_clear| {
|
||||||
|
to_clear
|
||||||
|
.entry(pos)
|
||||||
|
.and_modify(|flags| *flags = *flags | CHECKPOINT)
|
||||||
|
.or_insert(CHECKPOINT);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| BTreeMap::from([(pos, CHECKPOINT)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the leaves that have been marked for removal
|
||||||
|
for unmark_pos in checkpoint.marks_removed.iter() {
|
||||||
|
let subtree_addr = Address::above_position(Self::subtree_level(), *unmark_pos);
|
||||||
|
clear_positions
|
||||||
|
.entry(subtree_addr)
|
||||||
|
.and_modify(|to_clear| {
|
||||||
|
to_clear
|
||||||
|
.entry(*unmark_pos)
|
||||||
|
.and_modify(|flags| *flags = *flags | MARKED)
|
||||||
|
.or_insert(MARKED);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| BTreeMap::from([(*unmark_pos, MARKED)]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune each affected subtree
|
||||||
|
for (subtree_addr, positions) in clear_positions.into_iter() {
|
||||||
|
let cleared = self
|
||||||
|
.store
|
||||||
|
.get_shard(subtree_addr)
|
||||||
|
.map(|subtree| subtree.clear_flags(positions));
|
||||||
|
if let Some(cleared) = cleared {
|
||||||
|
self.store.put_shard(cleared)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that the leaves have been pruned, actually remove the checkpoints
|
||||||
|
for c in checkpoints_to_delete {
|
||||||
|
self.checkpoints.remove(&c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the position of the checkpoint, if any, along with the number of subsequent
|
||||||
|
/// checkpoints at the same position. Returns `None` if `checkpoint_depth == 0` or if
|
||||||
|
/// insufficient checkpoints exist to seek back to the requested depth.
|
||||||
|
pub fn checkpoint_at_depth(&self, checkpoint_depth: usize) -> Option<(&C, &Checkpoint)> {
|
||||||
|
if checkpoint_depth == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.checkpoints.iter().rev().nth(checkpoint_depth - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the position of the rightmost leaf inserted as of the given checkpoint.
|
||||||
|
///
|
||||||
|
/// Returns the maximum leaf position if `checkpoint_depth == 0` (or `Ok(None)` in this
|
||||||
|
/// case if the tree is empty) or an error if the checkpointed position cannot be restored
|
||||||
|
/// because it has been pruned. Note that no actual level-0 leaf may exist at this position.
|
||||||
|
pub fn max_leaf_position(
|
||||||
|
&self,
|
||||||
|
checkpoint_depth: usize,
|
||||||
|
) -> Result<Option<Position>, QueryError> {
|
||||||
|
if checkpoint_depth == 0 {
|
||||||
|
// TODO: This relies on the invariant that the last shard in the subtrees vector is
|
||||||
|
// never created without a leaf then being added to it. However, this may be a
|
||||||
|
// difficult invariant to maintain when adding empty roots, so perhaps we need a
|
||||||
|
// better way of tracking the actual max position of the tree; we might want to
|
||||||
|
// just store it directly.
|
||||||
|
Ok(self.store.last_shard().and_then(|t| t.max_position()))
|
||||||
|
} else {
|
||||||
|
match self.checkpoint_at_depth(checkpoint_depth) {
|
||||||
|
Some((_, c)) => Ok(c.position()),
|
||||||
|
None => {
|
||||||
|
// There is no checkpoint at the specified depth, so we report it as pruned.
|
||||||
|
Err(QueryError::CheckpointPruned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We need an applicative functor for Result for this function so that we can correctly
|
// We need an applicative functor for Result for this function so that we can correctly
|
||||||
// accumulate errors, but we don't have one so we just write a special- cased version here.
|
// accumulate errors, but we don't have one so we just write a special- cased version here.
|
||||||
fn accumulate_result_with<A, B, C>(
|
fn accumulate_result_with<A, B, C>(
|
||||||
|
@ -1499,10 +1878,14 @@ pub mod testing {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
LocatedPrunableTree, LocatedTree, Node, PrunableTree, QueryError, Tree, EPHEMERAL, MARKED,
|
LocatedPrunableTree, LocatedTree, Node, PrunableTree, QueryError, ShardStore, ShardTree,
|
||||||
|
Tree, EPHEMERAL, MARKED,
|
||||||
};
|
};
|
||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
use incrementalmerkletree::{Address, Level, Position, Retention};
|
use incrementalmerkletree::{
|
||||||
|
testing::{self, check_append, complete_tree::CompleteTree, CombinedTree},
|
||||||
|
Address, Hashable, Level, Position, Retention,
|
||||||
|
};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
@ -1582,6 +1965,81 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn located_prunable_tree_insert_subtree() {
|
||||||
|
let t: LocatedPrunableTree<String> = LocatedTree {
|
||||||
|
root_addr: Address::from_parts(3.into(), 1),
|
||||||
|
root: parent(
|
||||||
|
leaf(("abcd".to_string(), EPHEMERAL)),
|
||||||
|
parent(nil(), leaf(("gh".to_string(), EPHEMERAL))),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
t.insert_subtree::<Infallible>(
|
||||||
|
LocatedTree {
|
||||||
|
root_addr: Address::from_parts(1.into(), 6),
|
||||||
|
root: parent(leaf(("e".to_string(), MARKED)), nil())
|
||||||
|
},
|
||||||
|
true
|
||||||
|
),
|
||||||
|
Ok((
|
||||||
|
LocatedTree {
|
||||||
|
root_addr: Address::from_parts(3.into(), 1),
|
||||||
|
root: parent(
|
||||||
|
leaf(("abcd".to_string(), EPHEMERAL)),
|
||||||
|
parent(
|
||||||
|
parent(leaf(("e".to_string(), MARKED)), nil()),
|
||||||
|
leaf(("gh".to_string(), EPHEMERAL))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
vec![]
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn located_prunable_tree_witness() {
|
||||||
|
let t: LocatedPrunableTree<String> = LocatedTree {
|
||||||
|
root_addr: Address::from_parts(3.into(), 0),
|
||||||
|
root: parent(
|
||||||
|
leaf(("abcd".to_string(), EPHEMERAL)),
|
||||||
|
parent(
|
||||||
|
parent(
|
||||||
|
leaf(("e".to_string(), MARKED)),
|
||||||
|
leaf(("f".to_string(), EPHEMERAL)),
|
||||||
|
),
|
||||||
|
leaf(("gh".to_string(), EPHEMERAL)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
t.witness(4.into(), 8.into()),
|
||||||
|
Ok(vec!["f", "gh", "abcd"]
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
t.witness(4.into(), 6.into()),
|
||||||
|
Ok(vec!["f", "__", "abcd"]
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
t.witness(4.into(), 7.into()),
|
||||||
|
Err(QueryError::TreeIncomplete(vec![Address::from_parts(
|
||||||
|
1.into(),
|
||||||
|
3
|
||||||
|
)]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type VecShardStore<H> = Vec<LocatedPrunableTree<H>>;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tree_marked_positions() {
|
fn tree_marked_positions() {
|
||||||
let t: PrunableTree<String> = parent(
|
let t: PrunableTree<String> = parent(
|
||||||
|
@ -1739,76 +2197,80 @@ mod tests {
|
||||||
assert_eq!(complete.subtree.right_filled_root(), Ok("abcd".to_string()));
|
assert_eq!(complete.subtree.right_filled_root(), Ok("abcd".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
impl<
|
||||||
fn located_prunable_tree_insert_subtree() {
|
H: Hashable + Ord + Clone,
|
||||||
let t: LocatedPrunableTree<String> = LocatedTree {
|
C: Clone + Ord + core::fmt::Debug,
|
||||||
root_addr: Address::from_parts(3.into(), 1),
|
S: ShardStore<H>,
|
||||||
root: parent(
|
const DEPTH: u8,
|
||||||
leaf(("abcd".to_string(), EPHEMERAL)),
|
const SHARD_HEIGHT: u8,
|
||||||
parent(nil(), leaf(("gh".to_string(), EPHEMERAL))),
|
> testing::Tree<H, C> for ShardTree<H, C, S, DEPTH, SHARD_HEIGHT>
|
||||||
),
|
{
|
||||||
};
|
fn depth(&self) -> u8 {
|
||||||
|
DEPTH
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(
|
fn append(&mut self, value: H, retention: Retention<C>) -> bool {
|
||||||
t.insert_subtree::<Infallible>(
|
ShardTree::append(self, value, retention).is_ok()
|
||||||
LocatedTree {
|
}
|
||||||
root_addr: Address::from_parts(1.into(), 6),
|
|
||||||
root: parent(leaf(("e".to_string(), MARKED)), nil())
|
fn current_position(&self) -> Option<Position> {
|
||||||
},
|
ShardTree::max_leaf_position(self, 0).ok().flatten()
|
||||||
true
|
}
|
||||||
),
|
|
||||||
Ok((
|
fn get_marked_leaf(&self, _position: Position) -> Option<&H> {
|
||||||
LocatedTree {
|
todo!()
|
||||||
root_addr: Address::from_parts(3.into(), 1),
|
}
|
||||||
root: parent(
|
|
||||||
leaf(("abcd".to_string(), EPHEMERAL)),
|
fn marked_positions(&self) -> BTreeSet<Position> {
|
||||||
parent(
|
todo!()
|
||||||
parent(leaf(("e".to_string(), MARKED)), nil()),
|
}
|
||||||
leaf(("gh".to_string(), EPHEMERAL))
|
|
||||||
)
|
fn root(&self, _checkpoint_depth: usize) -> Option<H> {
|
||||||
)
|
todo!()
|
||||||
},
|
}
|
||||||
vec![]
|
|
||||||
))
|
fn witness(&self, _position: Position, _checkpoint_depth: usize) -> Option<Vec<H>> {
|
||||||
);
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_mark(&mut self, _position: Position) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkpoint(&mut self, _checkpoint_id: C) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rewind(&mut self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn located_prunable_tree_witness() {
|
fn append() {
|
||||||
let t: LocatedPrunableTree<String> = LocatedTree {
|
check_append(|m| {
|
||||||
root_addr: Address::from_parts(3.into(), 0),
|
ShardTree::<String, usize, VecShardStore<String>, 4, 3>::new(vec![], m, 0)
|
||||||
root: parent(
|
});
|
||||||
leaf(("abcd".to_string(), EPHEMERAL)),
|
}
|
||||||
parent(
|
|
||||||
parent(
|
|
||||||
leaf(("e".to_string(), MARKED)),
|
|
||||||
leaf(("f".to_string(), EPHEMERAL)),
|
|
||||||
),
|
|
||||||
leaf(("gh".to_string(), EPHEMERAL)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
// Combined tree tests
|
||||||
t.witness(4.into(), 8.into()),
|
#[allow(clippy::type_complexity)]
|
||||||
Ok(vec!["f", "gh", "abcd"]
|
fn new_combined_tree<H: Hashable + Ord + Clone + core::fmt::Debug>(
|
||||||
.into_iter()
|
max_checkpoints: usize,
|
||||||
.map(|s| s.to_string())
|
) -> CombinedTree<
|
||||||
.collect())
|
H,
|
||||||
);
|
usize,
|
||||||
assert_eq!(
|
CompleteTree<H, usize, 4>,
|
||||||
t.witness(4.into(), 6.into()),
|
ShardTree<H, usize, VecShardStore<H>, 4, 3>,
|
||||||
Ok(vec!["f", "__", "abcd"]
|
> {
|
||||||
.into_iter()
|
CombinedTree::new(
|
||||||
.map(|s| s.to_string())
|
CompleteTree::new(max_checkpoints, 0),
|
||||||
.collect())
|
ShardTree::new(vec![], max_checkpoints, 0),
|
||||||
);
|
)
|
||||||
assert_eq!(
|
}
|
||||||
t.witness(4.into(), 7.into()),
|
|
||||||
Err(QueryError::TreeIncomplete(vec![Address::from_parts(
|
#[test]
|
||||||
1.into(),
|
fn combined_append() {
|
||||||
3
|
check_append(new_combined_tree);
|
||||||
)]))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue