Merge pull request #90 from zcash/shardtree-docs

`shardtree`: Improve documentation
This commit is contained in:
str4d 2023-07-25 19:35:35 +01:00 committed by GitHub
commit da97e6c399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 96 additions and 25 deletions

View File

@ -1,3 +1,5 @@
//! Helpers for inserting many leaves into a tree at once.
use std::{collections::BTreeMap, fmt, ops::Range, sync::Arc};
use incrementalmerkletree::{Address, Hashable, Level, Position, Retention};
@ -31,6 +33,9 @@ impl<
/// introduced to the tree, as well as whether or not those newly introduced nodes will need to
/// be filled with values in order to produce witnesses for inserted leaves with
/// [`Retention::Marked`] retention.
///
/// This method operates on a single thread. If you have parallelism available, consider using
/// [`LocatedPrunableTree::from_iter`] and [`Self::insert_tree`] instead.
#[allow(clippy::type_complexity)]
pub fn batch_insert<I: Iterator<Item = (H, Retention<C>)>>(
&mut self,
@ -101,7 +106,6 @@ pub struct BatchInsertionResult<H, C: Ord, I: Iterator<Item = (H, Retention<C>)>
pub remainder: I,
}
/// Operations on [`LocatedTree`]s that are annotated with Merkle hashes.
impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
/// Append a values from an iterator, beginning at the first available position in the tree.
///
@ -126,7 +130,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
/// the end of the iterator is outside of the subtree's range, the unconsumed part of the
/// iterator will be returned as part of the result.
///
/// Returns `Ok(None)` if the provided iterator is empty, `Ok(Some<BatchInsertionResult>)` if
/// Returns `Ok(None)` if the provided iterator is empty, `Ok(Some(BatchInsertionResult))` if
/// values were successfully inserted, or an error if the start position provided is outside
/// of this tree's position range or if a conflict with an existing subtree root is detected.
pub fn batch_insert<C: Clone + Ord, I: Iterator<Item = (H, Retention<C>)>>(
@ -162,9 +166,10 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
/// This may be used in conjunction with [`ShardTree::insert_tree`] to support
/// partially-parallelizable tree construction. Multiple subtrees may be constructed in
/// parallel from iterators over (preferably, though not necessarily) disjoint leaf ranges, and
/// [`ShardTree::insert_tree`] may be used to insert those subtrees into the `ShardTree` in
/// [`ShardTree::insert_tree`] may be used to insert those subtrees into the [`ShardTree`] in
/// arbitrary order.
///
/// # Parameters:
/// * `position_range` - The range of leaf positions at which values will be inserted. This
/// range is also used to place an upper bound on the number of items that will be consumed
/// from the `values` iterator.
@ -172,7 +177,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
/// in order to construct a witness for a marked node or to make it possible to rewind to a
/// checkpointed node may be pruned so long as their address is at less than the specified
/// level.
/// * `values` The iterator of `(H, [`Retention`])` pairs from which to construct the tree.
/// * `values` - The iterator of `(H, Retention)` pairs from which to construct the tree.
pub fn from_iter<C: Clone + Ord, I: Iterator<Item = (H, Retention<C>)>>(
position_range: Range<Position>,
prune_below: Level,

View File

@ -1,8 +1,16 @@
//! Error types for this crate.
use std::fmt;
use std::ops::Range;
use incrementalmerkletree::{Address, Position};
/// The error type for operations on a [`ShardTree`].
///
/// The parameter `S` is set to [`ShardStore::Error`].
///
/// [`ShardTree`]: crate::ShardTree
/// [`ShardStore::Error`]: crate::ShardStore::Error
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShardTreeError<S> {
Query(QueryError),
@ -50,7 +58,7 @@ where
}
}
/// An error prevented the insertion of values into the subtree.
/// Errors which can occur when inserting values into a subtree.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InsertionError {
/// The caller attempted to insert a subtree into a tree that does not contain
@ -103,7 +111,9 @@ impl fmt::Display for InsertionError {
impl std::error::Error for InsertionError {}
/// Errors that may be returned in the process of querying a [`ShardTree`]
/// Errors which can occur when querying a [`ShardTree`].
///
/// [`ShardTree`]: crate::ShardTree
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum QueryError {
/// The caller attempted to query the value at an address within a tree that does not contain
@ -114,6 +124,8 @@ pub enum QueryError {
CheckpointPruned,
/// It is not possible to compute a root for one or more subtrees because they contain
/// [`Node::Nil`] values at positions that cannot be replaced with default hashes.
///
/// [`Node::Nil`]: crate::Node::Nil
TreeIncomplete(Vec<Address>),
}

View File

@ -1,3 +1,8 @@
//! `shardtree` is a space-efficient fixed-depth Merkle tree that supports:
//! - Out-of-order insertion.
//! - Witnessing of marked leaves.
//! - Checkpointing, and state restoration to a checkpoint.
use core::fmt::Debug;
use either::Either;
use std::collections::{BTreeMap, BTreeSet};
@ -61,7 +66,7 @@ impl<
}
}
/// Consumes this tree and returns its underlying `ShardStore`.
/// Consumes this tree and returns its underlying [`ShardStore`].
pub fn into_store(self) -> S {
self.store
}
@ -82,7 +87,7 @@ impl<
Address::above_position(Self::subtree_level(), pos)
}
pub fn max_subtree_index() -> u64 {
fn max_subtree_index() -> u64 {
(0x1 << (DEPTH - SHARD_HEIGHT)) - 1
}
@ -513,9 +518,9 @@ impl<
/// 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.
/// This will also discard all checkpoints with depth less than or equal to 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_to_depth(
&mut self,
checkpoint_depth: usize,
@ -536,9 +541,9 @@ impl<
/// Truncates the tree, discarding all information after the specified checkpoint.
///
/// 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 for the
/// specified checkpoint identifier.
/// This will also discard all checkpoints with depth less than or equal to the specified depth.
/// Returns `true` if the truncation succeeds or has no effect, or `false` if no checkpoint
/// exists for the specified checkpoint identifier.
pub fn truncate_removing_checkpoint(
&mut self,
checkpoint_id: &C,

View File

@ -1,3 +1,5 @@
//! Helpers for working with trees that support pruning unneeded leaves and branches.
use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
@ -11,6 +13,7 @@ use crate::error::{InsertionError, QueryError};
use crate::{LocatedTree, Node, Tree};
bitflags! {
/// Flags storing the [`Retention`] state of a leaf.
pub struct RetentionFlags: u8 {
/// An leaf with `EPHEMERAL` retention can be pruned as soon as we are certain that it is not part
/// of the witness for a leaf with [`CHECKPOINT`] or [`MARKED`] retention.
@ -61,6 +64,7 @@ impl<C> From<Retention<C>> for RetentionFlags {
}
}
/// A [`Tree`] annotated with Merkle hashes.
pub type PrunableTree<H> = Tree<Option<Arc<H>>, (H, RetentionFlags)>;
impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
@ -98,7 +102,7 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
/// a vector of the addresses of `Nil` nodes that inhibited the computation of
/// such a root.
///
/// ### Parameters:
/// # Parameters:
/// * `truncate_at` An inclusive lower bound on positions in the tree beyond which all leaf
/// values will be treated as `Nil`.
pub fn root_hash(&self, root_addr: Address, truncate_at: Position) -> Result<H, Vec<Address>> {
@ -309,6 +313,7 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
}
}
/// A [`LocatedTree`] annotated with Merkle hashes.
pub type LocatedPrunableTree<H> = LocatedTree<Option<Arc<H>>, (H, RetentionFlags)>;
/// A data structure describing the nature of a [`Node::Nil`] node in the tree that was introduced
@ -324,7 +329,6 @@ pub struct IncompleteAt {
pub required_for_witness: bool,
}
/// Operations on [`LocatedTree`]s that are annotated with Merkle hashes.
impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
/// Computes the root hash of this tree, truncated to the given position.
///
@ -782,7 +786,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
/// Inserts leaves and subtree roots from the provided frontier into this tree, up to the level
/// of this tree's root.
///
/// Returns the updated tree, along with a `LocatedPrunableTree` containing only the remainder
/// Returns the updated tree, along with a [`LocatedPrunableTree`] containing only the remainder
/// of the frontier's ommers that had addresses at levels greater than the root of this tree.
///
/// Returns an error in the following cases:

View File

@ -1,3 +1,26 @@
//! Traits and structs for storing [`ShardTree`]s.
//!
//! [`ShardTree`]: crate::ShardTree
//!
//! # Structure
//!
//! The tree is represented as an ordered collection of fixed-depth subtrees, or "shards".
//! Each shard is a [`LocatedPrunableTree`]. The roots of the shards form the leaves in
//! the "cap", which is a [`PrunableTree`].
//!
//! ```text
//! Level
//! 3 root \
//! / \ |
//! / \ |
//! 2 / \ } cap
//! / \ / \ |
//! / \ / \ |
//! 1 A B C D / \
//! / \ / \ / \ / \ } shards
//! 0 /\ /\ /\ /\ /\ /\ /\ /\ /
//! ```
use std::collections::BTreeSet;
use incrementalmerkletree::{Address, Position};
@ -7,12 +30,19 @@ use crate::{LocatedPrunableTree, PrunableTree};
pub mod caching;
pub mod memory;
/// A capability for storage of fragment subtrees of the `ShardTree` type.
/// A capability for storage of fragment subtrees of the [`ShardTree`] type.
///
/// All fragment subtrees must have roots at level `SHARD_HEIGHT`
/// All fragment subtrees must have roots at level `SHARD_HEIGHT`.
///
/// [`ShardTree`]: crate::ShardTree
pub trait ShardStore {
/// The type used for leaves and nodes in the tree.
type H;
/// The type used to identify checkpointed positions in the tree.
type CheckpointId;
/// The error type for operations on this store.
type Error: std::error::Error;
/// Returns the subtree at the given root address, if any such subtree exists.
@ -221,6 +251,9 @@ pub enum TreeState {
AtPosition(Position),
}
/// The information required to save the state of a [`ShardTree`] at some [`Position`].
///
/// [`ShardTree`]: crate::ShardTree
#[derive(Clone, Debug)]
pub struct Checkpoint {
tree_state: TreeState,

View File

@ -8,6 +8,9 @@ use incrementalmerkletree::Address;
use super::{Checkpoint, ShardStore};
use crate::{LocatedPrunableTree, LocatedTree, Node, PrunableTree, Tree};
/// An implementation of [`ShardStore`] that stores all state in memory.
///
/// State is not persisted anywhere, and will be lost when the struct is dropped.
#[derive(Debug)]
pub struct MemoryShardStore<H, C: Ord> {
shards: Vec<LocatedPrunableTree<H>>,
@ -16,6 +19,7 @@ pub struct MemoryShardStore<H, C: Ord> {
}
impl<H, C: Ord> MemoryShardStore<H, C> {
/// Constructs a new empty `MemoryShardStore`.
pub fn empty() -> Self {
Self {
shards: vec![],

View File

@ -1,3 +1,5 @@
//! The core tree types.
use std::ops::Deref;
use std::sync::Arc;
@ -22,7 +24,7 @@ pub enum Node<C, A, V> {
impl<C, A, V> Node<C, A, V> {
/// Returns whether or not this is the `Nil` tree.
///
/// This is useful for cases where the compiler can automatically dereference an `Rc`, where
/// This is useful for cases where the compiler can automatically dereference an [`Arc`], where
/// one would otherwise need additional ceremony to make an equality check.
pub fn is_nil(&self) -> bool {
matches!(self, Node::Nil)
@ -37,6 +39,7 @@ impl<C, A, V> Node<C, A, V> {
}
}
/// Returns the annotation, if this is a parent node.
pub fn annotation(&self) -> Option<&A> {
match self {
Node::Parent { ann, .. } => Some(ann),
@ -45,7 +48,7 @@ impl<C, A, V> Node<C, A, V> {
}
}
/// Replaces the annotation on this node, if it is a `Node::Parent`; otherwise
/// Replaces the annotation on this node, if it is a [`Node::Parent`]; otherwise
/// returns this node unaltered.
pub fn reannotate(self, ann: A) -> Self {
match self {
@ -56,6 +59,7 @@ impl<C, A, V> Node<C, A, V> {
}
impl<'a, C: Clone, A: Clone, V: Clone> Node<C, &'a A, &'a V> {
/// Maps a `Node<C, &A, &V>` to a `Node<C, A, V>` by cloning the contents of the node.
pub fn cloned(&self) -> Node<C, A, V> {
match self {
Node::Parent { ann, left, right } => Node::Parent {
@ -83,14 +87,17 @@ impl<A, V> Deref for Tree<A, V> {
}
impl<A, V> Tree<A, V> {
/// Constructs the empty tree.
pub fn empty() -> Self {
Tree(Node::Nil)
}
/// Constructs a tree containing a single leaf.
pub fn leaf(value: V) -> Self {
Tree(Node::Leaf { value })
}
/// Constructs a tree containing a pair of leaves.
pub fn parent(ann: A, left: Self, right: Self) -> Self {
Tree(Node::Parent {
ann,
@ -99,12 +106,13 @@ impl<A, V> Tree<A, V> {
})
}
/// Returns `true` if the tree has no leaves.
pub fn is_empty(&self) -> bool {
self.0.is_nil()
}
/// Replaces the annotation at the root of the tree, if the root is a `Node::Parent`; otherwise
/// returns this tree unaltered.
/// Replaces the annotation at the root of the tree, if the root is a [`Node::Parent`];
/// otherwise returns this tree unaltered.
pub fn reannotate_root(self, ann: A) -> Self {
Tree(self.0.reannotate(ann))
}
@ -175,7 +183,7 @@ impl<A, V> LocatedTree<A, V> {
}
/// Returns a new [`LocatedTree`] with the provided value replacing the annotation of its root
/// node, if that root node is a `Node::Parent`. Otherwise .
/// node, if that root node is a [`Node::Parent`]. Otherwise returns this tree unaltered.
pub fn reannotate_root(self, value: A) -> Self {
LocatedTree {
root_addr: self.root_addr,
@ -189,7 +197,7 @@ impl<A, V> LocatedTree<A, V> {
self.root.incomplete_nodes(self.root_addr)
}
/// Returns the maximum position at which a non-Nil leaf has been observed in the tree.
/// Returns the maximum position at which a non-`Nil` leaf has been observed in the tree.
///
/// Note that no actual leaf value may exist at this position, as it may have previously been
/// pruned.