use crate::{
Context,
journal::{
Error as JError,
authenticated::{Backing as _, BackingRecovery as _, Stored},
contiguous::{
Contiguous, Many,
fixed::{Config as JConfig, Journal, Recovery as JournalRecovery},
},
},
merkle::{
Error, Family, Location, Position, Proof, Readable, batch,
hasher::Hasher,
mem::{Config as MemConfig, Mem},
},
metadata::{Config as MConfig, Metadata},
};
use commonware_codec::{Copying, DecodeExt, Write};
use commonware_cryptography::Digest;
use commonware_parallel::Strategy;
use commonware_runtime::{Handle, buffer::paged::CacheRef};
use commonware_utils::{futures::try_join_all, range::NonEmptyRange, sequence::prefixed_u64::U64};
use std::{
collections::{BTreeMap, BTreeSet},
num::{NonZeroU64, NonZeroUsize},
sync::Arc,
};
use tracing::warn;
pub struct UnmerkleizedBatch<F: Family, D: Digest, S: Strategy> {
inner: batch::UnmerkleizedBatch<F, D, S>,
}
impl<F: Family, D: Digest, S: Strategy> UnmerkleizedBatch<F, D, S> {
pub fn add(self, hasher: &impl Hasher<F, Digest = D>, element: &[u8]) -> Self {
Self {
inner: self.inner.add(hasher, element),
}
}
pub fn add_leaf_digest(self, digest: D) -> Self {
Self {
inner: self.inner.add_leaf_digest(digest),
}
}
pub(crate) fn add_many<Item: Write + Send + Sync>(
self,
hasher: &impl Hasher<F, Digest = D>,
items: &[Item],
) -> Self {
Self {
inner: self.inner.add_many(hasher, items),
}
}
pub fn leaves(&self) -> Location<F> {
self.inner.leaves()
}
pub fn strategy(&self) -> &S {
self.inner.strategy()
}
pub fn merkleize(
self,
base: &Mem<F, D>,
hasher: &impl Hasher<F, Digest = D>,
) -> Arc<batch::MerkleizedBatch<F, D, S>> {
self.inner.merkleize(base, hasher)
}
}
#[derive(Clone)]
pub struct Config<S: Strategy> {
pub journal_partition: String,
pub metadata_partition: String,
pub items_per_blob: NonZeroU64,
pub write_buffer: NonZeroUsize,
pub replay_buffer: NonZeroUsize,
pub strategy: S,
pub page_cache: CacheRef,
}
pub struct SyncConfig<F: Family, D: Digest, S: Strategy> {
pub config: Config<S>,
pub range: NonEmptyRange<Location<F>>,
pub pinned_nodes: Option<Vec<D>>,
}
pub struct Merkle<F: Family, E: Context, D: Digest, S: Strategy> {
mem: Arc<Mem<F, D>>,
pub(crate) pruned_to_pos: Position<F>,
pub(crate) journal: Journal<E, D>,
pub(crate) metadata: Metadata<E, U64, Vec<u8>>,
pub(crate) journal_dirty: bool,
pub(crate) strategy: S,
}
pub(crate) struct Recovery<F: Family, E: Context, D: Digest, S: Strategy> {
journal: Box<JournalRecovery<E, D>>,
metadata: Metadata<E, U64, Vec<u8>>,
mem: Mem<F, D>,
retained_size: Position<F>,
metadata_prune_pos: Position<F>,
effective_prune_pos: Position<F>,
strategy: S,
}
impl<F: Family, E: Context, D: Digest, S: Strategy> Recovery<F, E, D, S> {
pub(crate) fn leaves(&self) -> Location<F> {
self.mem.leaves()
}
pub(crate) async fn finish(mut self) -> Result<Merkle<F, E, D, S>, Error<F>> {
self.journal = self.journal.truncate(*self.retained_size).await?;
if *self.metadata_prune_pos > self.journal.bounds().start {
(self.journal, _) = self.journal.prune(*self.metadata_prune_pos).await?;
}
let journal = (*self.journal).finish(*self.retained_size).await?;
Merkle {
mem: Arc::new(self.mem),
pruned_to_pos: self.effective_prune_pos,
journal,
metadata: self.metadata,
journal_dirty: false,
strategy: self.strategy,
}
.sync()
.await
}
}
impl<F: Family, E: Context, D: Digest, S: Strategy> std::fmt::Debug for Merkle<F, E, D, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Merkle")
.field("size", &self.size())
.field("leaves", &self.leaves())
.finish_non_exhaustive()
}
}
const NODE_PREFIX: u8 = 0;
pub(crate) const PRUNED_TO_PREFIX: u8 = 1;
impl<F: Family, E: Context, D: Digest, S: Strategy> Merkle<F, E, D, S> {
pub fn size(&self) -> Position<F> {
self.mem.size()
}
pub fn leaves(&self) -> Location<F> {
self.mem.leaves()
}
pub fn bounds(&self) -> std::ops::Range<Location<F>> {
Location::try_from(self.pruned_to_pos).expect("valid pruned_to_pos")..self.mem.leaves()
}
pub async fn init(
context: E,
hasher: &impl Hasher<F, Digest = D>,
cfg: Config<S>,
) -> Result<Self, Error<F>> {
Self::prepare(context, hasher, cfg, None)
.await?
.finish()
.await
}
pub async fn init_at_most(
context: E,
hasher: &impl Hasher<F, Digest = D>,
cfg: Config<S>,
max_leaves: Location<F>,
) -> Result<Self, Error<F>> {
Self::prepare(context, hasher, cfg, Some(max_leaves))
.await?
.finish()
.await
}
pub(crate) async fn prepare(
context: E,
hasher: &impl Hasher<F, Digest = D>,
cfg: Config<S>,
max_leaves: Option<Location<F>>,
) -> Result<Recovery<F, E, D, S>, Error<F>> {
let metadata = Metadata::<_, U64, Vec<u8>>::init(
context.child("metadata"),
MConfig {
partition: cfg.metadata_partition,
codec_config: ((0..).into(), ()),
},
)
.await?;
let key = U64::new(PRUNED_TO_PREFIX, 0);
let persisted_pruned_to = match metadata.get(&key) {
Some(bytes) => Some(Location::<F>::new(u64::from_be_bytes(
bytes
.as_slice()
.try_into()
.map_err(|_| Error::DataCorrupted("invalid Merkle pruning boundary"))?,
))),
None => None,
};
let metadata_pruned_to = persisted_pruned_to.unwrap_or(Location::new(0));
let metadata_prune_pos = Position::try_from(metadata_pruned_to)?;
if let Some(cap) = max_leaves
&& cap < metadata_pruned_to
{
return Err(Error::ElementPruned(Position::try_from(cap)?));
}
let node_cap = max_leaves
.map(|leaves| Position::<F>::try_from(leaves).map_or(u64::MAX, |position| *position));
let journal_cfg = JConfig {
partition: cfg.journal_partition,
items_per_blob: cfg.items_per_blob,
page_cache: cfg.page_cache,
write_buffer: cfg.write_buffer,
replay_buffer: cfg.replay_buffer,
};
let orphaned = persisted_pruned_to.is_none()
&& Journal::<E, D>::span(context.child("span"), &journal_cfg)
.await?
.start
> 0;
let journal = if orphaned {
warn!("crash repair: discarding pruned node journal without a persisted boundary");
Journal::<E, D>::clear(context.child("journal"), journal_cfg, 0).await?
} else {
Journal::<E, D>::recover(context.child("journal"), journal_cfg, node_cap).await?
};
let journal = Box::new(journal);
let journal_size = Position::<F>::new(journal.bounds().end);
let boundary = Position::<F>::new(journal.bounds().start);
let boundary_floor = F::to_nearest_size(boundary);
let aligned_boundary = if boundary_floor == boundary {
boundary
} else {
Position::try_from(Location::try_from(boundary_floor)? + 1)?
};
let effective_prune_pos = metadata_prune_pos.max(aligned_boundary);
let retained_size = F::to_nearest_size(journal_size);
if effective_prune_pos > retained_size {
return Err(Error::MissingNode(effective_prune_pos));
}
let leaves = Location::try_from(retained_size)?;
let prune_loc = Location::try_from(effective_prune_pos)?;
let mut mem = Self::pinned(&metadata, Some(&journal), leaves, prune_loc).await?;
if retained_size != journal_size
&& max_leaves.is_none_or(|cap| leaves < cap)
&& let Ok(leaf) = journal.read(*retained_size).await
{
let batch = mem
.new_batch()
.add_leaf_digest(leaf)
.merkleize(&mem, hasher);
mem.apply_batch(&batch)?;
}
Ok(Recovery {
journal,
metadata,
mem,
retained_size,
metadata_prune_pos,
effective_prune_pos,
strategy: cfg.strategy,
})
}
async fn get_from_recovery(
metadata: &Metadata<E, U64, Vec<u8>>,
journal: Option<&JournalRecovery<E, D>>,
pos: Position<F>,
) -> Result<D, Error<F>> {
if let Some(bytes) = metadata.get(&U64::new(NODE_PREFIX, *pos)) {
return D::decode(Copying(bytes))
.map_err(|_| Error::DataCorrupted("could not read digest at requested pos"));
}
let Some(journal) = journal else {
return Err(Error::MissingNode(pos));
};
match journal.read(*pos).await {
Ok(node) => Ok(node),
Err(JError::ItemPruned(_) | JError::ItemOutOfRange(_)) => Err(Error::MissingNode(pos)),
Err(err) => Err(Error::Journal(err)),
}
}
async fn pinned(
metadata: &Metadata<E, U64, Vec<u8>>,
journal: Option<&JournalRecovery<E, D>>,
leaves: Location<F>,
prune_loc: Location<F>,
) -> Result<Mem<F, D>, Error<F>> {
let mut pinned_nodes = Vec::new();
for pos in F::nodes_to_pin(leaves) {
pinned_nodes.push(Self::get_from_recovery(metadata, journal, pos).await?);
}
let mut mem = Mem::init(MemConfig {
nodes: vec![],
pruning_boundary: leaves,
pinned_nodes,
})?;
let mut extra = BTreeMap::new();
for pos in F::nodes_to_pin(prune_loc) {
extra.insert(pos, Self::get_from_recovery(metadata, journal, pos).await?);
}
mem.add_pinned_nodes(extra);
Ok(mem)
}
pub async fn init_sync(context: E, cfg: SyncConfig<F, D, S>) -> Result<Self, Error<F>> {
let prune_loc = cfg.range.start();
let prune_pos = Position::try_from(prune_loc)?;
let end_pos = Position::try_from(cfg.range.end())?;
let metadata_cfg = MConfig {
partition: cfg.config.metadata_partition,
codec_config: ((0..).into(), ()),
};
let mut metadata = Metadata::init(context.child("metadata"), metadata_cfg).await?;
let nodes_to_pin_persisted: Vec<_> = F::nodes_to_pin(prune_loc).collect();
let journal_probe = if cfg.pinned_nodes.is_none() {
nodes_to_pin_persisted
.iter()
.filter(|&&pos| metadata.get(&U64::new(NODE_PREFIX, *pos)).is_none())
.max()
.copied()
.unwrap_or(prune_pos)
} else {
prune_pos
};
let journal = Stored::<E, Journal<E, D>>::open(
context.child("journal"),
JConfig {
partition: cfg.config.journal_partition,
items_per_blob: cfg.config.items_per_blob,
write_buffer: cfg.config.write_buffer,
replay_buffer: cfg.config.replay_buffer,
page_cache: cfg.config.page_cache,
},
*journal_probe,
*end_pos,
)
.await?;
let opened = match &journal {
Stored::Opened(journal) => Some(journal),
Stored::Unopened { .. } => None,
};
let bounds = opened.map_or(*prune_pos..*prune_pos, |journal| journal.bounds());
let journal_size = F::to_nearest_size(Position::new(bounds.end)).max(prune_pos);
let reset = journal_size == prune_pos && bounds != (*prune_pos..*prune_pos);
let retained_node_keys: BTreeSet<_> = nodes_to_pin_persisted
.iter()
.map(|pos| U64::new(NODE_PREFIX, **pos))
.collect();
metadata
.retain(|key: &U64, _| key.prefix() != NODE_PREFIX || retained_node_keys.contains(key));
let pruning_boundary_key = U64::new(PRUNED_TO_PREFIX, 0);
metadata.put(
pruning_boundary_key,
prune_loc.as_u64().to_be_bytes().into(),
);
if let Some(pinned_nodes) = cfg.pinned_nodes {
if pinned_nodes.len() != nodes_to_pin_persisted.len() {
return Err(Error::<F>::InvalidPinnedNodes);
}
for (pos, digest) in nodes_to_pin_persisted.into_iter().zip(pinned_nodes.iter()) {
metadata.put(U64::new(NODE_PREFIX, *pos), digest.to_vec());
}
} else {
for pos in nodes_to_pin_persisted {
let digest = Self::get_from_recovery(&metadata, opened, pos).await?;
metadata.put(U64::new(NODE_PREFIX, *pos), digest.to_vec());
}
}
let journal_leaves = Location::try_from(journal_size)?;
let mem = Self::pinned(&metadata, opened, journal_leaves, prune_loc).await?;
let metadata = metadata.sync().await?;
let mut journal = match journal {
Stored::Opened(journal) if reset => Box::new(journal).clear_to_size(*prune_pos).await?,
Stored::Opened(journal) => Box::new(journal).truncate(*journal_size).await?,
Stored::Unopened { context, cfg } => {
Box::new(Journal::<E, D>::clear(context, cfg, *prune_pos).await?)
}
};
(journal, _) = journal.prune(*prune_pos).await?;
let journal = (*journal).finish(*journal_size).await?;
Ok(Self {
mem: Arc::new(mem),
pruned_to_pos: prune_pos,
journal,
metadata,
journal_dirty: false,
strategy: cfg.config.strategy,
})
}
async fn update_metadata(
mut self,
prune_to_pos: Position<F>,
) -> Result<(Self, BTreeMap<Position<F>, D>), Error<F>> {
assert!(prune_to_pos >= self.pruned_to_pos);
let prune_loc = Location::try_from(prune_to_pos).expect("valid prune_to_pos");
let mut pinned_nodes = BTreeMap::new();
for pos in F::nodes_to_pin(prune_loc) {
let digest = self.get_node(pos).await?.expect(
"pinned node should exist if prune_to_pos is no less than self.pruned_to_pos",
);
self.metadata
.put(U64::new(NODE_PREFIX, *pos), digest.to_vec());
pinned_nodes.insert(pos, digest);
}
let key: U64 = U64::new(PRUNED_TO_PREFIX, 0);
self.metadata = self
.metadata
.put_sync(
key,
Location::try_from(prune_to_pos)?
.as_u64()
.to_be_bytes()
.into(),
)
.await
.map_err(Error::Metadata)?;
Ok((self, pinned_nodes))
}
pub async fn get_node(&self, position: Position<F>) -> Result<Option<D>, Error<F>> {
if let Some(node) = self.mem.get_node(position) {
return Ok(Some(node));
}
match self.journal.read(*position).await {
Ok(item) => Ok(Some(item)),
Err(JError::ItemPruned(_)) => Ok(None),
Err(e) => Err(Error::Journal(e)),
}
}
pub async fn get_nodes(&self, positions: &[Position<F>]) -> Result<Vec<D>, Error<F>> {
assert!(
positions.is_sorted_by(|a, b| a < b),
"positions must be strictly increasing"
);
let bounds = self.journal.bounds();
let mut nodes = vec![None; positions.len()];
let mut journal_positions = Vec::with_capacity(positions.len());
for (slot, &position) in nodes.iter_mut().zip(positions) {
if let Some(node) = self.mem.get_node(position) {
*slot = Some(node);
} else if *position >= bounds.start {
journal_positions.push(*position);
} else {
return Err(Error::ElementPruned(position));
}
}
let items = if journal_positions.is_empty() {
Vec::new()
} else {
self.journal
.read_many(&journal_positions)
.await
.map_err(Error::Journal)?
};
let mut items = items.into_iter();
Ok(nodes
.into_iter()
.map(|node| node.unwrap_or_else(|| items.next().expect("one item per journal read")))
.collect())
}
pub async fn pinned_nodes_at(&self, loc: Location<F>) -> Result<Vec<D>, Error<F>> {
if !loc.is_valid() {
return Err(Error::LocationOverflow(loc));
}
let futs = F::nodes_to_pin(loc)
.map(|p| async move { self.get_node(p).await?.ok_or(Error::ElementPruned(p)) })
.collect::<Vec<_>>();
try_join_all(futs).await
}
pub async fn flush(self) -> Result<Self, Error<F>> {
self.flush_internal().await
}
pub async fn sync(mut self) -> Result<Self, Error<F>> {
self = self.flush_internal().await?;
if self.journal_dirty {
self.journal = self.journal.sync().await?;
self.journal_dirty = false;
}
Ok(self)
}
pub async fn start_sync(mut self) -> Result<(Self, Handle<()>), Error<F>> {
self = self.flush_internal().await?;
let (journal, handle) = self.journal.start_sync().await?;
self.journal = journal;
self.journal_dirty = true;
Ok((self, handle))
}
async fn flush_internal(mut self) -> Result<Self, Error<F>> {
let journal_size = Position::<F>::new(self.journal.size());
let (sync_target_leaves, encoded, pinned_nodes) = {
let size = self.mem.size();
let sync_target_leaves = self.mem.leaves();
assert!(
journal_size <= size,
"journal size should never exceed in-memory structure size"
);
if journal_size == size {
return Ok(self);
}
let (head, tail) = self.mem.nodes_from(journal_size);
let encoded = self.journal.prepare_append(Many::Nested(&[head, tail]));
let prune_loc = Location::try_from(self.pruned_to_pos).expect("valid pruned_to_pos");
let mut pinned_nodes = BTreeMap::new();
for pos in F::nodes_to_pin(prune_loc) {
let digest = self.mem.get_node_unchecked(pos);
pinned_nodes.insert(pos, *digest);
}
(sync_target_leaves, encoded, pinned_nodes)
};
(self.journal, _) = self.journal.append_prepared(encoded).await?;
self.journal_dirty = true;
let mem = Arc::make_mut(&mut self.mem);
mem.prune(sync_target_leaves)
.expect("captured leaves is in bounds");
mem.add_pinned_nodes(pinned_nodes);
Ok(self)
}
pub async fn prune(mut self, loc: Location<F>) -> Result<Self, Error<F>> {
let pos = Position::try_from(loc)?;
if loc > self.mem.leaves() {
return Err(Error::LeafOutOfBounds(loc));
}
if pos <= self.pruned_to_pos {
return Ok(self);
}
self = self.sync().await?;
let pinned_nodes;
(self, pinned_nodes) = self.update_metadata(pos).await?;
(self.journal, _) = self.journal.prune(*pos).await?;
Arc::make_mut(&mut self.mem).add_pinned_nodes(pinned_nodes);
self.pruned_to_pos = pos;
Ok(self)
}
pub fn root(
&self,
hasher: &impl Hasher<F, Digest = D>,
inactive_peaks: usize,
) -> Result<D, Error<F>> {
self.mem.root(hasher, inactive_peaks)
}
pub async fn prune_all(mut self) -> Result<Self, Error<F>> {
let leaves = self.mem.leaves();
if leaves != 0 {
self = self.prune(leaves).await?;
}
Ok(self)
}
pub async fn destroy(self) -> Result<(), Error<F>> {
self.metadata.destroy().await?;
self.journal.destroy().await?;
Ok(())
}
#[cfg(any(test, feature = "fuzzing"))]
pub async fn simulate_partial_sync(mut self, write_limit: usize) -> Result<(), Error<F>> {
if write_limit == 0 {
return Ok(());
}
let journal_size = Position::<F>::new(self.journal.size());
let mut written_count = 0usize;
for i in *journal_size..*self.mem.size() {
let node = *self.mem.get_node_unchecked(Position::new(i));
(self.journal, _) = self.journal.append(&node).await?;
written_count += 1;
if written_count >= write_limit {
break;
}
}
self.journal.sync().await?;
Ok(())
}
#[cfg(test)]
pub fn get_pinned_nodes(&self) -> BTreeMap<Position<F>, D> {
self.mem.pinned_nodes()
}
#[cfg(test)]
pub async fn simulate_pruning_failure(mut self, prune_to: Location<F>) -> Result<(), Error<F>> {
let prune_to_pos = Position::try_from(prune_to)?;
assert!(prune_to_pos <= self.mem.size());
self = self.sync().await?;
self.update_metadata(prune_to_pos).await?;
Ok(())
}
pub fn apply_batch(
mut self,
batch: &batch::MerkleizedBatch<F, D, S>,
) -> Result<Self, Error<F>> {
Arc::make_mut(&mut self.mem).apply_batch(batch)?;
Ok(self)
}
pub(crate) fn to_batch(&self) -> Arc<batch::MerkleizedBatch<F, D, S>> {
batch::MerkleizedBatch::from_mem_with_strategy(&self.mem, self.strategy.clone())
}
pub fn mem(&self) -> &Mem<F, D> {
&self.mem
}
pub(crate) fn snapshot(&self) -> Arc<Mem<F, D>> {
Arc::clone(&self.mem)
}
pub fn new_batch(&self) -> UnmerkleizedBatch<F, D, S> {
UnmerkleizedBatch {
inner: self.mem.new_batch_with_strategy(self.strategy.clone()),
}
}
pub const fn strategy(&self) -> &S {
&self.strategy
}
pub async fn historical_proof(
&self,
hasher: &impl Hasher<F, Digest = D>,
leaves: Location<F>,
loc: Location<F>,
inactive_peaks: usize,
) -> Result<Proof<F, D>, Error<F>> {
if !loc.is_valid_index() {
return Err(Error::LocationOverflow(loc));
}
self.historical_range_proof(hasher, leaves, loc..loc + 1, inactive_peaks)
.await
}
pub async fn historical_range_proof(
&self,
hasher: &impl Hasher<F, Digest = D>,
leaves: Location<F>,
range: core::ops::Range<Location<F>>,
inactive_peaks: usize,
) -> Result<Proof<F, D>, Error<F>> {
if leaves > self.leaves() {
return Err(Error::RangeOutOfBounds(leaves));
}
crate::merkle::verification::historical_range_proof(
hasher,
self,
leaves,
range,
inactive_peaks,
)
.await
}
pub async fn proof(
&self,
hasher: &impl Hasher<F, Digest = D>,
loc: Location<F>,
inactive_peaks: usize,
) -> Result<Proof<F, D>, Error<F>> {
if !loc.is_valid_index() {
return Err(Error::LocationOverflow(loc));
}
self.range_proof(hasher, loc..loc + 1, inactive_peaks).await
}
pub async fn range_proof(
&self,
hasher: &impl Hasher<F, Digest = D>,
range: core::ops::Range<Location<F>>,
inactive_peaks: usize,
) -> Result<Proof<F, D>, Error<F>> {
self.historical_range_proof(hasher, self.leaves(), range, inactive_peaks)
.await
}
}
impl<F: Family, E: Context, D: Digest, S: Strategy> Readable for Merkle<F, E, D, S> {
type Family = F;
type Digest = D;
fn size(&self) -> Position<F> {
self.size()
}
fn get_node(&self, pos: Position<F>) -> Option<D> {
self.mem.get_node(pos)
}
}
impl<F: Family, E: Context, D: Digest, S: Strategy> crate::merkle::storage::Storage<F>
for Merkle<F, E, D, S>
{
type Digest = D;
fn size(&self) -> Position<F> {
self.size()
}
async fn get_node(&self, position: Position<F>) -> Result<Option<D>, Error<F>> {
Self::get_node(self, position).await
}
async fn get_nodes(&self, positions: &[Position<F>]) -> Result<Vec<D>, Error<F>> {
Self::get_nodes(self, positions).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
journal::contiguous::fixed::{Config as JConfig, Journal},
merkle::{
Bagging::ForwardFold, Location, LocationRangeExt as _, Position, Proof,
hasher::Standard, mmb, mmr,
},
metadata::{Config as MConfig, Metadata},
};
use commonware_cryptography::{
Hasher as _, Sha256,
sha256::{self, Digest},
};
use commonware_macros::test_traced;
use commonware_parallel::Sequential;
use commonware_runtime::{
Blob as _, Runner, Storage as _, Supervisor as _,
buffer::paged::CacheRef,
deterministic,
mocks::{
DelayedSyncContext, PendingSyncs, RecordingContext, drive_pending_syncs,
fail_pending_syncs, next_pending_sync,
},
};
use commonware_utils::{
NZU16, NZU64, NZUsize, non_empty_range, probability, sequence::prefixed_u64::U64,
};
use futures::FutureExt as _;
use std::{
collections::BTreeMap,
future::{Future as _, poll_fn},
num::{NonZeroU16, NonZeroUsize},
task::Poll,
};
fn test_digest(v: usize) -> Digest {
Sha256::hash(&[&v.to_be_bytes()])
}
const PAGE_SIZE: NonZeroU16 = NZU16!(111);
const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(5);
fn test_config() -> Config<Sequential> {
Config {
journal_partition: "journal-partition".into(),
metadata_partition: "metadata-partition".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(1024),
replay_buffer: NZUsize!(1024),
strategy: Sequential,
page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
}
}
async fn empty_journal_rejects_pruned_metadata<F: Family>(context: deterministic::Context) {
let cfg = test_config();
let metadata = Metadata::<_, U64, Vec<u8>>::init(
context.child("metadata"),
MConfig {
partition: cfg.metadata_partition.clone(),
codec_config: ((0..).into(), ()),
},
)
.await
.unwrap();
metadata
.put_sync(U64::new(PRUNED_TO_PREFIX, 0), 1u64.to_be_bytes().to_vec())
.await
.unwrap();
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let result =
Merkle::<F, _, Digest, Sequential>::init(context.child("open"), &hasher, cfg).await;
assert!(matches!(result, Err(Error::MissingNode(_))));
}
#[test]
fn test_empty_journal_rejects_pruned_metadata_mmr() {
deterministic::Runner::default()
.start(empty_journal_rejects_pruned_metadata::<mmr::Family>);
}
#[test]
fn test_empty_journal_rejects_pruned_metadata_mmb() {
deterministic::Runner::default()
.start(empty_journal_rejects_pruned_metadata::<mmb::Family>);
}
fn interrupted_destroy_reopens<F: Family>(seed: u64) -> (u32, u32) {
deterministic::Runner::seeded(seed).start(|context| async move {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let cfg = test_config();
let mut merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("first"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..100 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let merkle = merkle.prune(Location::new(60)).await.unwrap();
let root = merkle.root(&hasher, 0).unwrap();
let leaves = merkle.leaves();
context.storage_fault_config().write().sync_rate = Some(probability!(0.3));
context.storage_fault_config().write().remove_rate = Some(probability!(0.1));
let result = merkle.destroy().await;
context.storage_fault_config().write().sync_rate = None;
context.storage_fault_config().write().remove_rate = None;
if result.is_ok() {
return (0, 0);
}
let merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("second"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
let outcome = if merkle.leaves() == leaves {
assert_eq!(merkle.root(&hasher, 0).unwrap(), root, "seed {seed}");
(1, 0)
} else {
assert_eq!(*merkle.leaves(), 0, "seed {seed}");
(0, 1)
};
merkle.destroy().await.unwrap();
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("third"), &hasher, cfg)
.await
.unwrap();
assert_eq!(*merkle.leaves(), 0);
outcome
})
}
async fn interrupted_destroy_reopens_bounded<F: Family>(context: deterministic::Context) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let cfg = test_config();
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("first"), &hasher, cfg.clone())
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..100 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let merkle = merkle.prune(Location::new(60)).await.unwrap();
drop(merkle);
context.remove(&cfg.metadata_partition, None).await.unwrap();
let merkle = Merkle::<F, _, Digest, Sequential>::init_at_most(
context.child("second"),
&hasher,
cfg.clone(),
Location::new(30),
)
.await
.unwrap();
assert_eq!(*merkle.leaves(), 0);
merkle.destroy().await.unwrap();
let merkle = Merkle::<F, _, Digest, Sequential>::init(context.child("third"), &hasher, cfg)
.await
.unwrap();
assert_eq!(*merkle.leaves(), 0);
}
#[test]
fn test_interrupted_destroy_reopens_bounded_mmr() {
deterministic::Runner::default().start(interrupted_destroy_reopens_bounded::<mmr::Family>);
}
#[test]
fn test_interrupted_destroy_reopens_bounded_mmb() {
deterministic::Runner::default().start(interrupted_destroy_reopens_bounded::<mmb::Family>);
}
fn interrupted_destroy_reopens_all_seeds<F: Family>() {
let (mut unchanged, mut emptied) = (0, 0);
for seed in 0..32u64 {
let (u, e) = interrupted_destroy_reopens::<F>(seed);
unchanged += u;
emptied += e;
}
assert!(
unchanged > 0 && emptied > 0,
"unchanged {unchanged} emptied {emptied}"
);
}
#[test]
fn test_interrupted_destroy_reopens_mmr() {
interrupted_destroy_reopens_all_seeds::<mmr::Family>();
}
#[test]
fn test_interrupted_destroy_reopens_mmb() {
interrupted_destroy_reopens_all_seeds::<mmb::Family>();
}
async fn full_empty_inner<F: Family>(context: deterministic::Context) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("first"),
&hasher,
test_config(),
)
.await
.unwrap();
assert_eq!(mmr.size(), 0);
assert!(mmr.get_node(Position::<F>::new(0)).await.is_err());
let bounds = mmr.bounds();
assert!(bounds.is_empty());
mmr = mmr.prune_all().await.unwrap();
assert_eq!(bounds.start, 0);
mmr = mmr.prune(Location::<F>::new(0)).await.unwrap();
mmr = mmr.sync().await.unwrap();
drop(mmr);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("reopen"),
&hasher,
test_config(),
)
.await
.unwrap();
let batch = mmr.new_batch().add(&hasher, &test_digest(0));
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
assert_eq!(mmr.size(), 1);
mmr = mmr.sync().await.unwrap();
assert!(mmr.get_node(Position::<F>::new(0)).await.is_ok());
drop(mmr);
mmr = Merkle::<F, _, Digest, Sequential>::init_at_most(
context.child("cap_empty"),
&hasher,
test_config(),
Location::new(0),
)
.await
.unwrap();
assert_eq!(mmr.size(), 0);
mmr.sync().await.unwrap();
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("second"),
&hasher,
test_config(),
)
.await
.unwrap();
assert_eq!(mmr.size(), 0);
let empty_proof = Proof::<F, Digest>::default();
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let root = mmr.root(&hasher, 0).unwrap();
assert!(empty_proof.verify_range_inclusion(
&hasher,
&[] as &[Digest],
Location::<F>::new(0),
&root
));
assert!(empty_proof.verify_multi_inclusion(
&hasher,
&[] as &[(Digest, Location<F>)],
&root
));
let batch = mmr.new_batch().add(&hasher, &test_digest(0));
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let root = mmr.root(&hasher, 0).unwrap();
assert!(!empty_proof.verify_range_inclusion(
&hasher,
&[] as &[Digest],
Location::<F>::new(0),
&root
));
assert!(!empty_proof.verify_multi_inclusion(
&hasher,
&[] as &[(Digest, Location<F>)],
&root
));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_sync_observes_clean_start_sync_failure() {
deterministic::Runner::default().start(|context| async move {
let pending = PendingSyncs::default();
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let cfg = test_config();
let context = DelayedSyncContext {
inner: context,
pending: pending.clone(),
};
let merkle = drive_pending_syncs(
&pending,
Merkle::<mmr::Family, _, Digest, Sequential>::init(
context.child("seed"),
&hasher,
cfg.clone(),
),
)
.await
.unwrap();
let batch = merkle.new_batch().add(&hasher, &test_digest(0));
let batch = batch.merkleize(merkle.mem(), &hasher);
let merkle = merkle.apply_batch(&batch).unwrap();
let (merkle, handle) = merkle.start_sync().await.unwrap();
assert!(!pending.lock().is_empty());
drive_pending_syncs(&pending, handle).await.unwrap();
drop(merkle);
let merkle = drive_pending_syncs(
&pending,
Merkle::<mmr::Family, _, Digest, Sequential>::init(
context.child("reopen"),
&hasher,
cfg,
),
)
.await
.unwrap();
assert!(!merkle.journal_dirty);
let (merkle, handle) = merkle.start_sync().await.unwrap();
assert!(!pending.lock().is_empty());
fail_pending_syncs(&pending);
drop(handle);
let error = merkle.sync().await.expect_err("sync failure was lost");
assert!(matches!(
error,
Error::Journal(JError::Metadata(crate::metadata::Error::Runtime(_)))
));
});
}
async fn seed_lagging_watermark(context: &deterministic::Context) -> u64 {
let pending = PendingSyncs::default();
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let delayed = DelayedSyncContext {
inner: context.child("delayed"),
pending: pending.clone(),
};
let merkle = drive_pending_syncs(
&pending,
Merkle::<mmr::Family, _, Digest, Sequential>::init(
delayed.child("seed"),
&hasher,
test_config(),
),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
let merkle = merkle.apply_batch(&batch).unwrap();
let size = *merkle.size();
let merkle = drive_pending_syncs(&pending, merkle.flush()).await.unwrap();
let (merkle, handle) = merkle.start_sync().await.unwrap();
drive_pending_syncs(&pending, handle).await.unwrap();
drop(merkle);
let lagging = persisted_watermark(context).await.unwrap();
assert!(
lagging < size,
"watermark {lagging} covers all {size} nodes"
);
size
}
async fn persisted_watermark(context: &deterministic::Context) -> Option<u64> {
Journal::<_, Digest>::persisted_watermark(
context.child("probe"),
&test_config().journal_partition,
)
.await
.unwrap()
}
#[test_traced]
fn test_init_sync_publishes_watermark() {
deterministic::Runner::default().start(|context| async move {
let size = seed_lagging_watermark(&context).await;
let leaves = Location::<mmr::Family>::try_from(Position::new(size)).unwrap();
let merkle = Merkle::<mmr::Family, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: test_config(),
range: non_empty_range!(Location::new(0), leaves),
pinned_nodes: None,
},
)
.await
.unwrap();
assert_eq!(*merkle.size(), size);
drop(merkle);
assert_eq!(persisted_watermark(&context).await, Some(size));
});
}
#[test_traced]
fn test_full_empty_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_empty_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_empty_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_empty_inner::<mmb::Family>);
}
async fn full_prune_out_of_bounds_returns_error_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("oob_prune"),
&hasher,
test_config(),
)
.await
.unwrap();
let batch = mmr.new_batch().add(&hasher, &test_digest(0));
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
assert!(matches!(
mmr.prune(Location::<F>::new(2)).await,
Err(Error::LeafOutOfBounds(loc)) if loc == Location::<F>::new(2)
));
}
#[test_traced]
fn test_full_prune_out_of_bounds_returns_error_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_prune_out_of_bounds_returns_error_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_prune_out_of_bounds_returns_error_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_prune_out_of_bounds_returns_error_inner::<mmb::Family>);
}
async fn full_initialization_bounds_preserve_state_inner<F: Family>(
context: deterministic::Context,
) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let element_pruned_context = context.child("element_pruned_case");
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
element_pruned_context.child("element_pruned"),
&hasher,
test_config(),
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0u64..32 {
batch = batch.add(&hasher, &i.to_be_bytes());
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mmr = mmr.prune(Location::<F>::new(8)).await.unwrap();
let leaves_before = mmr.leaves();
drop(mmr);
assert!(matches!(
Merkle::<F, _, Digest, Sequential>::init_at_most(
element_pruned_context.child("cap"),
&hasher,
test_config(),
Location::new(7)
)
.await,
Err(Error::ElementPruned(_))
));
let mmr = Merkle::<F, _, Digest, Sequential>::init(
element_pruned_context.child("element_pruned_reopen"),
&hasher,
test_config(),
)
.await
.unwrap();
assert_eq!(mmr.leaves(), leaves_before);
mmr.destroy().await.unwrap();
let bounded_context = context.child("overshooting_cap");
let cfg = Config {
journal_partition: "overshooting-journal-partition".into(),
metadata_partition: "overshooting-metadata-partition".into(),
..test_config()
};
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
bounded_context.child("open"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0u64..8 {
batch = batch.add(&hasher, &i.to_be_bytes());
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let root = mmr.root(&hasher, 0).unwrap();
_ = mmr.sync().await.unwrap();
let mmr = Merkle::<F, _, Digest, Sequential>::init_at_most(
bounded_context.child("cap"),
&hasher,
cfg.clone(),
Location::new(u64::MAX),
)
.await
.unwrap();
assert_eq!(mmr.leaves(), Location::new(8));
assert_eq!(mmr.root(&hasher, 0).unwrap(), root);
drop(mmr);
let mmr = Merkle::<F, _, Digest, Sequential>::init(
bounded_context.child("reopen"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
assert_eq!(mmr.leaves(), Location::new(8));
assert_eq!(mmr.root(&hasher, 0).unwrap(), root);
drop(mmr);
let mmr = Merkle::<F, _, Digest, Sequential>::init_at_most(
bounded_context.child("empty"),
&hasher,
cfg.clone(),
Location::new(0),
)
.await
.unwrap();
assert_eq!(mmr.leaves(), Location::new(0));
drop(mmr);
let mmr = Merkle::<F, _, Digest, Sequential>::init(
bounded_context.child("empty_reopen"),
&hasher,
cfg,
)
.await
.unwrap();
assert_eq!(mmr.leaves(), Location::new(0));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_initialization_bounds_preserve_state_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_initialization_bounds_preserve_state_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_initialization_bounds_preserve_state_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_initialization_bounds_preserve_state_inner::<mmb::Family>);
}
async fn full_basic_inner<F: Family>(context: deterministic::Context) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let cfg = test_config();
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(context, &hasher, cfg)
.await
.unwrap();
const LEAF_COUNT: usize = 255;
let mut leaves = Vec::with_capacity(LEAF_COUNT);
for i in 0..LEAF_COUNT {
leaves.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for leaf in &leaves {
batch = batch.add(&hasher, leaf);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let expected_size = Position::<F>::try_from(Location::<F>::new(LEAF_COUNT as u64)).unwrap();
assert_eq!(mmr.size(), expected_size);
const TEST_ELEMENT: usize = 133;
let test_element_loc: Location<F> = Location::new(TEST_ELEMENT as u64);
let proof = mmr.proof(&hasher, test_element_loc, 0).await.unwrap();
let root = mmr.root(&hasher, 0).unwrap();
assert!(proof.verify_element_inclusion(
&hasher,
&leaves[TEST_ELEMENT],
test_element_loc,
&root
));
mmr = mmr.sync().await.unwrap();
let proof2 = mmr.proof(&hasher, test_element_loc, 0).await.unwrap();
assert_eq!(proof, proof2);
let range = Location::<F>::new(TEST_ELEMENT as u64)..Location::<F>::new(LEAF_COUNT as u64);
let proof = mmr.range_proof(&hasher, range.clone(), 0).await.unwrap();
assert!(proof.verify_range_inclusion(
&hasher,
&leaves[range.to_usize_range()],
test_element_loc,
&root
));
mmr.destroy().await.unwrap();
}
async fn full_get_nodes_matches_get_node_inner<F: Family>(context: deterministic::Context) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let cfg = test_config();
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(context, &hasher, cfg)
.await
.unwrap();
const LEAF_COUNT: usize = 200;
let mut batch = mmr.new_batch();
for i in 0..LEAF_COUNT {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
mmr = mmr.sync().await.unwrap();
mmr = mmr.prune(Location::<F>::new(50)).await.unwrap();
let mut batch = mmr.new_batch();
for i in LEAF_COUNT..LEAF_COUNT + 10 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let all: Vec<Position<F>> = (0..*mmr.size()).map(Position::new).collect();
let mut absent = Vec::new();
let mut available = Vec::new();
for &position in &all {
match mmr.get_node(position).await.unwrap() {
Some(node) => available.push((position, node)),
None => absent.push(position),
}
}
assert!(!absent.is_empty(), "expected some pruned positions");
assert!(!available.is_empty(), "expected some available positions");
match mmr.get_nodes(&all).await {
Err(Error::ElementPruned(position)) => {
assert!(absent.contains(&position), "position {position}")
}
other => panic!("expected ElementPruned, got {other:?}"),
}
let positions: Vec<Position<F>> = available.iter().map(|&(pos, _)| pos).collect();
let batched = mmr.get_nodes(&positions).await.unwrap();
assert_eq!(batched.len(), available.len());
for (slot, &(position, node)) in available.iter().enumerate() {
assert_eq!(batched[slot], node, "position {position}");
}
let sparse: Vec<Position<F>> = positions.iter().copied().step_by(7).collect();
let batched = mmr.get_nodes(&sparse).await.unwrap();
for (slot, &position) in sparse.iter().enumerate() {
let single = mmr.get_node(position).await.unwrap().unwrap();
assert_eq!(batched[slot], single, "position {position}");
}
assert!(mmr.get_nodes(&[]).await.unwrap().is_empty());
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_get_nodes_matches_get_node_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_get_nodes_matches_get_node_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_get_nodes_matches_get_node_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_get_nodes_matches_get_node_inner::<mmb::Family>);
}
#[test_traced]
fn test_full_basic_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_basic_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_basic_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_basic_inner::<mmb::Family>);
}
async fn full_flush_inner<F: Family>(context: deterministic::Context) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("first"),
&hasher,
test_config(),
)
.await
.unwrap();
const LEAF_COUNT: usize = 100;
let mut leaves = Vec::with_capacity(LEAF_COUNT);
for i in 0..LEAF_COUNT {
leaves.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for leaf in &leaves {
batch = batch.add(&hasher, leaf);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let expected_size = Position::<F>::try_from(Location::<F>::new(LEAF_COUNT as u64)).unwrap();
let root = mmr.root(&hasher, 0).unwrap();
mmr = mmr.flush().await.unwrap();
assert_eq!(Position::<F>::new(mmr.journal.size()), expected_size);
assert_eq!(mmr.size(), expected_size);
assert_eq!(
mmr.mem().bounds().start,
Location::<F>::new(LEAF_COUNT as u64)
);
mmr = mmr.flush().await.unwrap();
assert_eq!(Position::<F>::new(mmr.journal.size()), expected_size);
assert_eq!(mmr.root(&hasher, 0).unwrap(), root);
const TEST_ELEMENT: usize = 42;
let loc: Location<F> = Location::new(TEST_ELEMENT as u64);
let proof = mmr.proof(&hasher, loc, 0).await.unwrap();
assert!(proof.verify_element_inclusion(&hasher, &leaves[TEST_ELEMENT], loc, &root));
mmr = mmr.sync().await.unwrap();
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_flush_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_flush_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_flush_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_flush_inner::<mmb::Family>);
}
fn full_flush_crash_inner<F: Family>() {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let executor = deterministic::Runner::default();
let (synced_size, checkpoint) = executor.start_and_recover(|context| async move {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("first"),
&hasher,
Config {
items_per_blob: NZU64!(512),
..test_config()
},
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..50usize {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mut mmr = mmr.sync().await.unwrap();
let synced_size = mmr.size();
let mut batch = mmr.new_batch();
for i in 50..100usize {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
mmr = mmr.flush().await.unwrap();
assert_eq!(Position::<F>::new(mmr.journal.size()), mmr.size());
synced_size
});
let executor = deterministic::Runner::from(checkpoint);
executor.start(|context| async move {
let mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("second"),
&hasher,
Config {
items_per_blob: NZU64!(512),
..test_config()
},
)
.await
.unwrap();
assert_eq!(mmr.size(), synced_size);
});
}
#[test_traced]
fn test_full_flush_crash_recovery_mmr() {
full_flush_crash_inner::<mmr::Family>();
}
#[test_traced]
fn test_full_flush_crash_recovery_mmb() {
full_flush_crash_inner::<mmb::Family>();
}
fn full_flush_then_sync_crash_inner<F: Family>() {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let executor = deterministic::Runner::default();
let (full_size, checkpoint) = executor.start_and_recover(|context| async move {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("first"),
&hasher,
test_config(),
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..100usize {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
mmr = mmr.flush().await.unwrap();
let mmr = mmr.sync().await.unwrap();
mmr.size()
});
let executor = deterministic::Runner::from(checkpoint);
executor.start(|context| async move {
let mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("second"),
&hasher,
test_config(),
)
.await
.unwrap();
assert_eq!(mmr.size(), full_size);
});
}
#[test_traced]
fn test_full_flush_then_sync_crash_recovery_mmr() {
full_flush_then_sync_crash_inner::<mmr::Family>();
}
#[test_traced]
fn test_full_flush_then_sync_crash_recovery_mmb() {
full_flush_then_sync_crash_inner::<mmb::Family>();
}
async fn full_recovery_inner<F: Family>(context: deterministic::Context) {
use crate::journal::contiguous::fixed::{Config as JConfig, Journal};
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("first"),
&hasher,
test_config(),
)
.await
.unwrap();
assert_eq!(mmr.size(), 0);
const LEAF_COUNT: usize = 252;
let mut leaves = Vec::with_capacity(LEAF_COUNT);
for i in 0..LEAF_COUNT {
leaves.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for leaf in &leaves {
batch = batch.add(&hasher, leaf);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let expected_size = Position::<F>::try_from(Location::<F>::new(LEAF_COUNT as u64)).unwrap();
assert_eq!(mmr.size(), expected_size);
let mmr = mmr.sync().await.unwrap();
drop(mmr);
{
let journal: Journal<_, Digest> = Journal::init(
context.child("corrupt"),
JConfig {
partition: "journal-partition".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(1024),
replay_buffer: NZUsize!(1024),
page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
},
)
.await
.unwrap();
assert_eq!(journal.size(), expected_size);
let (journal, _) = journal.append(&Sha256::hash(&[b"orphan"])).await.unwrap();
let journal = journal.sync().await.unwrap();
assert_eq!(journal.size(), expected_size + 1);
}
let mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("second"),
&hasher,
test_config(),
)
.await
.unwrap();
let recovered_size =
Position::<F>::try_from(Location::<F>::new(LEAF_COUNT as u64 + 1)).unwrap();
assert_eq!(mmr.size(), recovered_size);
drop(mmr);
let mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("third"),
&hasher,
test_config(),
)
.await
.unwrap();
assert_eq!(mmr.size(), recovered_size);
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_recovery_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_recovery_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_recovery_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_recovery_inner::<mmb::Family>);
}
async fn full_pruning_inner<F: Family>(context: deterministic::Context) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
const LEAF_COUNT: usize = 2000;
let cfg_pruned = test_config();
let mut pruned_mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("pruned"),
&hasher,
cfg_pruned.clone(),
)
.await
.unwrap();
let cfg_unpruned = Config {
journal_partition: "unpruned-journal-partition".into(),
metadata_partition: "unpruned-metadata-partition".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(1024),
replay_buffer: NZUsize!(1024),
strategy: Sequential,
page_cache: cfg_pruned.page_cache.clone(),
};
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("unpruned"),
&hasher,
cfg_unpruned,
)
.await
.unwrap();
let mut leaves = Vec::with_capacity(LEAF_COUNT);
for i in 0..LEAF_COUNT {
leaves.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for leaf in &leaves {
batch = batch.add(&hasher, leaf);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mut batch = pruned_mmr.new_batch();
for leaf in &leaves {
batch = batch.add(&hasher, leaf);
}
let batch = batch.merkleize(pruned_mmr.mem(), &hasher);
pruned_mmr = pruned_mmr.apply_batch(&batch).unwrap();
let expected_size = Position::<F>::try_from(Location::<F>::new(LEAF_COUNT as u64)).unwrap();
assert_eq!(mmr.size(), expected_size);
assert_eq!(pruned_mmr.size(), expected_size);
for i in 0usize..300 {
let prune_loc = Location::<F>::new(std::cmp::min(i as u64 * 10, *pruned_mmr.leaves()));
pruned_mmr = pruned_mmr.prune(prune_loc).await.unwrap();
assert_eq!(prune_loc, pruned_mmr.bounds().start);
let digest = test_digest(LEAF_COUNT + i);
leaves.push(digest);
let last_leaf = leaves.last().unwrap();
let batch = pruned_mmr.new_batch().add(&hasher, last_leaf);
let batch = batch.merkleize(pruned_mmr.mem(), &hasher);
pruned_mmr = pruned_mmr.apply_batch(&batch).unwrap();
let batch = mmr.new_batch().add(&hasher, last_leaf);
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
assert_eq!(
pruned_mmr.root(&hasher, 0).unwrap(),
mmr.root(&hasher, 0).unwrap()
);
}
pruned_mmr = pruned_mmr.sync().await.unwrap();
assert_eq!(
pruned_mmr.root(&hasher, 0).unwrap(),
mmr.root(&hasher, 0).unwrap()
);
pruned_mmr.sync().await.unwrap();
let mut pruned_mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("pruned_reopen"),
&hasher,
cfg_pruned.clone(),
)
.await
.unwrap();
assert_eq!(
pruned_mmr.root(&hasher, 0).unwrap(),
mmr.root(&hasher, 0).unwrap()
);
let size = pruned_mmr.size();
pruned_mmr = pruned_mmr.prune_all().await.unwrap();
assert_eq!(
pruned_mmr.root(&hasher, 0).unwrap(),
mmr.root(&hasher, 0).unwrap()
);
let bounds = pruned_mmr.bounds();
assert!(bounds.is_empty());
assert_eq!(bounds.start, Location::<F>::try_from(size).unwrap());
let batch = mmr.new_batch().add(&hasher, &test_digest(LEAF_COUNT));
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let batch = pruned_mmr
.new_batch()
.add(&hasher, &test_digest(LEAF_COUNT));
let batch = batch.merkleize(pruned_mmr.mem(), &hasher);
pruned_mmr = pruned_mmr.apply_batch(&batch).unwrap();
assert!(*pruned_mmr.size() % cfg_pruned.items_per_blob != 0);
pruned_mmr.sync().await.unwrap();
let mut pruned_mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("pruned_reopen").with_attribute("index", 2),
&hasher,
cfg_pruned.clone(),
)
.await
.unwrap();
assert_eq!(
pruned_mmr.root(&hasher, 0).unwrap(),
mmr.root(&hasher, 0).unwrap()
);
let bounds = pruned_mmr.bounds();
assert!(!bounds.is_empty());
assert_eq!(bounds.start, Location::<F>::try_from(size).unwrap());
pruned_mmr = pruned_mmr
.prune(Location::<F>::try_from(size).unwrap() - 1)
.await
.unwrap();
assert_eq!(
pruned_mmr.bounds().start,
Location::<F>::try_from(size).unwrap()
);
while *pruned_mmr.size() % cfg_pruned.items_per_blob != 0 {
let batch = pruned_mmr
.new_batch()
.add(&hasher, &test_digest(LEAF_COUNT));
let batch = batch.merkleize(pruned_mmr.mem(), &hasher);
pruned_mmr = pruned_mmr.apply_batch(&batch).unwrap();
}
pruned_mmr = pruned_mmr.prune_all().await.unwrap();
assert!(pruned_mmr.bounds().is_empty());
pruned_mmr.destroy().await.unwrap();
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_pruning_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_pruning_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_pruning_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_pruning_inner::<mmb::Family>);
}
async fn full_recovery_with_pruning_inner<F: Family>(context: deterministic::Context) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
const LEAF_COUNT: usize = 2000;
let mut leaves = Vec::with_capacity(LEAF_COUNT);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
for i in 0..LEAF_COUNT {
leaves.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for leaf in &leaves {
batch = batch.add(&hasher, leaf);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let expected_size = Position::<F>::try_from(Location::<F>::new(LEAF_COUNT as u64)).unwrap();
assert_eq!(mmr.size(), expected_size);
let mmr = mmr.sync().await.unwrap();
drop(mmr);
for i in 0usize..200 {
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("iter").with_attribute("index", i),
&hasher,
test_config(),
)
.await
.unwrap();
let start_size = mmr.size();
let start_leaves = *mmr.leaves();
let prune_loc = Location::<F>::new(std::cmp::min(i as u64 * 50, start_leaves));
if i % 5 == 0 {
mmr.simulate_pruning_failure(prune_loc).await.unwrap();
continue;
}
mmr = mmr.prune(prune_loc).await.unwrap();
for j in 0..10 {
let digest = test_digest(100 * (i + 1) + j);
leaves.push(digest);
let batch = mmr
.new_batch()
.add(&hasher, leaves.last().unwrap())
.add(&hasher, leaves.last().unwrap());
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let digest = test_digest(LEAF_COUNT + i);
leaves.push(digest);
let batch = mmr
.new_batch()
.add(&hasher, leaves.last().unwrap())
.add(&hasher, leaves.last().unwrap());
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
}
let end_size = mmr.size();
let total_to_write = (*end_size - *start_size) as usize;
let partial_write_limit = i % total_to_write;
mmr.simulate_partial_sync(partial_write_limit)
.await
.unwrap();
}
let mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("final"),
&hasher,
test_config(),
)
.await
.unwrap();
mmr.destroy().await.unwrap();
}
#[test_traced("WARN")]
fn test_full_recovery_with_pruning_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_recovery_with_pruning_inner::<mmr::Family>);
}
#[test_traced("WARN")]
fn test_full_recovery_with_pruning_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_recovery_with_pruning_inner::<mmb::Family>);
}
async fn full_historical_proof_basic_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(context, &hasher, cfg)
.await
.unwrap();
let mut elements = Vec::new();
for i in 0..10 {
elements.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for elt in &elements {
batch = batch.add(&hasher, elt);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let original_leaves = mmr.leaves();
let historical_proof = mmr
.historical_range_proof(
&hasher,
original_leaves,
Location::<F>::new(2)..Location::<F>::new(6),
0,
)
.await
.unwrap();
assert_eq!(historical_proof.leaves, original_leaves);
let root = mmr.root(&hasher, 0).unwrap();
assert!(historical_proof.verify_range_inclusion(
&hasher,
&elements[2..6],
Location::<F>::new(2),
&root
));
let regular_proof = mmr
.range_proof(&hasher, Location::<F>::new(2)..Location::<F>::new(6), 0)
.await
.unwrap();
assert_eq!(regular_proof.leaves, historical_proof.leaves);
assert_eq!(regular_proof.digests, historical_proof.digests);
for i in 10..20 {
elements.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for elt in &elements[10..20] {
batch = batch.add(&hasher, elt);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let new_historical_proof = mmr
.historical_range_proof(
&hasher,
original_leaves,
Location::<F>::new(2)..Location::<F>::new(6),
0,
)
.await
.unwrap();
assert_eq!(new_historical_proof.leaves, historical_proof.leaves);
assert_eq!(new_historical_proof.digests, historical_proof.digests);
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_basic_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_basic_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_basic_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_basic_inner::<mmb::Family>);
}
async fn full_historical_proof_with_pruning_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("main"), &hasher, test_config())
.await
.unwrap();
let mut elements = Vec::new();
for i in 0..50 {
elements.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for elt in &elements {
batch = batch.add(&hasher, elt);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let prune_loc = Location::<F>::new(16);
let mmr = mmr.prune(prune_loc).await.unwrap();
let mut ref_mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("ref"),
&hasher,
Config {
journal_partition: "ref-journal-pruned".into(),
metadata_partition: "ref-metadata-pruned".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(1024),
replay_buffer: NZUsize!(1024),
strategy: Sequential,
page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
},
)
.await
.unwrap();
let mut batch = ref_mmr.new_batch();
for elt in elements.iter().take(41) {
batch = batch.add(&hasher, elt);
}
let batch = batch.merkleize(ref_mmr.mem(), &hasher);
ref_mmr = ref_mmr.apply_batch(&batch).unwrap();
let historical_leaves = ref_mmr.leaves();
let historical_root = ref_mmr.root(&hasher, 0).unwrap();
let historical_proof = mmr
.historical_range_proof(
&hasher,
historical_leaves,
Location::<F>::new(35)..Location::<F>::new(39),
0,
)
.await
.unwrap();
assert_eq!(historical_proof.leaves, historical_leaves);
assert!(historical_proof.verify_range_inclusion(
&hasher,
&elements[35..39],
Location::<F>::new(35),
&historical_root
));
ref_mmr.destroy().await.unwrap();
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_with_pruning_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_with_pruning_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_with_pruning_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_with_pruning_inner::<mmb::Family>);
}
async fn full_historical_proof_large_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("server"),
&hasher,
Config {
journal_partition: "server-journal".into(),
metadata_partition: "server-metadata".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(1024),
replay_buffer: NZUsize!(1024),
strategy: Sequential,
page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
},
)
.await
.unwrap();
let mut elements = Vec::new();
for i in 0..100 {
elements.push(test_digest(i));
}
let mut batch = mmr.new_batch();
for elt in &elements {
batch = batch.add(&hasher, elt);
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let range = Location::<F>::new(30)..Location::<F>::new(61);
let mut ref_mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("client"),
&hasher,
Config {
journal_partition: "client-journal".into(),
metadata_partition: "client-metadata".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(1024),
replay_buffer: NZUsize!(1024),
strategy: Sequential,
page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
},
)
.await
.unwrap();
let mut batch = ref_mmr.new_batch();
for elt in elements.iter().take(*range.end as usize) {
batch = batch.add(&hasher, elt);
}
let batch = batch.merkleize(ref_mmr.mem(), &hasher);
ref_mmr = ref_mmr.apply_batch(&batch).unwrap();
let historical_leaves = ref_mmr.leaves();
let expected_root = ref_mmr.root(&hasher, 0).unwrap();
let proof = mmr
.historical_range_proof(&hasher, historical_leaves, range.clone(), 0)
.await
.unwrap();
assert!(proof.verify_range_inclusion(
&hasher,
&elements[range.to_usize_range()],
range.start,
&expected_root, ));
ref_mmr.destroy().await.unwrap();
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_large_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_large_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_large_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_large_inner::<mmb::Family>);
}
async fn full_historical_proof_singleton_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(context, &hasher, cfg)
.await
.unwrap();
let element = test_digest(0);
let batch = mmr.new_batch().add(&hasher, &element);
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let single_proof = mmr
.historical_range_proof(
&hasher,
Location::<F>::new(1),
Location::<F>::new(0)..Location::<F>::new(1),
0,
)
.await
.unwrap();
let root = mmr.root(&hasher, 0).unwrap();
assert!(single_proof.verify_range_inclusion(
&hasher,
&[element],
Location::<F>::new(0),
&root
));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_singleton_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_singleton_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_singleton_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_singleton_inner::<mmb::Family>);
}
async fn full_init_sync_empty_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
context.storage_fault_config().write().remove_rate =
Some(commonware_utils::probability!(1.0));
let sync_cfg = SyncConfig::<F, sha256::Digest, Sequential> {
config: test_config(),
range: non_empty_range!(Location::<F>::new(0), Location::<F>::new(52)),
pinned_nodes: None,
};
let mut sync_mmr =
Merkle::<F, _, Digest, Sequential>::init_sync(context.child("storage"), sync_cfg)
.await
.unwrap();
context.storage_fault_config().write().remove_rate = None;
assert_eq!(sync_mmr.size(), 0);
let bounds = sync_mmr.bounds();
assert_eq!(bounds.start, 0);
assert!(bounds.is_empty());
let new_element = test_digest(999);
let batch = sync_mmr.new_batch().add(&hasher, &new_element);
let batch = batch.merkleize(sync_mmr.mem(), &hasher);
sync_mmr = sync_mmr.apply_batch(&batch).unwrap();
let _root = sync_mmr.root(&hasher, 0).unwrap();
sync_mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_init_sync_empty_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_empty_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_init_sync_empty_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_empty_inner::<mmb::Family>);
}
async fn init_sync_empty_at_start_keeps_journal_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let start = Location::<F>::new(16);
let end = Location::<F>::new(32);
let prune_pos = Position::try_from(start).unwrap();
let mut reference_cfg = test_config();
reference_cfg.journal_partition = "reference-journal".into();
reference_cfg.metadata_partition = "reference-metadata".into();
let reference = seed_recovery_tree::<F>(&context, reference_cfg, *end).await;
let root = reference.root(&hasher, 0).unwrap();
let pins = reference.pinned_nodes_at(start).await.unwrap();
reference.destroy().await.unwrap();
let cfg = test_config();
let merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("first"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(start, end),
pinned_nodes: Some(pins.clone()),
},
)
.await
.unwrap();
assert_eq!(merkle.size(), prune_pos);
drop(merkle);
context.storage_fault_config().write().remove_rate =
Some(commonware_utils::probability!(1.0));
let mut merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("retry"),
SyncConfig {
config: cfg,
range: non_empty_range!(start, end),
pinned_nodes: Some(pins),
},
)
.await
.unwrap();
context.storage_fault_config().write().remove_rate = None;
assert_eq!(merkle.size(), prune_pos);
let mut batch = merkle.new_batch();
for i in *start..*end {
batch = batch.add(&hasher, &test_digest(i as usize));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
merkle.destroy().await.unwrap();
}
#[test_traced]
fn test_init_sync_empty_at_start_keeps_journal_mmr() {
deterministic::Runner::default()
.start(init_sync_empty_at_start_keeps_journal_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_empty_at_start_keeps_journal_mmb() {
deterministic::Runner::default()
.start(init_sync_empty_at_start_keeps_journal_inner::<mmb::Family>);
}
async fn full_init_sync_nonempty_exact_match_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mmr = mmr.sync().await.unwrap();
let original_size = mmr.size();
let original_leaves = mmr.leaves();
let original_root = mmr.root(&hasher, 0).unwrap();
let lower_bound_loc = mmr.bounds().start;
let upper_bound_loc = mmr.leaves();
let lower_bound_pos = Position::<F>::try_from(lower_bound_loc).unwrap();
let upper_bound_pos = mmr.size();
let mut expected_nodes = BTreeMap::new();
for i in *lower_bound_pos..*upper_bound_pos {
expected_nodes.insert(
Position::<F>::new(i),
mmr.get_node(Position::<F>::new(i)).await.unwrap().unwrap(),
);
}
let sync_cfg = SyncConfig::<F, sha256::Digest, Sequential> {
config: test_config(),
range: non_empty_range!(lower_bound_loc, upper_bound_loc),
pinned_nodes: None,
};
let mmr = mmr.sync().await.unwrap();
drop(mmr);
let sync_mmr =
Merkle::<F, _, Digest, Sequential>::init_sync(context.child("sync"), sync_cfg)
.await
.unwrap();
assert_eq!(sync_mmr.size(), original_size);
assert_eq!(sync_mmr.leaves(), original_leaves);
let bounds = sync_mmr.bounds();
assert_eq!(bounds.start, lower_bound_loc);
assert!(!bounds.is_empty());
assert_eq!(sync_mmr.root(&hasher, 0).unwrap(), original_root);
for pos in *lower_bound_pos..*upper_bound_pos {
let pos = Position::<F>::new(pos);
assert_eq!(
sync_mmr.get_node(pos).await.unwrap(),
expected_nodes.get(&pos).cloned()
);
}
sync_mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_init_sync_nonempty_exact_match_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_nonempty_exact_match_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_init_sync_nonempty_exact_match_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_nonempty_exact_match_inner::<mmb::Family>);
}
async fn full_init_sync_partial_overlap_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..30 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mmr = mmr.sync().await.unwrap();
let mmr = mmr.prune(Location::<F>::new(6)).await.unwrap();
let original_size = mmr.size();
let original_leaves = mmr.leaves();
let original_root = mmr.root(&hasher, 0).unwrap();
let original_pruning_boundary = mmr.bounds().start;
let original_pruning_pos = Position::<F>::try_from(original_pruning_boundary).unwrap();
let lower_bound_loc = original_pruning_boundary;
let upper_bound_loc = original_leaves + 6;
let mut expected_nodes = BTreeMap::new();
for i in *original_pruning_pos..*original_size {
let pos = Position::<F>::new(i);
expected_nodes.insert(pos, mmr.get_node(pos).await.unwrap().unwrap());
}
let sync_cfg = SyncConfig::<F, sha256::Digest, Sequential> {
config: test_config(),
range: non_empty_range!(lower_bound_loc, upper_bound_loc),
pinned_nodes: None,
};
let mmr = mmr.sync().await.unwrap();
drop(mmr);
let sync_mmr =
Merkle::<F, _, Digest, Sequential>::init_sync(context.child("sync"), sync_cfg)
.await
.unwrap();
assert_eq!(sync_mmr.size(), original_size);
let bounds = sync_mmr.bounds();
assert_eq!(bounds.start, lower_bound_loc);
assert!(!bounds.is_empty());
assert_eq!(sync_mmr.root(&hasher, 0).unwrap(), original_root);
for i in *original_pruning_pos..*original_size {
let pos = Position::<F>::new(i);
assert_eq!(
sync_mmr.get_node(pos).await.unwrap(),
expected_nodes.get(&pos).cloned()
);
}
sync_mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_init_sync_partial_overlap_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_partial_overlap_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_init_sync_partial_overlap_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_partial_overlap_inner::<mmb::Family>);
}
async fn seed_recovery_tree<F: Family>(
context: &deterministic::Context,
cfg: Config<Sequential>,
leaves: u64,
) -> Merkle<F, deterministic::Context, Digest, Sequential> {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("seed"), &hasher, cfg)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..leaves {
batch = batch.add(&hasher, &test_digest(i as usize));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
merkle.sync().await.unwrap()
}
async fn init_sync_recovered_pins_reopen_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
for (leaves, start, end) in [(16, 16, 32), (100, 32, 128)] {
let context = context.child(if leaves == 16 { "reset" } else { "reuse" });
let cfg = test_config();
let merkle = seed_recovery_tree::<F>(&context, cfg.clone(), leaves).await;
let root = merkle.root(&hasher, 0).unwrap();
drop(merkle);
let merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(Location::new(start), Location::new(end)),
pinned_nodes: None,
},
)
.await
.unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
_ = merkle.sync().await.unwrap();
let merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("reopen"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
assert_eq!(merkle.bounds(), Location::new(start)..Location::new(leaves));
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
let batch = merkle
.new_batch()
.add(&hasher, &test_digest(leaves as usize));
let batch = batch.merkleize(merkle.mem(), &hasher);
let merkle = merkle.apply_batch(&batch).unwrap();
let appended_root = merkle.root(&hasher, 0).unwrap();
_ = merkle.sync().await.unwrap();
let merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("after_append"),
&hasher,
cfg,
)
.await
.unwrap();
assert_eq!(merkle.leaves(), leaves + 1);
assert_eq!(merkle.root(&hasher, 0).unwrap(), appended_root);
merkle.destroy().await.unwrap();
}
}
#[test]
fn test_init_sync_recovered_pins_reopen_mmr() {
deterministic::Runner::default()
.start(init_sync_recovered_pins_reopen_inner::<mmr::Family>);
}
#[test]
fn test_init_sync_recovered_pins_reopen_mmb() {
deterministic::Runner::default()
.start(init_sync_recovered_pins_reopen_inner::<mmb::Family>);
}
fn init_sync_metadata_ahead_of_journal_inner<F: Family>() {
let (root, checkpoint) =
deterministic::Runner::default().start_and_recover(|context| async move {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut source_cfg = test_config();
source_cfg.journal_partition = "source-journal".into();
source_cfg.metadata_partition = "source-metadata".into();
let source = seed_recovery_tree::<F>(&context, source_cfg, 16).await;
let root = source.root(&hasher, 0).unwrap();
let pins = source.pinned_nodes_at(Location::new(16)).await.unwrap();
source.destroy().await.unwrap();
let cfg = test_config();
drop(seed_recovery_tree::<F>(&context, cfg.clone(), 4).await);
let mut metadata = Metadata::<_, U64, Vec<u8>>::init(
context.child("metadata"),
MConfig {
partition: cfg.metadata_partition,
codec_config: ((0..).into(), ()),
},
)
.await
.unwrap();
metadata.put(U64::new(PRUNED_TO_PREFIX, 0), 16u64.to_be_bytes().to_vec());
for (pos, pin) in F::nodes_to_pin(Location::new(16)).zip(pins) {
metadata.put(U64::new(NODE_PREFIX, *pos), pin.to_vec());
}
_ = metadata.sync().await.unwrap();
root
});
deterministic::Runner::from(checkpoint).start(|context| async move {
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
assert!(matches!(
Merkle::<F, _, Digest, Sequential>::init(
context.child("ordinary"),
&hasher,
cfg.clone()
)
.await,
Err(Error::MissingNode(_))
));
let merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("retry"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(Location::new(16), Location::new(32)),
pinned_nodes: None,
},
)
.await
.unwrap();
assert_eq!(merkle.bounds(), Location::new(16)..Location::new(16));
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
drop(merkle);
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.bounds(), Location::new(16)..Location::new(16));
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
merkle.destroy().await.unwrap();
});
}
#[test]
fn test_init_sync_metadata_ahead_of_journal_mmr() {
init_sync_metadata_ahead_of_journal_inner::<mmr::Family>();
}
#[test]
fn test_init_sync_metadata_ahead_of_journal_mmb() {
init_sync_metadata_ahead_of_journal_inner::<mmb::Family>();
}
fn init_sync_recovered_pins_crash_inner<F: Family>() {
for (leaves, start, end) in [(16, 16, 32), (100, 32, 128)] {
for seed in 0..16 {
let ((root, succeeded), checkpoint) = deterministic::Runner::seeded(seed)
.start_and_recover(move |context| async move {
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
let merkle = seed_recovery_tree::<F>(&context, cfg.clone(), leaves).await;
let root = merkle.root(&hasher, 0).unwrap();
drop(merkle);
*context.storage_fault_config().write() = deterministic::FaultConfig {
write_rate: Some(deterministic::WriteConfig {
failure_rate: commonware_utils::probability!(0.2),
retention_rate: commonware_utils::probability!(0.5),
mode: deterministic::PartialWriteMode::Subset,
}),
sync_rate: Some(commonware_utils::probability!(0.2)),
remove_rate: Some(commonware_utils::probability!(0.2)),
resize_rate: Some(deterministic::ResizeConfig {
failure_rate: commonware_utils::probability!(0.2),
partial_rate: commonware_utils::probability!(0.5),
}),
..Default::default()
};
let result = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("interrupted"),
SyncConfig {
config: cfg,
range: non_empty_range!(Location::new(start), Location::new(end)),
pinned_nodes: None,
},
)
.await;
let succeeded = result.is_ok();
drop(result);
(root, succeeded)
});
deterministic::Runner::from(checkpoint).start(move |context| async move {
*context.storage_fault_config().write() = deterministic::FaultConfig::default();
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
let merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("reopen"),
&hasher,
cfg.clone(),
)
.await
.unwrap_or_else(|err| {
panic!("seed={seed} leaves={leaves} succeeded={succeeded}: {err:?}")
});
assert_eq!(merkle.leaves(), leaves);
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
drop(merkle);
let merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("retry"),
SyncConfig {
config: cfg,
range: non_empty_range!(Location::new(start), Location::new(end)),
pinned_nodes: None,
},
)
.await
.unwrap();
assert_eq!(merkle.bounds(), Location::new(start)..Location::new(leaves));
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
});
}
}
}
#[test]
fn test_init_sync_recovered_pins_crash_mmr() {
init_sync_recovered_pins_crash_inner::<mmr::Family>();
}
#[test]
fn test_init_sync_recovered_pins_crash_mmb() {
init_sync_recovered_pins_crash_inner::<mmb::Family>();
}
async fn node_blob_names(
context: &deterministic::Context,
cfg: &Config<Sequential>,
) -> Vec<u64> {
let mut names = context
.scan(&format!("{}-blobs", cfg.journal_partition))
.await
.unwrap()
.into_iter()
.map(|name| u64::from_be_bytes(name.try_into().unwrap()))
.collect::<Vec<_>>();
names.sort_unstable();
names
}
fn init_sync_after_interrupted_bounded_publication_inner<F: Family>(boundary_leaves: u64) {
let ((root_at_boundary, root_at_end), checkpoint) = deterministic::Runner::default()
.start_and_recover(move |context| async move {
let hasher = Standard::<Sha256>::new(ForwardFold);
let boundary = Location::<F>::new(boundary_leaves);
let end = Location::<F>::new(boundary_leaves * 2);
let boundary_pos = Position::<F>::try_from(boundary).unwrap();
let end_pos = Position::<F>::try_from(end).unwrap();
let mut cfg = test_config();
cfg.items_per_blob = NonZeroU64::new(*boundary_pos).unwrap();
let mut merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("seed"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..boundary_leaves as usize {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
merkle = merkle.sync().await.unwrap();
let root_at_boundary = merkle.root(&hasher, 0).unwrap();
let mut batch = merkle.new_batch();
for i in boundary_leaves as usize..boundary_leaves as usize * 2 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let root_at_end = merkle.root(&hasher, 0).unwrap();
let mut merkle = merkle.sync().await.unwrap();
if boundary_leaves == 11 {
merkle = merkle.prune(Location::new(10)).await.unwrap();
let pins: Vec<_> = F::nodes_to_pin(boundary).collect();
assert!(pins.iter().any(|pos| {
merkle.metadata.get(&U64::new(NODE_PREFIX, **pos)).is_some()
}));
assert!(pins.iter().any(|pos| {
merkle.metadata.get(&U64::new(NODE_PREFIX, **pos)).is_none()
}));
} else {
assert!(merkle.metadata.keys().next().is_none());
}
assert_eq!(merkle.journal.bounds(), 0..*end_pos);
assert_eq!(node_blob_names(&context, &cfg).await, vec![0, 1, 2]);
drop(merkle);
let pending = PendingSyncs::default();
let delayed = DelayedSyncContext {
inner: context.child("bounded"),
pending: pending.clone(),
};
let mut bounded = Box::pin(Merkle::<F, _, Digest, Sequential>::init_at_most(
delayed,
&hasher,
cfg.clone(),
boundary,
));
poll_fn(|cx| match bounded.as_mut().poll(cx) {
Poll::Ready(result) => {
panic!("init_at_most returned before its predecessor sync: {result:?}")
}
Poll::Pending if pending.lock().is_empty() => {
cx.waker().wake_by_ref();
Poll::Pending
}
Poll::Pending => Poll::Ready(()),
})
.await;
let deferred = next_pending_sync(&pending);
deferred
.blocked
.await
.expect("predecessor sync never reached its deferred completion");
assert_eq!(pending.starts(), 1);
assert_eq!(pending.entered(), 1);
assert_eq!(pending.completions(), 0);
assert!(
bounded.as_mut().now_or_never().is_none(),
"init_at_most must still await predecessor durability"
);
assert_eq!(node_blob_names(&context, &cfg).await, vec![0]);
drop(bounded);
drop(deferred.release);
(root_at_boundary, root_at_end)
});
let (root_at_end, checkpoint) =
deterministic::Runner::from(checkpoint).start_and_recover(move |context| async move {
let hasher = Standard::<Sha256>::new(ForwardFold);
let boundary = Location::<F>::new(boundary_leaves);
let end = Location::<F>::new(boundary_leaves * 2);
let boundary_pos = Position::<F>::try_from(boundary).unwrap();
let mut cfg = test_config();
cfg.items_per_blob = NonZeroU64::new(*boundary_pos).unwrap();
assert_eq!(node_blob_names(&context, &cfg).await, vec![0]);
assert_eq!(persisted_watermark(&context).await, Some(*boundary_pos));
let mut merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(boundary, end),
pinned_nodes: None,
},
)
.await
.unwrap();
assert_eq!(merkle.bounds(), boundary..boundary);
assert_eq!(merkle.root(&hasher, 0).unwrap(), root_at_boundary);
let mut batch = merkle.new_batch();
for i in boundary_leaves as usize..boundary_leaves as usize * 2 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), root_at_end);
_ = merkle.sync().await.unwrap();
root_at_end
});
deterministic::Runner::from(checkpoint).start(move |context| async move {
let hasher = Standard::<Sha256>::new(ForwardFold);
let boundary = Location::<F>::new(boundary_leaves);
let end = Location::<F>::new(boundary_leaves * 2);
let boundary_pos = Position::<F>::try_from(boundary).unwrap();
let mut cfg = test_config();
cfg.items_per_blob = NonZeroU64::new(*boundary_pos).unwrap();
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.bounds(), boundary..end);
assert_eq!(merkle.root(&hasher, 0).unwrap(), root_at_end);
merkle.destroy().await.unwrap();
});
}
#[test]
fn test_init_sync_after_interrupted_bounded_publication_mmr() {
for boundary in [8, 11] {
init_sync_after_interrupted_bounded_publication_inner::<mmr::Family>(boundary);
}
}
#[test]
fn test_init_sync_after_interrupted_bounded_publication_mmb() {
for boundary in [8, 11] {
init_sync_after_interrupted_bounded_publication_inner::<mmb::Family>(boundary);
}
}
async fn init_sync_after_interrupted_clear_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let local_start = Location::<F>::new(7);
let local_end = Location::<F>::new(12);
let items_per_blob = test_config().items_per_blob.get();
assert_ne!(
*Position::<F>::try_from(local_start).unwrap() % items_per_blob,
0
);
for (start, target, end) in [(20, 20, 28), (4, 4, 12), (9, 20, 16)] {
for (removed, pinned) in [(false, true), (false, false), (true, true), (true, false)] {
let context = context
.child("case")
.with_attribute("start", start)
.with_attribute("removed", removed)
.with_attribute("pinned", pinned);
let mut cfg = test_config();
cfg.journal_partition = format!("journal-{start}-{removed}-{pinned}");
cfg.metadata_partition = format!("metadata-{start}-{removed}-{pinned}");
let start = Location::<F>::new(start);
let end = Location::<F>::new(end);
let target = Position::<F>::try_from(Location::<F>::new(target)).unwrap();
let mut reference_cfg = cfg.clone();
reference_cfg.journal_partition.push_str("-reference");
reference_cfg.metadata_partition.push_str("-reference");
let reference = seed_recovery_tree::<F>(&context, reference_cfg, *end).await;
let root = reference.root(&hasher, 0).unwrap();
let local_pins = reference.pinned_nodes_at(local_start).await.unwrap();
let pins = reference.pinned_nodes_at(start).await.unwrap();
reference.destroy().await.unwrap();
let mut merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("local"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(local_start, local_end),
pinned_nodes: Some(local_pins),
},
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in *local_start..*local_end {
batch = batch.add(&hasher, &test_digest(i as usize));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
_ = merkle.sync().await.unwrap();
let mut metadata = Metadata::<_, U64, Vec<u8>>::init(
context.child("metadata"),
MConfig {
partition: cfg.metadata_partition.clone(),
codec_config: ((0..).into(), ()),
},
)
.await
.unwrap();
metadata.retain(|key, _| key.prefix() != NODE_PREFIX);
metadata.put(
U64::new(PRUNED_TO_PREFIX, 0),
start.as_u64().to_be_bytes().to_vec(),
);
for (pos, pin) in F::nodes_to_pin(start).zip(&pins) {
metadata.put(U64::new(NODE_PREFIX, *pos), pin.to_vec());
}
_ = metadata.sync().await.unwrap();
Journal::<_, Digest>::test_stage_clear(
context.child("intent"),
&cfg.journal_partition,
*target,
)
.await
.unwrap();
if removed {
context
.remove(&format!("{}-blobs", cfg.journal_partition), None)
.await
.unwrap();
}
let mut merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(start, end),
pinned_nodes: pinned.then_some(pins),
},
)
.await
.unwrap();
assert_eq!(merkle.size(), Position::try_from(start).unwrap());
let mut batch = merkle.new_batch();
for i in *start..*end {
batch = batch.add(&hasher, &test_digest(i as usize));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
_ = merkle.sync().await.unwrap();
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.bounds(), start..end);
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
merkle.destroy().await.unwrap();
}
}
}
#[test]
fn test_init_sync_after_interrupted_clear_mmr() {
deterministic::Runner::default()
.start(init_sync_after_interrupted_clear_inner::<mmr::Family>);
}
#[test]
fn test_init_sync_after_interrupted_clear_mmb() {
deterministic::Runner::default()
.start(init_sync_after_interrupted_clear_inner::<mmb::Family>);
}
async fn init_sync_rejects_malformed_metadata_pin_inner<F: Family>(
context: deterministic::Context,
) {
let cfg = test_config();
let boundary = Location::<F>::new(11);
let merkle = seed_recovery_tree::<F>(&context, cfg.clone(), 22).await;
let mut merkle = merkle.prune(Location::new(10)).await.unwrap();
let pos = F::nodes_to_pin(boundary)
.find(|pos| merkle.metadata.get(&U64::new(NODE_PREFIX, **pos)).is_some())
.unwrap();
assert!(merkle.journal.read(*pos).await.is_ok());
merkle.metadata.put(U64::new(NODE_PREFIX, *pos), Vec::new());
merkle.metadata = merkle.metadata.sync().await.unwrap();
drop(merkle);
let result = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg,
range: non_empty_range!(boundary, Location::new(22)),
pinned_nodes: None,
},
)
.await;
assert!(matches!(result, Err(Error::DataCorrupted(_))));
}
#[test]
fn test_init_sync_rejects_malformed_metadata_pin_mmr() {
deterministic::Runner::default()
.start(init_sync_rejects_malformed_metadata_pin_inner::<mmr::Family>);
}
#[test]
fn test_init_sync_rejects_malformed_metadata_pin_mmb() {
deterministic::Runner::default()
.start(init_sync_rejects_malformed_metadata_pin_inner::<mmb::Family>);
}
async fn init_sync_rejects_missing_pin_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let start = Location::<F>::new(6);
let end = Location::<F>::new(12);
let pins: Vec<_> = F::nodes_to_pin(start).collect();
let highest = *pins.iter().max().unwrap();
assert_eq!(*highest, 9);
for (leaves, blobs, missing) in [(5, vec![0, 1], highest), (3, vec![0], pins[0])] {
let context = context.child("stored").with_attribute("leaves", leaves);
let mut cfg = test_config();
cfg.journal_partition = format!("journal-{leaves}");
cfg.metadata_partition = format!("metadata-{leaves}");
let merkle = seed_recovery_tree::<F>(&context, cfg.clone(), leaves).await;
let root = merkle.root(&hasher, 0).unwrap();
drop(merkle);
assert_eq!(node_blob_names(&context, &cfg).await, blobs);
let result = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(start, end),
pinned_nodes: None,
},
)
.await;
assert!(
matches!(&result, Err(Error::MissingNode(pos)) if *pos == missing),
"{result:?}"
);
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.leaves(), Location::new(leaves));
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
merkle.destroy().await.unwrap();
}
}
#[test]
fn test_init_sync_rejects_missing_pin_mmr() {
deterministic::Runner::default().start(init_sync_rejects_missing_pin_inner::<mmr::Family>);
}
#[test]
fn test_init_sync_rejects_missing_pin_mmb() {
deterministic::Runner::default().start(init_sync_rejects_missing_pin_inner::<mmb::Family>);
}
fn init_recovery_prune_crash_inner<F: Family>() {
let boundary = Position::try_from(Location::<F>::new(8)).unwrap();
for per_blob in [1, 5, 7, *boundary] {
for unbounded in [false, true] {
let (root, checkpoint) =
deterministic::Runner::default().start_and_recover(move |context| async move {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut cfg = test_config();
cfg.items_per_blob = NonZeroU64::new(per_blob).unwrap();
let merkle = seed_recovery_tree::<F>(&context, cfg.clone(), 16).await;
let (merkle, _) = merkle.update_metadata(boundary).await.unwrap();
drop(merkle);
let mut pending = Merkle::<F, _, Digest, Sequential>::prepare(
context.child("cap"),
&hasher,
cfg.clone(),
Some(Location::new(8)),
)
.await
.unwrap();
let root = pending.mem.root(&hasher, 0).unwrap();
pending.journal = pending
.journal
.truncate(*pending.retained_size)
.await
.unwrap();
if unbounded {
drop(pending);
pending = Merkle::<F, _, Digest, Sequential>::prepare(
context.child("ordinary"),
&hasher,
cfg,
None,
)
.await
.unwrap();
pending.journal = pending
.journal
.truncate(*pending.retained_size)
.await
.unwrap();
}
(pending.journal, _) = pending
.journal
.prune(*pending.metadata_prune_pos)
.await
.unwrap();
drop(pending);
root
});
deterministic::Runner::from(checkpoint).start(move |context| async move {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut cfg = test_config();
cfg.items_per_blob = NonZeroU64::new(per_blob).unwrap();
let merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("reopen"),
&hasher,
cfg.clone(),
)
.await
.unwrap_or_else(|err| {
panic!("per_blob={per_blob} unbounded={unbounded}: {err:?}")
});
assert_eq!(merkle.bounds(), Location::new(8)..Location::new(8));
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
drop(merkle);
let merkle = Merkle::<F, _, Digest, Sequential>::init_at_most(
context.child("retry"),
&hasher,
cfg,
Location::new(8),
)
.await
.unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
});
}
}
}
#[test]
fn test_init_recovery_prune_crash_mmr() {
init_recovery_prune_crash_inner::<mmr::Family>();
}
#[test]
fn test_init_recovery_prune_crash_mmb() {
init_recovery_prune_crash_inner::<mmb::Family>();
}
async fn full_init_sync_truncates_state_beyond_range_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, cfg.clone())
.await
.unwrap();
let target_end = Location::<F>::new(20);
let mut batch = merkle.new_batch();
for i in 0..20 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let target_root = merkle.root(&hasher, 0).unwrap();
let restart = Location::<F>::new(7);
let pinned_nodes = merkle.pinned_nodes_at(restart).await.unwrap();
let mut batch = merkle.new_batch();
for i in 20..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let merkle = merkle.sync().await.unwrap();
drop(merkle);
let sync_cfg = SyncConfig::<F, sha256::Digest, Sequential> {
config: cfg.clone(),
range: non_empty_range!(restart, target_end),
pinned_nodes: Some(pinned_nodes),
};
let merkle = Merkle::<F, _, Digest, Sequential>::init_sync(context.child("sync"), sync_cfg)
.await
.unwrap();
assert_eq!(merkle.leaves(), target_end);
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
let merkle = merkle.sync().await.unwrap();
drop(merkle);
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.leaves(), target_end);
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
merkle.destroy().await.unwrap();
}
#[test_traced]
fn test_full_init_sync_truncates_state_beyond_range_mmr() {
deterministic::Runner::default()
.start(full_init_sync_truncates_state_beyond_range_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_init_sync_truncates_state_beyond_range_mmb() {
deterministic::Runner::default()
.start(full_init_sync_truncates_state_beyond_range_inner::<mmb::Family>);
}
async fn full_init_sync_discards_state_pruned_past_range_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, cfg.clone())
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let target_root = merkle.root(&hasher, 0).unwrap();
let restart = Location::<F>::new(7);
let pinned_nodes = merkle.pinned_nodes_at(restart).await.unwrap();
let merkle = merkle.sync().await.unwrap();
let merkle = merkle.prune(Location::new(30)).await.unwrap();
let merkle = merkle.sync().await.unwrap();
assert!(merkle.bounds().start > restart);
drop(merkle);
let sync_cfg = SyncConfig::<F, sha256::Digest, Sequential> {
config: cfg,
range: non_empty_range!(restart, Location::<F>::new(60)),
pinned_nodes: Some(pinned_nodes),
};
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init_sync(context.child("sync"), sync_cfg)
.await
.unwrap();
assert_eq!(merkle.bounds(), restart..restart);
let mut batch = merkle.new_batch();
for i in 7..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
merkle.destroy().await.unwrap();
}
#[test_traced]
fn test_full_init_sync_discards_state_pruned_past_range_mmr() {
deterministic::Runner::default()
.start(full_init_sync_discards_state_pruned_past_range_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_init_sync_discards_state_pruned_past_range_mmb() {
deterministic::Runner::default()
.start(full_init_sync_discards_state_pruned_past_range_inner::<mmb::Family>);
}
async fn tear_node_blob(context: &deterministic::Context, cfg: &Config<Sequential>, blob: u64) {
let (blob, len) = context
.open(
&format!("{}-blobs", cfg.journal_partition),
&blob.to_be_bytes(),
)
.await
.unwrap();
blob.resize(len - 1).await.unwrap();
blob.sync().await.unwrap();
}
async fn init_sync_ahead_skips_discarded_blobs_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let restart = Location::<F>::new(7);
let end = Location::<F>::new(20);
let end_pos = Position::<F>::try_from(end).unwrap();
let stale = || U64::new(NODE_PREFIX, u64::MAX);
let mut calls = Vec::new();
let mut reads = Vec::new();
for (stored_leaves, torn) in [(50, false), (80, false), (80, true)] {
let context = context
.child("stored")
.with_attribute("leaves", stored_leaves)
.with_attribute("torn", torn);
let mut cfg = test_config();
cfg.journal_partition = format!("journal-{torn}");
cfg.metadata_partition = format!("metadata-{torn}");
let mut merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("seed"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..20 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let target_root = merkle.root(&hasher, 0).unwrap();
let pinned_nodes = merkle.pinned_nodes_at(restart).await.unwrap();
let mut batch = merkle.new_batch();
for i in 20..stored_leaves {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let merkle = merkle.sync().await.unwrap();
let newest = (*merkle.size() - 1) / cfg.items_per_blob.get();
assert!(newest * cfg.items_per_blob.get() >= *end_pos);
drop(merkle);
let metadata_cfg = MConfig {
partition: cfg.metadata_partition.clone(),
codec_config: ((0..).into(), ()),
};
let mut metadata =
Metadata::<_, U64, Vec<u8>>::init(context.child("stale"), metadata_cfg.clone())
.await
.unwrap();
metadata.put(stale(), test_digest(usize::MAX).to_vec());
_ = metadata.sync().await.unwrap();
if torn {
tear_node_blob(&context, &cfg, newest).await;
}
let pending = PendingSyncs::default();
pending.arm();
let (recorded, recordings) = RecordingContext::new(context.child("delayed"));
let delayed = DelayedSyncContext {
inner: recorded,
pending: pending.clone(),
};
let merkle = drive_pending_syncs(
&pending,
Merkle::<F, _, Digest, Sequential>::init_sync(
delayed.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(restart, end),
pinned_nodes: Some(pinned_nodes),
},
),
)
.await
.unwrap();
assert_eq!(merkle.leaves(), end);
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
calls.push(pending.calls());
reads.push(recordings.snapshot().reads.len());
drop(merkle);
let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("check"), metadata_cfg)
.await
.unwrap();
assert!(metadata.get(&stale()).is_none());
drop(metadata);
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.leaves(), end);
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
merkle.destroy().await.unwrap();
}
assert_eq!(calls[0], calls[1]);
assert_eq!(calls[1], calls[2]);
assert_eq!(reads[0], reads[1], "reading cost grew with discarded nodes");
assert_eq!(reads[1], reads[2]);
}
#[test_traced]
fn test_init_sync_ahead_skips_discarded_blobs_mmr() {
deterministic::Runner::default()
.start(init_sync_ahead_skips_discarded_blobs_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_ahead_skips_discarded_blobs_mmb() {
deterministic::Runner::default()
.start(init_sync_ahead_skips_discarded_blobs_inner::<mmb::Family>);
}
async fn init_sync_reset_leaves_retained_blobs_unopened_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let restart = Location::<F>::new(7);
let end = Location::<F>::new(20);
let mut calls = Vec::new();
for torn in [false, true] {
let context = context.child(if torn { "torn" } else { "clean" });
let mut cfg = test_config();
cfg.journal_partition = format!("journal-{torn}");
cfg.metadata_partition = format!("metadata-{torn}");
let mut merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("seed"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let target_root = merkle.root(&hasher, 0).unwrap();
let pinned_nodes = merkle.pinned_nodes_at(restart).await.unwrap();
let merkle = merkle.sync().await.unwrap();
let merkle = merkle.prune(Location::new(30)).await.unwrap();
let merkle = merkle.sync().await.unwrap();
assert!(merkle.bounds().start > end);
let newest = (*merkle.size() - 1) / cfg.items_per_blob.get();
drop(merkle);
if torn {
tear_node_blob(&context, &cfg, newest).await;
}
let pending = PendingSyncs::default();
pending.arm();
let delayed = DelayedSyncContext {
inner: context,
pending: pending.clone(),
};
let mut merkle = drive_pending_syncs(
&pending,
Merkle::<F, _, Digest, Sequential>::init_sync(
delayed.child("sync"),
SyncConfig {
config: cfg,
range: non_empty_range!(restart, end),
pinned_nodes: Some(pinned_nodes),
},
),
)
.await
.unwrap();
assert_eq!(merkle.bounds(), restart..restart);
calls.push(pending.calls());
let mut batch = merkle.new_batch();
for i in 7..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
merkle.destroy().await.unwrap();
}
assert_eq!(calls[0], calls[1]);
}
#[test_traced]
fn test_init_sync_reset_leaves_retained_blobs_unopened_mmr() {
deterministic::Runner::default()
.start(init_sync_reset_leaves_retained_blobs_unopened_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_reset_leaves_retained_blobs_unopened_mmb() {
deterministic::Runner::default()
.start(init_sync_reset_leaves_retained_blobs_unopened_inner::<mmb::Family>);
}
async fn init_sync_invalid_pins_leave_pruned_journal_intact_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let restart = Location::<F>::new(7);
let end = Location::<F>::new(20);
let cfg = test_config();
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("seed"), &hasher, cfg.clone())
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let root = merkle.root(&hasher, 0).unwrap();
let mut pinned_nodes = merkle.pinned_nodes_at(restart).await.unwrap();
let merkle = merkle.sync().await.unwrap();
let merkle = merkle.prune(Location::new(30)).await.unwrap();
let merkle = merkle.sync().await.unwrap();
let bounds = merkle.bounds();
assert!(bounds.start > end);
drop(merkle);
pinned_nodes.push(test_digest(usize::MAX));
let result = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(restart, end),
pinned_nodes: Some(pinned_nodes),
},
)
.await;
assert!(matches!(result, Err(Error::InvalidPinnedNodes)));
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.bounds(), bounds);
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
merkle.destroy().await.unwrap();
}
#[test_traced]
fn test_init_sync_invalid_pins_leave_pruned_journal_intact_mmr() {
deterministic::Runner::default()
.start(init_sync_invalid_pins_leave_pruned_journal_intact_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_invalid_pins_leave_pruned_journal_intact_mmb() {
deterministic::Runner::default()
.start(init_sync_invalid_pins_leave_pruned_journal_intact_inner::<mmb::Family>);
}
async fn init_sync_ahead_within_end_blob_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let restart = Location::<F>::new(7);
let end = Location::<F>::new(20);
let end_pos = Position::<F>::try_from(end).unwrap();
let stale = || U64::new(NODE_PREFIX, u64::MAX);
let cfg = test_config();
let items_per_blob = cfg.items_per_blob.get();
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("seed"), &hasher, cfg.clone())
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..20 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let target_root = merkle.root(&hasher, 0).unwrap();
let pinned_nodes = merkle.pinned_nodes_at(restart).await.unwrap();
let mut next = 20;
while *merkle.size() <= *end_pos {
let batch = merkle.new_batch().add(&hasher, &test_digest(next));
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
next += 1;
}
let merkle = merkle.sync().await.unwrap();
drop(merkle);
let newest = context
.scan(&format!("{}-blobs", cfg.journal_partition))
.await
.unwrap()
.into_iter()
.map(|name| u64::from_be_bytes(name.try_into().unwrap()))
.max()
.unwrap();
assert_eq!(newest, *end_pos / items_per_blob);
let metadata_cfg = MConfig {
partition: cfg.metadata_partition.clone(),
codec_config: ((0..).into(), ()),
};
let mut metadata =
Metadata::<_, U64, Vec<u8>>::init(context.child("stale"), metadata_cfg.clone())
.await
.unwrap();
metadata.put(stale(), test_digest(usize::MAX).to_vec());
_ = metadata.sync().await.unwrap();
let merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(restart, end),
pinned_nodes: Some(pinned_nodes),
},
)
.await
.unwrap();
assert_eq!(merkle.leaves(), end);
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
drop(merkle);
let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("check"), metadata_cfg)
.await
.unwrap();
assert!(metadata.get(&stale()).is_none());
drop(metadata);
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.leaves(), end);
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
merkle.destroy().await.unwrap();
}
#[test_traced]
fn test_init_sync_ahead_within_end_blob_mmr() {
deterministic::Runner::default()
.start(init_sync_ahead_within_end_blob_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_ahead_within_end_blob_mmb() {
deterministic::Runner::default()
.start(init_sync_ahead_within_end_blob_inner::<mmb::Family>);
}
async fn init_sync_repairs_retained_torn_tail_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let restart = Location::<F>::new(7);
let end = Location::<F>::new(22);
let end_pos = Position::<F>::try_from(end).unwrap();
let mut calls = Vec::new();
for torn in [false, true] {
let context = context.child(if torn { "torn" } else { "clean" });
let mut cfg = test_config();
cfg.journal_partition = format!("journal-{torn}");
cfg.metadata_partition = format!("metadata-{torn}");
let items_per_blob = cfg.items_per_blob.get();
let nodes = (*end_pos - 1) % items_per_blob + 1;
assert!(nodes * size_of::<Digest>() as u64 > u64::from(PAGE_SIZE.get()));
let pending = PendingSyncs::default();
let delayed = DelayedSyncContext {
inner: context.child("seeding"),
pending: pending.clone(),
};
let merkle = drive_pending_syncs(
&pending,
Merkle::<F, _, Digest, Sequential>::init(
delayed.child("seed"),
&hasher,
cfg.clone(),
),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..end.as_u64() as usize {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
let merkle = merkle.apply_batch(&batch).unwrap();
let target_root = merkle.root(&hasher, 0).unwrap();
let pinned_nodes = merkle.pinned_nodes_at(restart).await.unwrap();
let merkle = drive_pending_syncs(&pending, merkle.flush()).await.unwrap();
let (merkle, handle) = merkle.start_sync().await.unwrap();
drive_pending_syncs(&pending, handle).await.unwrap();
assert_eq!(merkle.size(), end_pos);
drop(merkle);
let newest = (*end_pos - 1) / items_per_blob;
let watermark = Journal::<_, Digest>::persisted_watermark(
context.child("probe"),
&cfg.journal_partition,
)
.await
.unwrap()
.unwrap();
assert!(watermark <= newest * items_per_blob);
if torn {
tear_node_blob(&context, &cfg, newest).await;
}
let pending = PendingSyncs::default();
pending.arm();
let delayed = DelayedSyncContext {
inner: context.child("syncing"),
pending: pending.clone(),
};
let mut merkle = drive_pending_syncs(
&pending,
Merkle::<F, _, Digest, Sequential>::init_sync(
delayed.child("sync"),
SyncConfig {
config: cfg,
range: non_empty_range!(restart, end),
pinned_nodes: Some(pinned_nodes),
},
),
)
.await
.unwrap();
calls.push(pending.calls());
if torn {
assert!(merkle.leaves() < end);
} else {
assert_eq!(merkle.leaves(), end);
}
let mut batch = merkle.new_batch();
for i in merkle.leaves().as_u64()..end.as_u64() {
batch = batch.add(&hasher, &test_digest(i as usize));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
merkle.destroy().await.unwrap();
}
assert!(calls[1] > calls[0]);
}
#[test_traced]
fn test_init_sync_repairs_retained_torn_tail_mmr() {
deterministic::Runner::default()
.start(init_sync_repairs_retained_torn_tail_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_repairs_retained_torn_tail_mmb() {
deterministic::Runner::default()
.start(init_sync_repairs_retained_torn_tail_inner::<mmb::Family>);
}
async fn init_sync_start_below_retained_leaves_blobs_unopened_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let restart = Location::<F>::new(20);
let end = Location::<F>::new(60);
let prune_pos = Position::<F>::try_from(restart).unwrap();
let end_pos = Position::<F>::try_from(end).unwrap();
let stale = || U64::new(NODE_PREFIX, u64::MAX);
let mut calls = Vec::new();
for torn in [false, true] {
let context = context.child(if torn { "torn" } else { "clean" });
let mut cfg = test_config();
cfg.journal_partition = format!("journal-{torn}");
cfg.metadata_partition = format!("metadata-{torn}");
let items_per_blob = cfg.items_per_blob.get();
let pending = PendingSyncs::default();
let delayed = DelayedSyncContext {
inner: context.child("seeding"),
pending: pending.clone(),
};
let mut merkle = drive_pending_syncs(
&pending,
Merkle::<F, _, Digest, Sequential>::init(
delayed.child("seed"),
&hasher,
cfg.clone(),
),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let pinned_nodes = merkle.pinned_nodes_at(restart).await.unwrap();
let merkle = drive_pending_syncs(&pending, merkle.sync()).await.unwrap();
let mut merkle = drive_pending_syncs(&pending, merkle.prune(Location::new(30)))
.await
.unwrap();
let boundary = *Position::<F>::try_from(merkle.bounds().start).unwrap();
let retained = boundary / items_per_blob * items_per_blob;
assert!(*prune_pos < retained && retained < *end_pos);
let mut batch = merkle.new_batch();
for i in 50..60 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let target_root = merkle.root(&hasher, 0).unwrap();
let merkle = drive_pending_syncs(&pending, merkle.flush()).await.unwrap();
let (merkle, handle) = merkle.start_sync().await.unwrap();
drive_pending_syncs(&pending, handle).await.unwrap();
let newest = (*merkle.size() - 1) / items_per_blob;
drop(merkle);
let watermark = Journal::<_, Digest>::persisted_watermark(
context.child("probe"),
&cfg.journal_partition,
)
.await
.unwrap()
.unwrap();
assert!(watermark <= newest * items_per_blob);
assert!(newest * items_per_blob < *end_pos);
let metadata_cfg = MConfig {
partition: cfg.metadata_partition.clone(),
codec_config: ((0..).into(), ()),
};
let mut metadata =
Metadata::<_, U64, Vec<u8>>::init(context.child("stale"), metadata_cfg.clone())
.await
.unwrap();
metadata.put(stale(), test_digest(usize::MAX).to_vec());
_ = metadata.sync().await.unwrap();
if torn {
tear_node_blob(&context, &cfg, newest).await;
}
let pending = PendingSyncs::default();
pending.arm();
let delayed = DelayedSyncContext {
inner: context.child("syncing"),
pending: pending.clone(),
};
let mut merkle = drive_pending_syncs(
&pending,
Merkle::<F, _, Digest, Sequential>::init_sync(
delayed.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(restart, end),
pinned_nodes: Some(pinned_nodes),
},
),
)
.await
.unwrap();
assert_eq!(merkle.bounds(), restart..restart);
calls.push(pending.calls());
let mut batch = merkle.new_batch();
for i in 20..60 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
drop(merkle);
let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("check"), metadata_cfg)
.await
.unwrap();
assert!(metadata.get(&stale()).is_none());
drop(metadata);
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.bounds(), restart..restart);
merkle.destroy().await.unwrap();
}
assert_eq!(calls[0], calls[1]);
}
#[test_traced]
fn test_init_sync_start_below_retained_leaves_blobs_unopened_mmr() {
deterministic::Runner::default()
.start(init_sync_start_below_retained_leaves_blobs_unopened_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_start_below_retained_leaves_blobs_unopened_mmb() {
deterministic::Runner::default()
.start(init_sync_start_below_retained_leaves_blobs_unopened_inner::<mmb::Family>);
}
async fn init_sync_stale_below_start_leaves_blobs_unopened_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let restart = Location::<F>::new(30);
let end = Location::<F>::new(50);
let prune_pos = Position::<F>::try_from(restart).unwrap();
let stale = || U64::new(NODE_PREFIX, u64::MAX);
let mut reference_cfg = test_config();
reference_cfg.journal_partition = "journal-reference".into();
reference_cfg.metadata_partition = "metadata-reference".into();
let mut reference = Merkle::<F, _, Digest, Sequential>::init(
context.child("reference"),
&hasher,
reference_cfg,
)
.await
.unwrap();
let mut batch = reference.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(reference.mem(), &hasher);
reference = reference.apply_batch(&batch).unwrap();
let target_root = reference.root(&hasher, 0).unwrap();
let pinned_nodes = reference.pinned_nodes_at(restart).await.unwrap();
reference.destroy().await.unwrap();
let mut calls = Vec::new();
for torn in [false, true] {
let context = context.child(if torn { "torn" } else { "clean" });
let mut cfg = test_config();
cfg.journal_partition = format!("journal-{torn}");
cfg.metadata_partition = format!("metadata-{torn}");
let items_per_blob = cfg.items_per_blob.get();
let pending = PendingSyncs::default();
let delayed = DelayedSyncContext {
inner: context.child("seeding"),
pending: pending.clone(),
};
let mut merkle = drive_pending_syncs(
&pending,
Merkle::<F, _, Digest, Sequential>::init(
delayed.child("seed"),
&hasher,
cfg.clone(),
),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..20 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let merkle = drive_pending_syncs(&pending, merkle.flush()).await.unwrap();
let (merkle, handle) = merkle.start_sync().await.unwrap();
drive_pending_syncs(&pending, handle).await.unwrap();
let newest = (*merkle.size() - 1) / items_per_blob;
drop(merkle);
let watermark = Journal::<_, Digest>::persisted_watermark(
context.child("probe"),
&cfg.journal_partition,
)
.await
.unwrap()
.unwrap_or(0);
assert!(watermark <= newest * items_per_blob);
assert!((newest + 1) * items_per_blob <= *prune_pos);
let metadata_cfg = MConfig {
partition: cfg.metadata_partition.clone(),
codec_config: ((0..).into(), ()),
};
let mut metadata =
Metadata::<_, U64, Vec<u8>>::init(context.child("stale"), metadata_cfg.clone())
.await
.unwrap();
metadata.put(stale(), test_digest(usize::MAX).to_vec());
_ = metadata.sync().await.unwrap();
if torn {
tear_node_blob(&context, &cfg, newest).await;
}
let pending = PendingSyncs::default();
pending.arm();
let delayed = DelayedSyncContext {
inner: context.child("syncing"),
pending: pending.clone(),
};
let mut merkle = drive_pending_syncs(
&pending,
Merkle::<F, _, Digest, Sequential>::init_sync(
delayed.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(restart, end),
pinned_nodes: Some(pinned_nodes.clone()),
},
),
)
.await
.unwrap();
assert_eq!(merkle.bounds(), restart..restart);
calls.push(pending.calls());
let mut batch = merkle.new_batch();
for i in 30..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
drop(merkle);
let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("check"), metadata_cfg)
.await
.unwrap();
assert!(metadata.get(&stale()).is_none());
drop(metadata);
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.bounds(), restart..restart);
merkle.destroy().await.unwrap();
}
assert_eq!(calls[0], calls[1]);
}
#[test_traced]
fn test_init_sync_stale_below_start_leaves_blobs_unopened_mmr() {
deterministic::Runner::default()
.start(init_sync_stale_below_start_leaves_blobs_unopened_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_stale_below_start_leaves_blobs_unopened_mmb() {
deterministic::Runner::default()
.start(init_sync_stale_below_start_leaves_blobs_unopened_inner::<mmb::Family>);
}
#[test_traced]
fn test_full_init_sync_discards_stale_pinned_metadata() {
deterministic::Runner::default().start(|context| async move {
type F = mmr::Family;
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
let mut merkle = Merkle::<F, _, Digest, Sequential>::init(
context.child("old"),
&hasher,
cfg.clone(),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..40 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let merkle = merkle.sync().await.unwrap();
let merkle = merkle.prune(Location::new(30)).await.unwrap();
let merkle = merkle.sync().await.unwrap();
drop(merkle);
let mut target_cfg = test_config();
target_cfg.journal_partition = "target-journal-partition".into();
target_cfg.metadata_partition = "target-metadata-partition".into();
let mut target = Merkle::<F, _, Digest, Sequential>::init(
context.child("target"),
&hasher,
target_cfg,
)
.await
.unwrap();
let mut batch = target.new_batch();
for i in 0..20 {
batch = batch.add(&hasher, &test_digest(1_000 + i));
}
let batch = batch.merkleize(target.mem(), &hasher);
target = target.apply_batch(&batch).unwrap();
let target_root = target.root(&hasher, 0).unwrap();
let restart = Location::new(7);
let pinned_nodes = target.pinned_nodes_at(restart).await.unwrap();
target.destroy().await.unwrap();
let sync_cfg = SyncConfig::<F, sha256::Digest, Sequential> {
config: cfg.clone(),
range: non_empty_range!(restart, Location::new(20)),
pinned_nodes: Some(pinned_nodes),
};
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init_sync(context.child("sync"), sync_cfg)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 7..20 {
batch = batch.add(&hasher, &test_digest(1_000 + i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
let merkle = merkle.sync().await.unwrap();
drop(merkle);
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), target_root);
merkle.destroy().await.unwrap();
});
}
async fn full_init_sync_rejects_extra_pinned_nodes_inner<F: Family>(
context: deterministic::Context,
) {
let sync_cfg = SyncConfig::<F, sha256::Digest, Sequential> {
config: test_config(),
range: non_empty_range!(Location::<F>::new(6), Location::<F>::new(20)),
pinned_nodes: Some(vec![test_digest(1), test_digest(2), test_digest(3)]),
};
let result =
Merkle::<F, _, Digest, Sequential>::init_sync(context.child("sync"), sync_cfg).await;
assert!(matches!(result, Err(Error::InvalidPinnedNodes)));
}
#[test_traced]
fn test_full_init_sync_rejects_extra_pinned_nodes_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_rejects_extra_pinned_nodes_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_init_sync_rejects_extra_pinned_nodes_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_rejects_extra_pinned_nodes_inner::<mmb::Family>);
}
async fn full_init_stale_metadata_returns_error_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mmr = mmr.sync().await.unwrap();
let prune_loc = Location::<F>::new(25);
mmr.prune(prune_loc).await.unwrap();
let meta_cfg = MConfig {
partition: test_config().metadata_partition,
codec_config: ((0..).into(), ()),
};
let mut metadata =
Metadata::<_, U64, Vec<u8>>::init(context.child("meta_tamper"), meta_cfg)
.await
.unwrap();
metadata.clear();
let key = U64::new(PRUNED_TO_PREFIX, 0);
metadata
.put_sync(key, 0u64.to_be_bytes().to_vec())
.await
.unwrap();
let result = Merkle::<F, _, Digest, Sequential>::init(
context.child("reopened"),
&hasher,
test_config(),
)
.await;
match result {
Err(Error::MissingNode(_)) => {} Ok(_) => panic!("expected MissingNode error, got Ok"),
Err(e) => panic!("expected MissingNode error, got {:?}", e),
}
}
#[test_traced("WARN")]
fn test_full_init_stale_metadata_returns_error_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_init_stale_metadata_returns_error_inner::<mmr::Family>);
}
#[test_traced("WARN")]
fn test_full_init_stale_metadata_returns_error_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_init_stale_metadata_returns_error_inner::<mmb::Family>);
}
async fn full_init_rejects_prune_boundary_beyond_recovered_size_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = test_config();
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, cfg.clone())
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..12 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let merkle = merkle.sync().await.unwrap();
let merkle = merkle.prune(Location::new(12)).await.unwrap();
let merkle = merkle.sync().await.unwrap();
drop(merkle);
let journal_cfg = JConfig {
partition: cfg.journal_partition.clone(),
items_per_blob: cfg.items_per_blob,
page_cache: cfg.page_cache.clone(),
write_buffer: cfg.write_buffer,
replay_buffer: cfg.replay_buffer,
};
let journal =
Journal::<_, Digest>::init(context.child("interrupted_reset"), journal_cfg.clone())
.await
.unwrap();
let recovered_size = Position::<F>::try_from(Location::<F>::new(8)).unwrap();
assert!(
journal.bounds().start > *recovered_size,
"test reset must discard a pruned prefix"
);
drop(journal);
let journal = Journal::<_, Digest>::init_at_size(
context.child("reset"),
journal_cfg,
*recovered_size,
)
.await
.unwrap();
drop(journal);
match Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg).await
{
Err(Error::MissingNode(_)) => {}
Ok(_) => panic!("pruning boundary beyond recovered size must fail closed"),
Err(err) => panic!("expected MissingNode error, got {err:?}"),
}
}
#[test_traced("WARN")]
fn test_full_init_rejects_prune_boundary_beyond_recovered_size_mmr() {
deterministic::Runner::default()
.start(full_init_rejects_prune_boundary_beyond_recovered_size_inner::<mmr::Family>);
}
#[test_traced("WARN")]
fn test_full_init_rejects_prune_boundary_beyond_recovered_size_mmb() {
deterministic::Runner::default()
.start(full_init_rejects_prune_boundary_beyond_recovered_size_inner::<mmb::Family>);
}
async fn full_init_metadata_ahead_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mmr = mmr.sync().await.unwrap();
let prune_loc = Location::<F>::new(16);
let mmr = mmr.prune(prune_loc).await.unwrap();
let expected_root = mmr.root(&hasher, 0).unwrap();
let expected_size = mmr.size();
drop(mmr);
let mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("reopened"),
&hasher,
test_config(),
)
.await
.unwrap();
assert_eq!(mmr.bounds().start, prune_loc);
assert_eq!(mmr.size(), expected_size);
assert_eq!(mmr.root(&hasher, 0).unwrap(), expected_root);
mmr.destroy().await.unwrap();
}
#[test_traced("WARN")]
fn test_full_init_metadata_ahead_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_init_metadata_ahead_inner::<mmr::Family>);
}
#[test_traced("WARN")]
fn test_full_init_metadata_ahead_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_init_metadata_ahead_inner::<mmb::Family>);
}
async fn full_init_sync_computes_pinned_nodes_before_pruning_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let cfg = Config {
journal_partition: "mmr-journal".into(),
metadata_partition: "mmr-metadata".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(64),
replay_buffer: NZUsize!(64),
strategy: Sequential,
page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, cfg.clone())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..100 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mmr = mmr.sync().await.unwrap();
let original_size = mmr.size();
let original_root = mmr.root(&hasher, 0).unwrap();
drop(mmr);
let prune_loc = Location::<F>::new(32);
let sync_cfg = SyncConfig::<F, sha256::Digest, Sequential> {
config: cfg,
range: non_empty_range!(prune_loc, Location::<F>::new(128)),
pinned_nodes: None, };
let sync_mmr =
Merkle::<F, _, Digest, Sequential>::init_sync(context.child("sync"), sync_cfg)
.await
.unwrap();
assert_eq!(sync_mmr.size(), original_size);
assert_eq!(sync_mmr.root(&hasher, 0).unwrap(), original_root);
assert_eq!(sync_mmr.bounds().start, prune_loc);
sync_mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_init_sync_computes_pinned_nodes_before_pruning_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_computes_pinned_nodes_before_pruning_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_init_sync_computes_pinned_nodes_before_pruning_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_computes_pinned_nodes_before_pruning_inner::<mmb::Family>);
}
async fn full_historical_proof_pruned_elements_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..64 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let prune_loc = Location::<F>::new(16);
let mmr = mmr.prune(prune_loc).await.unwrap();
let historical_leaves = mmr.leaves();
let mut pruned_loc = None;
for loc_u64 in 0..*historical_leaves {
let loc = Location::<F>::new(loc_u64);
let result = mmr
.historical_range_proof(&hasher, historical_leaves, loc..loc + 1, 0)
.await;
if matches!(result, Err(Error::ElementPruned(_))) {
pruned_loc = Some(loc);
break;
}
}
let pruned_loc = pruned_loc.expect("expected at least one pruned location");
let mut batch = mmr.new_batch();
for i in 0..8 {
batch = batch.add(&hasher, &test_digest(10_000 + i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
let mmr = mmr.apply_batch(&batch).unwrap();
let requested = mmr.leaves();
let result = mmr
.historical_range_proof(&hasher, requested, pruned_loc..pruned_loc + 1, 0)
.await;
assert!(matches!(result, Err(Error::ElementPruned(_))));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_pruned_elements_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_pruned_elements_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_pruned_elements_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_pruned_elements_inner::<mmb::Family>);
}
async fn full_append_while_historical_proof_is_available_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..20 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let historical_leaves = Location::<F>::new(10);
let range = Location::<F>::new(2)..Location::<F>::new(8);
let batch = mmr
.new_batch()
.add(&hasher, &test_digest(100))
.add(&hasher, &test_digest(101));
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let proof = mmr
.historical_range_proof(&hasher, historical_leaves, range.clone(), 0)
.await
.unwrap();
let expected = mmr
.historical_range_proof(&hasher, historical_leaves, range, 0)
.await
.unwrap();
assert_eq!(proof, expected);
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_append_while_historical_proof_is_available_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_append_while_historical_proof_is_available_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_append_while_historical_proof_is_available_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_append_while_historical_proof_is_available_inner::<mmb::Family>);
}
async fn full_historical_proof_after_sync_reads_from_journal_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..64 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mmr = mmr.sync().await.unwrap();
let historical_leaves = Location::<F>::new(20);
let range = Location::<F>::new(5)..Location::<F>::new(15);
let expected = mmr
.historical_range_proof(&hasher, historical_leaves, range.clone(), 0)
.await
.unwrap();
let actual = mmr
.historical_range_proof(&hasher, historical_leaves, range, 0)
.await
.unwrap();
assert_eq!(actual, expected);
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_after_sync_reads_from_journal_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_after_sync_reads_from_journal_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_after_sync_reads_from_journal_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_after_sync_reads_from_journal_inner::<mmb::Family>);
}
async fn full_historical_proof_after_pruning_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..30 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let prune_loc = Location::<F>::new(10);
let mmr = mmr.prune(prune_loc).await.unwrap();
let requested = Location::<F>::new(20);
let range = prune_loc..requested;
let proof = mmr
.historical_range_proof(&hasher, requested, range, 0)
.await
.unwrap();
assert!(proof.leaves > Location::<F>::new(0));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_after_pruning_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_after_pruning_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_after_pruning_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_after_pruning_inner::<mmb::Family>);
}
async fn full_historical_proof_edge_cases_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("empty"),
&hasher,
test_config(),
)
.await
.unwrap();
let empty_end = Location::<F>::new(0);
let empty_result = mmr
.historical_range_proof(&hasher, empty_end, empty_end..empty_end, 0)
.await;
assert!(matches!(empty_result, Err(Error::Empty)));
let oob_result = mmr
.historical_range_proof(&hasher, empty_end + 1, empty_end..empty_end + 1, 0)
.await;
assert!(matches!(
oob_result,
Err(Error::RangeOutOfBounds(loc)) if loc == empty_end + 1
));
mmr.destroy().await.unwrap();
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("fully_pruned"),
&hasher,
test_config(),
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..20 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let end = mmr.leaves();
mmr = mmr.prune_all().await.unwrap();
assert!(mmr.bounds().is_empty());
let pruned_result = mmr
.historical_range_proof(&hasher, end, end - 1..end, 0)
.await;
assert!(matches!(pruned_result, Err(Error::ElementPruned(_))));
let oob_result = mmr
.historical_range_proof(&hasher, end + 1, end - 1..end, 0)
.await;
assert!(matches!(
oob_result,
Err(Error::RangeOutOfBounds(loc)) if loc == end + 1
));
mmr.destroy().await.unwrap();
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("single_leaf"),
&hasher,
test_config(),
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..11 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let end = mmr.leaves();
let keep_loc = end - 1;
mmr = mmr.prune(keep_loc).await.unwrap();
let ok_result = mmr
.historical_range_proof(&hasher, end, keep_loc..end, 0)
.await;
assert!(ok_result.is_ok());
let pruned_end = keep_loc - 1;
let start_loc = Location::<F>::new(1);
let pruned_result = mmr
.historical_range_proof(&hasher, end, start_loc..pruned_end + 1, 0)
.await;
assert!(matches!(pruned_result, Err(Error::ElementPruned(_))));
let oob_result = mmr
.historical_range_proof(&hasher, end + 1, keep_loc..end, 0)
.await;
assert!(matches!(oob_result, Err(Error::RangeOutOfBounds(_))));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_edge_cases_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_edge_cases_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_edge_cases_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_edge_cases_inner::<mmb::Family>);
}
async fn full_historical_proof_out_of_bounds_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("oob"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..8 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let requested = mmr.leaves() + 1;
let result = mmr
.historical_range_proof(&hasher, requested, Location::<F>::new(0)..requested, 0)
.await;
assert!(matches!(
result,
Err(Error::RangeOutOfBounds(loc)) if loc == requested
));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_out_of_bounds_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_out_of_bounds_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_out_of_bounds_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_out_of_bounds_inner::<mmb::Family>);
}
async fn full_historical_proof_range_validation_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("range_validation"),
&hasher,
test_config(),
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..32 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let valid_range = Location::<F>::new(0)..Location::<F>::new(1);
let requested = Location::<F>::new(5);
let empty_range = requested..requested;
let empty_result = mmr
.historical_range_proof(&hasher, requested, empty_range, 0)
.await;
assert!(matches!(empty_result, Err(Error::Empty)));
let leaves_oob = mmr.leaves() + 1;
let result = mmr
.historical_range_proof(&hasher, leaves_oob, valid_range.clone(), 0)
.await;
assert!(matches!(
result,
Err(Error::RangeOutOfBounds(loc)) if loc == leaves_oob
));
let end_oob = mmr.leaves() + 1;
let range_oob = Location::<F>::new(0)..end_oob;
let result = mmr
.historical_range_proof(&hasher, requested, range_oob, 0)
.await;
assert!(matches!(
result,
Err(Error::RangeOutOfBounds(loc)) if loc == end_oob
));
let range_end_gt_requested = requested + 1;
let range_oob_at_requested = Location::<F>::new(0)..range_end_gt_requested;
assert!(range_end_gt_requested <= mmr.leaves());
let result = mmr
.historical_range_proof(&hasher, requested, range_oob_at_requested, 0)
.await;
assert!(matches!(
result,
Err(Error::RangeOutOfBounds(loc)) if loc == range_end_gt_requested
));
let overflow_loc = Location::<F>::new(u64::MAX);
let overflow_range = Location::<F>::new(0)..overflow_loc;
let result = mmr
.historical_range_proof(&hasher, requested, overflow_range, 0)
.await;
assert!(matches!(
result,
Err(Error::RangeOutOfBounds(loc)) if loc == overflow_loc
));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_range_validation_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_range_validation_inner::<mmr::Family>);
}
#[test_traced]
fn test_full_historical_proof_range_validation_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_historical_proof_range_validation_inner::<mmb::Family>);
}
async fn full_historical_proof_non_size_prune_excludes_pruned_leaves_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("non_size_prune"),
&hasher,
test_config(),
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..16 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let end = mmr.leaves();
let mut failures = Vec::new();
for prune_leaf in 1..*end {
let prune_loc = Location::<F>::new(prune_leaf);
mmr = mmr.prune(prune_loc).await.unwrap();
for loc_u64 in 0..*end {
let loc = Location::<F>::new(loc_u64);
let range_includes_pruned_leaf = loc < prune_loc;
match mmr.historical_proof(&hasher, end, loc, 0).await {
Ok(_) => {}
Err(Error::ElementPruned(_)) if range_includes_pruned_leaf => {}
Err(Error::ElementPruned(_)) => failures.push(format!(
"prune_loc={prune_loc} loc={loc} returned ElementPruned without a pruned range element"
)),
Err(err) => failures
.push(format!("prune_loc={prune_loc} loc={loc} err={err}")),
}
}
}
assert!(
failures.is_empty(),
"historical proof generation returned unexpected errors: {failures:?}"
);
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_full_historical_proof_non_size_prune_excludes_pruned_leaves_mmr() {
let executor = deterministic::Runner::default();
executor.start(
full_historical_proof_non_size_prune_excludes_pruned_leaves_inner::<mmr::Family>,
);
}
#[test_traced]
fn test_full_historical_proof_non_size_prune_excludes_pruned_leaves_mmb() {
let executor = deterministic::Runner::default();
executor.start(
full_historical_proof_non_size_prune_excludes_pruned_leaves_inner::<mmb::Family>,
);
}
async fn full_init_sync_recovers_from_invalid_journal_size_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr =
Merkle::<F, _, Digest, Sequential>::init(context.child("init"), &hasher, test_config())
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..3 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let valid_size = mmr.size();
let valid_root = mmr.root(&hasher, 0).unwrap();
let mmr = mmr.sync().await.unwrap();
drop(mmr);
{
let journal: Journal<_, Digest> = Journal::init(
context.child("corrupt"),
JConfig {
partition: "journal-partition".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(1024),
replay_buffer: NZUsize!(1024),
page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
},
)
.await
.unwrap();
assert_eq!(journal.size(), valid_size);
let (journal, _) = journal.append(&Sha256::hash(&[b"orphan"])).await.unwrap();
let journal = journal.sync().await.unwrap();
assert_eq!(journal.size(), valid_size + 1);
}
let sync_cfg = SyncConfig::<F, Digest, Sequential> {
config: test_config(),
range: non_empty_range!(Location::<F>::new(0), Location::<F>::new(100)),
pinned_nodes: None,
};
let sync_mmr =
Merkle::<F, _, Digest, Sequential>::init_sync(context.child("sync"), sync_cfg)
.await
.unwrap();
assert_eq!(sync_mmr.size(), valid_size);
assert_eq!(sync_mmr.root(&hasher, 0).unwrap(), valid_root);
sync_mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_init_sync_recovers_from_invalid_journal_size_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_recovers_from_invalid_journal_size_inner::<mmr::Family>);
}
#[test_traced]
fn test_init_sync_recovers_from_invalid_journal_size_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_init_sync_recovers_from_invalid_journal_size_inner::<mmb::Family>);
}
async fn full_stale_batch_inner<F: Family>(context: deterministic::Context) {
let hasher: Standard<Sha256> = Standard::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("storage"),
&Standard::<Sha256>::new(ForwardFold),
test_config(),
)
.await
.unwrap();
let batch_a = mmr.new_batch().add(&hasher, b"leaf-a");
let batch_a = batch_a.merkleize(mmr.mem(), &hasher);
let batch_b = mmr.new_batch().add(&hasher, b"leaf-b");
let batch_b = batch_b.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch_a).unwrap();
assert!(matches!(
mmr.apply_batch(&batch_b),
Err(Error::StaleBatch { .. })
));
}
#[test]
fn test_stale_batch_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_stale_batch_inner::<mmr::Family>);
}
#[test]
fn test_stale_batch_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_stale_batch_inner::<mmb::Family>);
}
async fn full_new_batch_returns_append_only_wrapper_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("storage"),
&hasher,
test_config(),
)
.await
.unwrap();
let _batch: UnmerkleizedBatch<F, Digest, Sequential> = mmr.new_batch();
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_new_batch_returns_append_only_wrapper_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_new_batch_returns_append_only_wrapper_inner::<mmr::Family>);
}
#[test_traced]
fn test_new_batch_returns_append_only_wrapper_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_new_batch_returns_append_only_wrapper_inner::<mmb::Family>);
}
async fn full_update_leaf_after_sync_returns_pruned_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let mut mmr = Merkle::<F, _, Digest, Sequential>::init(
context.child("storage"),
&hasher,
test_config(),
)
.await
.unwrap();
let mut batch = mmr.new_batch();
for i in 0..50 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(mmr.mem(), &hasher);
mmr = mmr.apply_batch(&batch).unwrap();
let mmr = mmr.sync().await.unwrap();
let batch = mmr.to_batch().new_batch();
let result = batch.update_leaf(&hasher, Location::<F>::new(0), b"updated");
assert!(matches!(result, Err(Error::ElementPruned(_))));
mmr.destroy().await.unwrap();
}
#[test_traced]
fn test_update_leaf_after_sync_returns_pruned_mmr() {
let executor = deterministic::Runner::default();
executor.start(full_update_leaf_after_sync_returns_pruned_inner::<mmr::Family>);
}
#[test_traced]
fn test_update_leaf_after_sync_returns_pruned_mmb() {
let executor = deterministic::Runner::default();
executor.start(full_update_leaf_after_sync_returns_pruned_inner::<mmb::Family>);
}
async fn init_sync_genesis_drops_stale_pins_inner<F: Family>(context: deterministic::Context) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let boundary = Location::<F>::new(16);
let cfg = test_config();
let metadata_cfg = MConfig {
partition: cfg.metadata_partition.clone(),
codec_config: ((0..).into(), ()),
};
let mut metadata =
Metadata::<_, U64, Vec<u8>>::init(context.child("metadata"), metadata_cfg)
.await
.unwrap();
metadata.put(
U64::new(PRUNED_TO_PREFIX, 0),
boundary.as_u64().to_be_bytes().to_vec(),
);
for (i, pos) in F::nodes_to_pin(boundary).enumerate() {
metadata.put(U64::new(NODE_PREFIX, *pos), test_digest(1_000 + i).to_vec());
}
_ = metadata.sync().await.unwrap();
let mut merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(Location::<F>::new(0), boundary),
pinned_nodes: None,
},
)
.await
.unwrap();
assert_eq!(merkle.bounds(), Location::new(0)..Location::new(0));
let mut batch = merkle.new_batch();
for i in 0..16 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let root = merkle.root(&hasher, 0).unwrap();
_ = merkle.sync().await.unwrap();
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
merkle.destroy().await.unwrap();
}
#[test]
fn test_init_sync_genesis_drops_stale_pins_mmr() {
deterministic::Runner::default()
.start(init_sync_genesis_drops_stale_pins_inner::<mmr::Family>);
}
#[test]
fn test_init_sync_genesis_drops_stale_pins_mmb() {
deterministic::Runner::default()
.start(init_sync_genesis_drops_stale_pins_inner::<mmb::Family>);
}
async fn init_sync_lower_range_drops_future_pins_inner<F: Family>(
context: deterministic::Context,
) {
let hasher = Standard::<Sha256>::new(ForwardFold);
let abandoned = Location::<F>::new(16);
let lower = Location::<F>::new(2);
let cfg = test_config();
let metadata_cfg = MConfig {
partition: cfg.metadata_partition.clone(),
codec_config: ((0..).into(), ()),
};
let mut merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("local"), &hasher, cfg.clone())
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0..8 {
batch = batch.add(&hasher, &test_digest(i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
_ = merkle.sync().await.unwrap();
let mut source_cfg = cfg.clone();
source_cfg.journal_partition.push_str("-source");
source_cfg.metadata_partition.push_str("-source");
let source = seed_recovery_tree::<F>(&context, source_cfg, *abandoned).await;
let pins = source.pinned_nodes_at(abandoned).await.unwrap();
source.destroy().await.unwrap();
let mut metadata =
Metadata::<_, U64, Vec<u8>>::init(context.child("metadata"), metadata_cfg)
.await
.unwrap();
metadata.put(
U64::new(PRUNED_TO_PREFIX, 0),
abandoned.as_u64().to_be_bytes().to_vec(),
);
for (pos, pin) in F::nodes_to_pin(abandoned).zip(pins) {
metadata.put(U64::new(NODE_PREFIX, *pos), pin.to_vec());
}
_ = metadata.sync().await.unwrap();
let mut merkle = Merkle::<F, _, Digest, Sequential>::init_sync(
context.child("sync"),
SyncConfig {
config: cfg.clone(),
range: non_empty_range!(lower, Location::<F>::new(8)),
pinned_nodes: None,
},
)
.await
.unwrap();
assert_eq!(merkle.bounds(), lower..Location::new(8));
let mut batch = merkle.new_batch();
for i in 8..16 {
batch = batch.add(&hasher, &test_digest(1_000 + i));
}
let batch = batch.merkleize(merkle.mem(), &hasher);
merkle = merkle.apply_batch(&batch).unwrap();
let root = merkle.root(&hasher, 0).unwrap();
_ = merkle.sync().await.unwrap();
let merkle =
Merkle::<F, _, Digest, Sequential>::init(context.child("reopen"), &hasher, cfg)
.await
.unwrap();
assert_eq!(merkle.root(&hasher, 0).unwrap(), root);
merkle.destroy().await.unwrap();
}
#[test]
fn test_init_sync_lower_range_drops_future_pins_mmr() {
deterministic::Runner::default()
.start(init_sync_lower_range_drops_future_pins_inner::<mmr::Family>);
}
#[test]
fn test_init_sync_lower_range_drops_future_pins_mmb() {
deterministic::Runner::default()
.start(init_sync_lower_range_drops_future_pins_inner::<mmb::Family>);
}
}