use crate::merkle::{
self, hasher::Hasher as HasherTrait, storage::Storage as StorageTrait, Family, Graftable,
Location, Position, Readable,
};
use commonware_cryptography::{Digest, Hasher as CHasher};
use commonware_utils::bitmap::BitMap;
use core::{cmp::Ordering, marker::PhantomData};
use tracing::debug;
pub const fn height<const N: usize>() -> u32 {
BitMap::<N>::CHUNK_SIZE_BITS.trailing_zeros()
}
pub fn graftable_chunks<F: Graftable>(ops_leaves: u64, grafting_height: u32) -> u64 {
assert!(grafting_height >= 1, "grafting_height must be >= 1");
let pos = F::subtree_root_position(Location::<F>::new(0), grafting_height);
let birth_chunk_0 = F::peak_birth_size(pos, grafting_height);
if ops_leaves < birth_chunk_0 {
return 0;
}
let chunk_size = 1u64 << grafting_height;
(ops_leaves - birth_chunk_0) / chunk_size + 1
}
pub(super) fn chunk_aligned_inactive_peaks<F: Family>(
leaves: Location<F>,
inactivity_floor: Location<F>,
grafting_height: u32,
) -> Result<usize, merkle::Error<F>> {
let size = F::location_to_position(leaves);
let chunk_size = 1u64 << grafting_height;
let floor = *inactivity_floor;
let mut leaf_end = 0u64;
let mut aligned_count = 0usize;
for (idx, (_pos, height)) in F::peaks(size).enumerate() {
let next_leaf_end = leaf_end + (1u64 << height);
if next_leaf_end > floor {
break;
}
leaf_end = next_leaf_end;
if leaf_end.is_multiple_of(chunk_size) {
aligned_count = idx + 1;
}
}
Ok(aligned_count)
}
pub fn ops_to_grafted_pos<F: Graftable>(ops_pos: Position<F>, grafting_height: u32) -> Position<F> {
let ops_height = F::pos_to_height(ops_pos);
assert!(
ops_height >= grafting_height,
"position height {ops_height} < grafting height {grafting_height}"
);
let grafted_height = ops_height - grafting_height;
let ops_leaf_loc = F::leftmost_leaf(ops_pos, ops_height);
let chunk_idx = *ops_leaf_loc >> grafting_height;
let grafted_leaf_loc = Location::<F>::new(chunk_idx);
F::subtree_root_position(grafted_leaf_loc, grafted_height)
}
pub fn grafted_to_ops_pos<F: Graftable>(
grafted_pos: Position<F>,
grafting_height: u32,
) -> Position<F> {
let grafted_height = F::pos_to_height(grafted_pos);
let grafted_leaf = F::leftmost_leaf(grafted_pos, grafted_height);
let ops_leaf_start = Location::<F>::new(*grafted_leaf << grafting_height);
let ops_height = grafted_height + grafting_height;
F::subtree_root_position(ops_leaf_start, ops_height)
}
#[derive(Clone)]
pub(super) struct GraftedHasher<F: Graftable, H: HasherTrait<F>> {
inner: H,
grafting_height: u32,
_family: PhantomData<F>,
}
impl<F: Graftable, H: HasherTrait<F>> GraftedHasher<F, H> {
pub(super) const fn new(inner: H, grafting_height: u32) -> Self {
Self {
inner,
grafting_height,
_family: PhantomData,
}
}
}
impl<F: Graftable, H: HasherTrait<F>> HasherTrait<F> for GraftedHasher<F, H> {
type Digest = H::Digest;
fn hash<'a>(&self, parts: impl IntoIterator<Item = &'a [u8]>) -> Self::Digest {
self.inner.hash(parts)
}
fn root_bagging(&self) -> merkle::Bagging {
self.inner.root_bagging()
}
fn node_digest(
&self,
pos: Position<F>,
left: &Self::Digest,
right: &Self::Digest,
) -> Self::Digest {
let ops_pos = grafted_to_ops_pos::<F>(pos, self.grafting_height);
self.inner.node_digest(ops_pos, left, right)
}
}
#[derive(Clone)]
pub(super) struct Verifier<'a, F: Graftable, H: CHasher> {
hasher: merkle::hasher::Standard<H>,
grafting_height: u32,
chunks: Vec<&'a [u8]>,
start_chunk_index: u64,
graftable_chunks: u64,
_ops_family: PhantomData<F>,
}
impl<'a, F: Graftable, H: CHasher> Verifier<'a, F, H> {
pub(super) const fn new(
grafting_height: u32,
start_chunk_index: u64,
chunks: Vec<&'a [u8]>,
graftable_chunks: u64,
bagging: merkle::Bagging,
) -> Self {
Self {
hasher: merkle::hasher::Standard::new(bagging),
grafting_height,
chunks,
start_chunk_index,
graftable_chunks,
_ops_family: PhantomData,
}
}
}
impl<F: Graftable, H: CHasher> HasherTrait<F> for Verifier<'_, F, H> {
type Digest = H::Digest;
fn hash<'a>(&self, parts: impl IntoIterator<Item = &'a [u8]>) -> H::Digest {
self.hasher.hash(parts)
}
fn root_bagging(&self) -> merkle::Bagging {
<merkle::hasher::Standard<H> as HasherTrait<F>>::root_bagging(&self.hasher)
}
fn node_digest(
&self,
pos: merkle::Position<F>,
left_digest: &H::Digest,
right_digest: &H::Digest,
) -> H::Digest {
match F::pos_to_height(pos).cmp(&self.grafting_height) {
Ordering::Less | Ordering::Greater => {
self.hasher.node_digest(pos, left_digest, right_digest)
}
Ordering::Equal => {
let ops_subtree_root = self.hasher.node_digest(pos, left_digest, right_digest);
let loc = F::leftmost_leaf(pos, self.grafting_height);
let chunk_idx = *loc >> self.grafting_height;
if chunk_idx >= self.graftable_chunks {
debug!(?chunk_idx, "skipping pending chunk");
return ops_subtree_root;
}
let Some(local) = chunk_idx
.checked_sub(self.start_chunk_index)
.filter(|&l| l < self.chunks.len() as u64)
.map(|l| l as usize)
else {
debug!(?pos, "chunk not available for grafted leaf");
return ops_subtree_root;
};
let chunk = self.chunks[local];
if chunk.iter().all(|&b| b == 0) {
ops_subtree_root
} else {
self.hash([chunk, ops_subtree_root.as_ref()])
}
}
}
}
}
pub(super) struct Storage<
'a,
F: Graftable,
D: Digest,
G: Readable<Family = F, Digest = D, Error = merkle::Error<F>>,
S: StorageTrait<F, Digest = D>,
H: HasherTrait<F, Digest = D> + Clone,
> {
grafted_tree: &'a G,
grafting_height: u32,
ops_tree: &'a S,
grafted_hasher: GraftedHasher<F, H>,
_phantom: PhantomData<(F, D)>,
}
impl<
'a,
F: Graftable,
D: Digest,
G: Readable<Family = F, Digest = D, Error = merkle::Error<F>>,
S: StorageTrait<F, Digest = D>,
H: HasherTrait<F, Digest = D> + Clone,
> Storage<'a, F, D, G, S, H>
{
pub(super) const fn new(
grafted_tree: &'a G,
grafting_height: u32,
ops_tree: &'a S,
hasher: H,
) -> Self {
Self {
grafted_tree,
grafting_height,
ops_tree,
grafted_hasher: GraftedHasher::new(hasher, grafting_height),
_phantom: PhantomData,
}
}
fn reconstruct_grafted_node(&self, pos: Position<F>) -> Option<D> {
if let Some(node) = self.grafted_tree.get_node(pos) {
return Some(node);
}
let height = F::pos_to_height(pos);
if height == 0 {
return None;
}
let (left, right) = F::children(pos, height);
let left_digest = self.reconstruct_grafted_node(left)?;
let right_digest = self.reconstruct_grafted_node(right)?;
Some(
self.grafted_hasher
.node_digest(pos, &left_digest, &right_digest),
)
}
}
impl<
F: Graftable,
D: Digest,
G: Readable<Family = F, Digest = D, Error = merkle::Error<F>>,
S: StorageTrait<F, Digest = D>,
H: HasherTrait<F, Digest = D> + Clone + Send + Sync,
> StorageTrait<F> for Storage<'_, F, D, G, S, H>
{
type Digest = D;
async fn size(&self) -> Position<F> {
self.ops_tree.size().await
}
async fn get_node(&self, pos: Position<F>) -> Result<Option<D>, merkle::Error<F>> {
let ops_height = F::pos_to_height(pos);
if ops_height < self.grafting_height {
return self.ops_tree.get_node(pos).await;
}
let grafted_pos = ops_to_grafted_pos::<F>(pos, self.grafting_height);
Ok(self.reconstruct_grafted_node(grafted_pos))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
merkle::{conformance::build_test_mmr, Bagging::ForwardFold},
mmb, mmr,
mmr::{
iterator::{pos_to_height, PeakIterator},
mem::Mmr,
verification, Location, Position, StandardHasher,
},
};
use commonware_cryptography::{sha256, Sha256};
use commonware_macros::test_traced;
use commonware_runtime::{deterministic, Runner};
const ALL_CHUNKS_GRAFTABLE: u64 = u64::MAX;
fn count_single_peak_chunks<F: Graftable>(ops_leaves: u64, grafting_height: u32) -> u64 {
let chunk_size = 1u64 << grafting_height;
let total_complete = ops_leaves / chunk_size;
if total_complete == 0 {
return 0;
}
let size = F::location_to_position(crate::merkle::Location::<F>::new(ops_leaves));
let mut count = 0u64;
for chunk_idx in 0..total_complete {
let mut iter = F::chunk_peaks(size, chunk_idx, grafting_height);
let _first = iter.next();
if iter.next().is_none() {
count += 1;
}
}
count
}
fn graftable_chunks_matches_oracle<F: Graftable>() {
for grafting_height in 1u32..=8 {
let chunk_size = 1u64 << grafting_height;
let n_max = (chunk_size * 80).min(2000);
for ops_leaves in 0u64..=n_max {
let graftable = graftable_chunks::<F>(ops_leaves, grafting_height);
let oracle = count_single_peak_chunks::<F>(ops_leaves, grafting_height);
assert_eq!(
graftable, oracle,
"mismatch: family graftable={graftable}, oracle={oracle}, ops_leaves={ops_leaves}, G={grafting_height}"
);
let complete = ops_leaves / chunk_size;
assert!(
graftable <= complete,
"graftable {graftable} exceeded complete {complete} (ops_leaves={ops_leaves}, G={grafting_height})"
);
assert!(
complete - graftable <= 1,
"complete-graftable gap > 1: complete={complete}, graftable={graftable}, ops_leaves={ops_leaves}, G={grafting_height}"
);
}
}
}
#[test]
fn test_graftable_chunks_mmr_matches_chunk_peaks() {
graftable_chunks_matches_oracle::<mmr::Family>();
}
#[test]
fn test_graftable_chunks_mmb_matches_chunk_peaks() {
graftable_chunks_matches_oracle::<mmb::Family>();
}
#[test]
fn test_graftable_chunks_mmr_no_pending() {
for grafting_height in 1u32..=8 {
let chunk_size = 1u64 << grafting_height;
let n_max = (chunk_size * 80).min(2000);
for ops_leaves in 0u64..=n_max {
let graftable = graftable_chunks::<mmr::Family>(ops_leaves, grafting_height);
let complete = ops_leaves / chunk_size;
assert_eq!(
graftable, complete,
"MMR has unexpected pending state: ops_leaves={ops_leaves}, G={grafting_height}, graftable={graftable}, complete={complete}"
);
}
}
}
#[test]
fn test_graftable_chunks_mmb_at_most_one_pending() {
for grafting_height in 1u32..=8 {
let chunk_size = 1u64 << grafting_height;
let n_max = (chunk_size * 80).min(2000);
for ops_leaves in 0u64..=n_max {
let graftable = graftable_chunks::<mmb::Family>(ops_leaves, grafting_height);
let complete = ops_leaves / chunk_size;
let pending = complete.saturating_sub(graftable);
assert!(
pending <= 1,
"MMB has {pending} pending chunks (ops_leaves={ops_leaves}, G={grafting_height}); should be <= 1"
);
}
}
}
#[test]
#[should_panic(expected = "grafting_height must be >= 1")]
fn test_graftable_chunks_rejects_height_zero() {
let _ = graftable_chunks::<mmr::Family>(100, 0);
}
#[test]
fn test_verifier_graftable_chunks_zero_skips_chunk_combine() {
const GH: u32 = 1;
let chunk: [u8; 1] = [0xAB];
let left = Sha256::fill(0x01);
let right = Sha256::fill(0x02);
let pos_at_g = mmr::Family::subtree_root_position(Location::new(0), GH);
let standard: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
let expected_no_combine = <StandardHasher<Sha256> as HasherTrait<mmr::Family>>::node_digest(
&standard, pos_at_g, &left, &right,
);
let v = Verifier::<mmr::Family, Sha256>::new(GH, 0, vec![&chunk], 0, ForwardFold);
let got = <Verifier<'_, mmr::Family, Sha256> as HasherTrait<mmr::Family>>::node_digest(
&v, pos_at_g, &left, &right,
);
assert_eq!(
got, expected_no_combine,
"graftable_chunks=0 must skip chunk combination at the grafting height"
);
let v_graftable = Verifier::<mmr::Family, Sha256>::new(GH, 0, vec![&chunk], 1, ForwardFold);
let got_graftable =
<Verifier<'_, mmr::Family, Sha256> as HasherTrait<mmr::Family>>::node_digest(
&v_graftable,
pos_at_g,
&left,
&right,
);
assert_ne!(got, got_graftable);
}
fn ops_pos_to_chunk_idx(ops_pos: Position, grafting_height: u32) -> u64 {
let loc = mmr::Family::leftmost_leaf(ops_pos, grafting_height);
*loc >> grafting_height
}
fn chunk_idx_to_ops_pos(chunk_idx: u64, grafting_height: u32) -> Position {
let first_leaf_loc = Location::new(chunk_idx << grafting_height);
mmr::Family::subtree_root_position(first_leaf_loc, grafting_height)
}
fn build_test_grafted_mmr(
standard: &StandardHasher<Sha256>,
ops_mmr: &Mmr<sha256::Digest>,
chunks: &[sha256::Digest],
grafting_height: u32,
) -> Mmr<sha256::Digest> {
let grafted_hasher =
GraftedHasher::<mmr::Family, _>::new(standard.clone(), grafting_height);
let mut grafted_mmr = Mmr::new();
if !chunks.is_empty() {
let leaf_hasher = StandardHasher::<Sha256>::new(ForwardFold);
let batch = {
let mut batch = grafted_mmr.new_batch();
for (i, chunk) in chunks.iter().enumerate() {
let ops_pos = chunk_idx_to_ops_pos(i as u64, grafting_height);
let ops_subtree_root = ops_mmr
.get_node(ops_pos)
.expect("ops tree missing node at mapped position");
batch = batch.add_leaf_digest(
leaf_hasher.hash([chunk.as_ref(), ops_subtree_root.as_ref()]),
);
}
batch.merkleize(&grafted_mmr, &grafted_hasher)
};
grafted_mmr.apply_batch(&batch).unwrap();
}
grafted_mmr
}
#[test_traced]
fn test_chunk_idx_to_ops_pos_roundtrip() {
for grafting_height in 1..10 {
for chunk_idx in 0..1000u64 {
let ops_pos = chunk_idx_to_ops_pos(chunk_idx, grafting_height);
assert_eq!(
pos_to_height(ops_pos),
grafting_height,
"chunk_idx_to_ops_pos should return a position at the grafting height"
);
let back = ops_pos_to_chunk_idx(ops_pos, grafting_height);
assert_eq!(chunk_idx, back);
}
}
}
#[test_traced]
fn test_ops_to_grafted_pos_leaves() {
for grafting_height in 1..8 {
for chunk_idx in 0..200u64 {
let ops_pos = chunk_idx_to_ops_pos(chunk_idx, grafting_height);
let grafted_pos = ops_to_grafted_pos(ops_pos, grafting_height);
let expected = *Position::try_from(Location::new(chunk_idx)).unwrap();
assert_eq!(
grafted_pos, expected,
"leaf mismatch: chunk_idx={chunk_idx}, gh={grafting_height}"
);
}
}
}
#[test_traced]
fn test_ops_grafted_roundtrip() {
for grafting_height in 1..6 {
for chunk_idx in 0..100u64 {
let ops_pos = chunk_idx_to_ops_pos(chunk_idx, grafting_height);
let grafted_pos = ops_to_grafted_pos(ops_pos, grafting_height);
let back = grafted_to_ops_pos(grafted_pos, grafting_height);
assert_eq!(
ops_pos, back,
"leaf roundtrip failed: chunk={chunk_idx}, gh={grafting_height}"
);
}
for chunk_idx in (0..100u64).step_by(2) {
let left_ops = chunk_idx_to_ops_pos(chunk_idx, grafting_height);
let parent_ops = Position::new(*left_ops + (1u64 << (grafting_height + 1)));
if pos_to_height(parent_ops) < grafting_height {
continue;
}
let grafted_pos = ops_to_grafted_pos(parent_ops, grafting_height);
let back = grafted_to_ops_pos(grafted_pos, grafting_height);
assert_eq!(
parent_ops, back,
"internal roundtrip failed: chunk={chunk_idx}, gh={grafting_height}"
);
}
}
}
#[test_traced]
fn test_ops_to_grafted_pos_known_values() {
assert_eq!(ops_to_grafted_pos(Position::new(2), 1), 0);
assert_eq!(ops_to_grafted_pos(Position::new(5), 1), 1);
assert_eq!(ops_to_grafted_pos(Position::new(6), 1), 2);
assert_eq!(ops_to_grafted_pos(Position::new(9), 1), 3);
assert_eq!(ops_to_grafted_pos(Position::new(12), 1), 4);
assert_eq!(ops_to_grafted_pos(Position::new(13), 1), 5);
assert_eq!(ops_to_grafted_pos(Position::new(14), 1), 6);
}
#[test_traced]
fn test_grafted_leaf_computation() {
let executor = deterministic::Runner::default();
executor.start(|_| async move {
const NUM_ELEMENTS: u64 = 200;
let standard: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
let mmr = Mmr::new();
let ops_mmr = build_test_mmr(&standard, mmr, NUM_ELEMENTS);
let elements: Vec<_> = (0..NUM_ELEMENTS)
.map(|i| standard.digest(&i.to_be_bytes()))
.collect();
{
assert_eq!(chunk_idx_to_ops_pos(0, 0), Position::new(0));
assert_eq!(chunk_idx_to_ops_pos(1, 0), Position::new(1));
let grafted = build_test_grafted_mmr(&standard, &ops_mmr, &elements, 0);
let gp = ops_to_grafted_pos(chunk_idx_to_ops_pos(0, 0), 0);
assert!(grafted.get_node(gp).is_some());
}
let ops_mmr = build_test_mmr(&standard, ops_mmr, NUM_ELEMENTS);
{
assert_eq!(chunk_idx_to_ops_pos(0, 1), Position::new(2));
assert_eq!(chunk_idx_to_ops_pos(1, 1), Position::new(5));
assert_eq!(chunk_idx_to_ops_pos(2, 1), Position::new(9));
assert_eq!(chunk_idx_to_ops_pos(3, 1), Position::new(12));
assert_eq!(chunk_idx_to_ops_pos(4, 1), Position::new(17));
let grafted = build_test_grafted_mmr(&standard, &ops_mmr, &elements, 1);
let gp = ops_to_grafted_pos(chunk_idx_to_ops_pos(0, 1), 1);
assert!(grafted.get_node(gp).is_some());
}
assert_eq!(chunk_idx_to_ops_pos(0, 2), Position::new(6));
assert_eq!(chunk_idx_to_ops_pos(1, 2), Position::new(13));
assert_eq!(chunk_idx_to_ops_pos(0, 3), Position::new(14));
});
}
#[test_traced]
fn test_merkleize_grafted() {
let standard: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
let grafting_height = 1u32;
let mut ops_mmr = Mmr::new();
let batch = {
let mut batch = ops_mmr.new_batch();
for i in 0u8..4 {
batch = batch.add(&standard, &Sha256::fill(i));
}
batch.merkleize(&ops_mmr, &standard)
};
ops_mmr.apply_batch(&batch).unwrap();
let c1 = Sha256::fill(0xF1);
let c2 = Sha256::fill(0xF2);
let grafted_hasher = GraftedHasher::<mmr::Family, _>::new(standard, grafting_height);
let mut grafted = Mmr::new();
let pos0 = chunk_idx_to_ops_pos(0, grafting_height);
let pos1 = chunk_idx_to_ops_pos(1, grafting_height);
let batch = {
let leaf_hasher = StandardHasher::<Sha256>::new(ForwardFold);
let sub0 = ops_mmr.get_node(pos0).unwrap();
let batch = grafted
.new_batch()
.add_leaf_digest(leaf_hasher.hash([c1.as_ref(), sub0.as_ref()]));
let sub1 = ops_mmr.get_node(pos1).unwrap();
batch
.add_leaf_digest(leaf_hasher.hash([c2.as_ref(), sub1.as_ref()]))
.merkleize(&grafted, &grafted_hasher)
};
grafted.apply_batch(&batch).unwrap();
let gp0 = ops_to_grafted_pos(pos0, grafting_height);
let gp1 = ops_to_grafted_pos(pos1, grafting_height);
let gp_root = ops_to_grafted_pos(Position::new(6), grafting_height);
assert!(grafted.get_node(gp0).is_some());
assert!(grafted.get_node(gp1).is_some());
assert!(grafted.get_node(gp_root).is_some());
}
#[test_traced]
fn test_grafted_storage_proofs() {
let executor = deterministic::Runner::default();
const GRAFTING_HEIGHT: u32 = 1;
executor.start(|_| async move {
let b1 = Sha256::fill(0x01);
let b2 = Sha256::fill(0x02);
let b3 = Sha256::fill(0x03);
let b4 = Sha256::fill(0x04);
let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
let mut ops_mmr = Mmr::new();
let batch = {
let mut batch = ops_mmr.new_batch();
batch = batch.add(&hasher, &b1);
batch = batch.add(&hasher, &b2);
batch = batch.add(&hasher, &b3);
batch = batch.add(&hasher, &b4);
batch.merkleize(&ops_mmr, &hasher)
};
ops_mmr.apply_batch(&batch).unwrap();
let c1 = Sha256::fill(0xF1);
let c2 = Sha256::fill(0xF2);
let grafted = build_test_grafted_mmr(&hasher, &ops_mmr, &[c1, c2], GRAFTING_HEIGHT);
let ops_root = ops_mmr.root(&hasher, 0).unwrap();
{
let combined = Storage::new(&grafted, GRAFTING_HEIGHT, &ops_mmr, hasher.clone());
assert_eq!(combined.size().await, ops_mmr.size());
let grafted_root = {
let ops_size = ops_mmr.size();
let ops_leaves = Location::try_from(ops_size).unwrap();
let mut peaks = Vec::new();
for (peak_pos, peak_height) in PeakIterator::new(ops_size) {
if peak_height >= GRAFTING_HEIGHT {
let gp = ops_to_grafted_pos(peak_pos, GRAFTING_HEIGHT);
peaks.push(grafted.get_node(gp).unwrap());
} else {
peaks.push(combined.get_node(peak_pos).await.unwrap().unwrap());
}
}
hasher
.root(ops_leaves, 0, peaks.iter())
.expect("zero inactive peaks is always valid")
};
assert_ne!(grafted_root, ops_root);
{
let loc = Location::new(0);
let proof = verification::range_proof(&hasher, &combined, loc..loc + 1, 0)
.await
.unwrap();
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
0,
vec![&c1],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
assert!(proof.verify_element_inclusion(&verifier, &b1, loc, &grafted_root));
let loc = Location::new(1);
let proof = verification::range_proof(&hasher, &combined, loc..loc + 1, 0)
.await
.unwrap();
assert!(proof.verify_element_inclusion(&verifier, &b2, loc, &grafted_root));
let loc = Location::new(2);
let proof = verification::range_proof(&hasher, &combined, loc..loc + 1, 0)
.await
.unwrap();
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
1,
vec![&c2],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
assert!(proof.verify_element_inclusion(&verifier, &b3, loc, &grafted_root));
let loc = Location::new(3);
let proof = verification::range_proof(&hasher, &combined, loc..loc + 1, 0)
.await
.unwrap();
assert!(proof.verify_element_inclusion(&verifier, &b4, loc, &grafted_root));
}
{
let loc = Location::new(3);
let proof = verification::range_proof(&hasher, &combined, loc..loc + 1, 0)
.await
.unwrap();
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
1,
vec![&c2],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
assert!(proof.verify_element_inclusion(&verifier, &b4, loc, &grafted_root));
assert!(!proof.verify_element_inclusion(&verifier, &b3, loc, &grafted_root));
assert!(!proof.verify_element_inclusion(&verifier, &b4, loc, &ops_root));
assert!(!proof.verify_element_inclusion(
&verifier,
&b4,
loc + 1,
&grafted_root,
));
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
0,
vec![&c1],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
assert!(!proof.verify_element_inclusion(&verifier, &b4, loc, &grafted_root));
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
2,
vec![&c2],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
assert!(!proof.verify_element_inclusion(&verifier, &b4, loc, &grafted_root));
}
{
let proof = verification::range_proof(
&hasher,
&combined,
Location::new(0)..Location::new(4),
0,
)
.await
.unwrap();
let range = vec![&b1, &b2, &b3, &b4];
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
0,
vec![&c1, &c2],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
assert!(proof.verify_range_inclusion(
&verifier,
&range,
Location::new(0),
&grafted_root,
));
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
0,
vec![&c1],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
assert!(!proof.verify_range_inclusion(
&verifier,
&range,
Location::new(0),
&grafted_root,
));
}
}
let b5 = Sha256::fill(0x05);
let batch = {
let mut batch = ops_mmr.new_batch();
batch = batch.add(&hasher, &b5);
batch.merkleize(&ops_mmr, &hasher)
};
ops_mmr.apply_batch(&batch).unwrap();
let combined = Storage::new(&grafted, GRAFTING_HEIGHT, &ops_mmr, hasher.clone());
assert_eq!(combined.size().await, ops_mmr.size());
let grafted_root = {
let ops_size = ops_mmr.size();
let ops_leaves = Location::try_from(ops_size).unwrap();
let mut peaks = Vec::new();
for (peak_pos, peak_height) in PeakIterator::new(ops_size) {
if peak_height >= GRAFTING_HEIGHT {
let gp = ops_to_grafted_pos(peak_pos, GRAFTING_HEIGHT);
peaks.push(grafted.get_node(gp).unwrap());
} else {
peaks.push(combined.get_node(peak_pos).await.unwrap().unwrap());
}
}
hasher
.root(ops_leaves, 0, peaks.iter())
.expect("zero inactive peaks is always valid")
};
let loc = Location::new(0);
let proof = merkle::verification::range_proof(&hasher, &combined, loc..loc + 1, 0)
.await
.unwrap();
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
0,
vec![&c1],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
assert!(proof.verify_element_inclusion(&verifier, &b1, loc, &grafted_root));
let verifier = Verifier::<mmr::Family, Sha256>::new(
GRAFTING_HEIGHT,
0,
vec![],
ALL_CHUNKS_GRAFTABLE,
ForwardFold,
);
let loc = Location::new(4);
let proof = merkle::verification::range_proof(&hasher, &combined, loc..loc + 1, 0)
.await
.unwrap();
assert!(proof.verify_element_inclusion(&verifier, &b5, loc, &grafted_root));
});
}
#[test_traced]
fn test_grafted_mmr_basic() {
let grafting_height = 1u32;
let standard: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
let d0 = Sha256::fill(0x01);
let d1 = Sha256::fill(0x02);
let grafted_hasher = GraftedHasher::<mmr::Family, _>::new(standard, grafting_height);
let mut grafted = Mmr::new();
let batch = grafted
.new_batch()
.add_leaf_digest(d0)
.add_leaf_digest(d1)
.merkleize(&grafted, &grafted_hasher);
grafted.apply_batch(&batch).unwrap();
let ops_pos_0 = chunk_idx_to_ops_pos(0, grafting_height);
let ops_pos_1 = chunk_idx_to_ops_pos(1, grafting_height);
let gp0 = ops_to_grafted_pos(ops_pos_0, grafting_height);
let gp1 = ops_to_grafted_pos(ops_pos_1, grafting_height);
assert_eq!(grafted.get_node(gp0), Some(d0));
assert_eq!(grafted.get_node(gp1), Some(d1));
let gp_root = ops_to_grafted_pos(Position::new(6), grafting_height);
assert!(grafted.get_node(gp_root).is_some());
let gp_far = ops_to_grafted_pos(chunk_idx_to_ops_pos(5, grafting_height), grafting_height);
assert_eq!(grafted.get_node(gp_far), None);
}
#[test_traced]
fn test_grafted_mmr_with_pruning() {
let grafting_height = 1u32;
let standard: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
let pinned_digest = Sha256::fill(0xAA);
let grafted_pruning_boundary = Location::new(4);
assert_eq!(*Position::try_from(grafted_pruning_boundary).unwrap(), 7);
let d4 = Sha256::fill(0xBB);
let grafted_hasher = GraftedHasher::<mmr::Family, _>::new(standard, grafting_height);
let mut grafted =
Mmr::from_components(Vec::new(), grafted_pruning_boundary, vec![pinned_digest])
.unwrap();
let batch = grafted
.new_batch()
.add_leaf_digest(d4)
.merkleize(&grafted, &grafted_hasher);
grafted.apply_batch(&batch).unwrap();
assert_eq!(grafted.get_node(Position::new(6)), Some(pinned_digest));
let ops_pos_4 = chunk_idx_to_ops_pos(4, grafting_height);
let gp4 = ops_to_grafted_pos(ops_pos_4, grafting_height);
assert_eq!(grafted.get_node(gp4), Some(d4));
}
#[test_traced]
fn test_graftable_chunks_always_single_peak() {
type F = mmb::Family;
let grafting_height = 2u32;
let mut exercised = 0usize;
for leaf_count in 1..=200u64 {
let size = F::location_to_position(mmb::Location::new(leaf_count));
let complete_chunks = leaf_count / (1u64 << grafting_height);
let graftable_chunks =
graftable_chunks::<F>(leaf_count, grafting_height).min(complete_chunks);
for chunk_idx in 0..graftable_chunks {
let count = F::chunk_peaks(size, chunk_idx, grafting_height).count();
assert_eq!(
count, 1,
"graftable chunk {chunk_idx} has {count} peaks (leaf_count={leaf_count}, graftable={graftable_chunks}, complete={complete_chunks})"
);
exercised += 1;
}
}
assert!(exercised > 0);
}
#[test_traced]
fn test_only_pending_chunk_can_be_multi_peak() {
type F = mmb::Family;
let grafting_height = 2u32;
let mut exercised_pending = 0usize;
for leaf_count in 1..=200u64 {
let size = F::location_to_position(mmb::Location::new(leaf_count));
let complete_chunks = leaf_count / (1u64 << grafting_height);
let graftable_chunks =
graftable_chunks::<F>(leaf_count, grafting_height).min(complete_chunks);
for chunk_idx in 0..complete_chunks {
let count = F::chunk_peaks(size, chunk_idx, grafting_height).count();
if chunk_idx < graftable_chunks {
assert_eq!(
count, 1,
"graftable chunk {chunk_idx} has {count} peaks (leaf_count={leaf_count})"
);
} else {
assert!(
count >= 1,
"pending chunk {chunk_idx} has {count} peaks (leaf_count={leaf_count})"
);
if count > 1 {
exercised_pending += 1;
}
}
}
}
assert!(
exercised_pending > 0,
"expected to exercise at least one multi-peak pending chunk"
);
}
}