pub mod batch;
#[cfg(test)]
pub(crate) mod conformance;
pub mod hasher;
#[cfg(feature = "std")]
mod persisted;
#[cfg(feature = "std")]
pub use persisted::{compact, full};
mod location;
pub mod mem;
pub mod mmb;
pub mod mmr;
pub mod path;
mod position;
mod proof;
mod read;
#[cfg(feature = "std")]
pub mod storage;
#[cfg(feature = "std")]
pub mod verification;
use alloc::vec::Vec;
use bytes::{Buf, BufMut};
use commonware_codec::{EncodeSize, Read, Write};
use commonware_cryptography::Digest;
use core::fmt::Debug;
pub use location::{Location, LocationRangeExt};
pub use position::Position;
#[cfg(test)]
pub(crate) use proof::build_range_proof;
pub use proof::{Proof, MAX_PROOF_DIGESTS_PER_ELEMENT};
pub use read::Readable;
use thiserror::Error;
pub const MAX_PINNED_NODES: usize = 64;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Bagging {
ForwardFold,
BackwardFold,
}
pub trait Family: Copy + Clone + Debug + Default + Send + Sync + 'static {
const MAX_NODES: Position<Self>;
const MAX_LEAVES: Location<Self>;
fn location_to_position(loc: Location<Self>) -> Position<Self>;
fn position_to_location(pos: Position<Self>) -> Option<Location<Self>>;
fn is_valid_size(size: Position<Self>) -> bool;
fn to_nearest_size(size: Position<Self>) -> Position<Self>;
fn peaks(size: Position<Self>) -> impl Iterator<Item = (Position<Self>, u32)> + Send;
fn inactive_peaks(size: Position<Self>, inactivity_floor: Location<Self>) -> usize {
let mut inactive_count = 0;
let mut leaf_capacity_sum = 0u64;
for (_, height) in Self::peaks(size) {
let capacity = 1u64.checked_shl(height).expect("height excessively large");
leaf_capacity_sum = leaf_capacity_sum
.checked_add(capacity)
.expect("capacity overflow");
if leaf_capacity_sum <= *inactivity_floor {
inactive_count += 1;
} else {
break;
}
}
inactive_count
}
fn nodes_to_pin(prune_loc: Location<Self>) -> impl Iterator<Item = Position<Self>> + Send {
let prune_pos = Self::location_to_position(prune_loc);
Self::peaks(prune_pos)
.filter(move |&(pos, _)| pos < prune_pos)
.map(|(pos, _)| pos)
.collect::<Vec<_>>()
.into_iter()
}
fn children(pos: Position<Self>, height: u32) -> (Position<Self>, Position<Self>);
fn parent_heights(leaves: Location<Self>) -> impl Iterator<Item = u32>;
fn pos_to_height(pos: Position<Self>) -> u32;
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Unused;
impl Write for Unused {
#[inline]
fn write(&self, _: &mut impl BufMut) {}
}
impl EncodeSize for Unused {
#[inline]
fn encode_size(&self) -> usize {
0
}
}
impl Read for Unused {
type Cfg = ();
#[inline]
fn read_cfg(_: &mut impl Buf, _: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
Ok(Self)
}
}
impl<D: Digest> TryFrom<Option<D>> for Unused {
type Error = commonware_codec::Error;
#[inline]
fn try_from(value: Option<D>) -> Result<Self, Self::Error> {
match value {
None => Ok(Self),
Some(_) => Err(commonware_codec::Error::Invalid(
"PendingChunk",
"pending chunk present for family without pending chunks",
)),
}
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Unused {
fn arbitrary(_: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
Ok(Self)
}
}
pub trait PendingChunk<D: Digest>:
Clone
+ Debug
+ Eq
+ Write
+ EncodeSize
+ Read<Cfg = ()>
+ Send
+ Sync
+ TryFrom<Option<D>, Error: Debug>
{
fn as_ref(&self) -> Option<&D> {
None
}
}
impl<D: Digest> PendingChunk<D> for Option<D> {
#[inline]
fn as_ref(&self) -> Option<&D> {
Self::as_ref(self)
}
}
impl<D: Digest> PendingChunk<D> for Unused {}
pub trait Graftable: Family {
type PendingChunk<D: Digest>: PendingChunk<D>;
fn chunk_peaks(
size: Position<Self>,
chunk_idx: u64,
grafting_height: u32,
) -> impl Iterator<Item = (Position<Self>, u32)> + Send;
fn subtree_root_position(leaf_start: Location<Self>, height: u32) -> Position<Self>;
fn leftmost_leaf(pos: Position<Self>, height: u32) -> Location<Self>;
fn peak_birth_size(pos: Position<Self>, height: u32) -> u64 {
let leftmost = *Self::leftmost_leaf(pos, height);
let width = 1u64.checked_shl(height).expect("height excessively large");
leftmost.checked_add(width).expect("birth size overflow")
}
}
#[derive(Debug, Error)]
pub enum Error<F: Family> {
#[error("{0} is not a leaf")]
NonLeaf(Position<F>),
#[error("{0} > MAX_NODES")]
PositionOverflow(Position<F>),
#[error("{0} > MAX_LEAVES")]
LocationOverflow(Location<F>),
#[error("range is empty")]
Empty,
#[error("range end out of bounds: {0}")]
RangeOutOfBounds(Location<F>),
#[error("invalid size: {0}")]
InvalidSize(u64),
#[error("leaf location out of bounds: {0}")]
LeafOutOfBounds(Location<F>),
#[error("element pruned: {0}")]
ElementPruned(Position<F>),
#[error("digest hidden behind synthetic accumulator: {0}")]
CompressedDigest(Position<F>),
#[error("invalid pinned nodes")]
InvalidPinnedNodes,
#[error("stale batch: base size {expected}, current size {actual}")]
StaleBatch {
expected: Position<F>,
actual: Position<F>,
},
#[error("ancestor dropped: expected size {expected}, actual size {actual}")]
AncestorDropped {
expected: Position<F>,
actual: Position<F>,
},
#[error("invalid proof")]
InvalidProof,
#[error("inactive peak count {requested} exceeds peak count {peaks}")]
InvalidInactivePeaks {
requested: usize,
peaks: usize,
},
#[error("root mismatch")]
RootMismatch,
#[error("missing digest: {0}")]
MissingDigest(Position<F>),
#[cfg(feature = "std")]
#[error("metadata error: {0}")]
Metadata(#[from] crate::metadata::Error),
#[cfg(feature = "std")]
#[error("journal error: {0}")]
Journal(#[from] crate::journal::Error),
#[cfg(feature = "std")]
#[error("runtime error: {0}")]
Runtime(#[from] commonware_runtime::Error),
#[error("missing node: {0}")]
MissingNode(Position<F>),
#[error("data corrupted: {0}")]
DataCorrupted(&'static str),
#[error("missing grafted leaf digest: {0}")]
MissingGraftedLeaf(Position<F>),
#[error("bit offset {0} out of bounds (size: {1})")]
BitOutOfBounds(u64, u64),
#[error("rewind beyond history")]
RewindBeyondHistory,
}