1use crate::{
9 journal::{
10 contiguous::{fixed, variable, Contiguous, Many, Mutable},
11 Error as JournalError,
12 },
13 merkle::{
14 self, batch, full::Merkle, hasher::Standard as StandardHasher, mem::Mem, Bagging, Family,
15 Location, Position, Proof, Readable,
16 },
17 Context,
18};
19use alloc::{
20 sync::{Arc, Weak},
21 vec::Vec,
22};
23use commonware_codec::{CodecFixedShared, CodecShared, Encode, EncodeShared};
24use commonware_cryptography::{Digest, Hasher};
25use commonware_macros::boxed;
26use commonware_parallel::Strategy;
27use core::{
28 num::{NonZeroU64, NonZeroUsize},
29 ops::Range,
30};
31use futures::{try_join, Stream, TryFutureExt as _};
32use thiserror::Error;
33use tracing::{debug, warn};
34
35#[derive(Error, Debug)]
37pub enum Error<F: Family> {
38 #[error("merkle error: {0}")]
39 Merkle(#[from] merkle::Error<F>),
40
41 #[error("journal error: {0}")]
42 Journal(#[from] super::Error),
43}
44
45type MerkleizedParent<F, H, Item, S> = Arc<MerkleizedBatch<F, <H as Hasher>::Digest, Item, S>>;
47
48pub struct UnmerkleizedBatch<F: Family, H: Hasher, Item: Send + Sync, S: Strategy> {
51 inner: batch::UnmerkleizedBatch<F, H::Digest, S>,
53 hasher: StandardHasher<H>,
55 items: Vec<Item>,
57 parent: Option<MerkleizedParent<F, H, Item, S>>,
59}
60
61type MerkleizedBatchArc<F, H, Item, S> = Arc<MerkleizedBatch<F, <H as Hasher>::Digest, Item, S>>;
62
63impl<F: Family, H: Hasher, Item: Encode + Send + Sync, S: Strategy>
64 UnmerkleizedBatch<F, H, Item, S>
65{
66 #[allow(clippy::should_implement_trait)]
68 pub fn add(mut self, item: Item) -> Self {
69 let encoded = item.encode();
70 self.inner = self.inner.add(&self.hasher, &encoded);
71 self.items.push(item);
72 self
73 }
74
75 fn collect_ancestor_items(
77 parent: &Option<MerkleizedParent<F, H, Item, S>>,
78 ) -> Vec<Arc<Vec<Item>>> {
79 let Some(parent) = parent else {
80 return Vec::new();
81 };
82 let mut items = Vec::new();
83 if !parent.items.is_empty() {
84 items.push(Arc::clone(&parent.items));
85 }
86 let mut current = parent.parent.as_ref().and_then(Weak::upgrade);
87 while let Some(batch) = current {
88 if !batch.items.is_empty() {
89 items.push(Arc::clone(&batch.items));
90 }
91 current = batch.parent.as_ref().and_then(Weak::upgrade);
92 }
93 items.reverse();
94 items
95 }
96
97 pub fn merkleize(self, base: &Mem<F, H::Digest>) -> MerkleizedBatchArc<F, H, Item, S> {
100 let Self {
101 inner,
102 hasher,
103 items,
104 parent,
105 } = self;
106
107 let items = Arc::new(items);
108 let merkle = inner.merkleize(base, &hasher);
109 let ancestor_items = Self::collect_ancestor_items(&parent);
110 Arc::new(MerkleizedBatch {
111 inner: merkle,
112 bagging: hasher.root_bagging(),
113 items,
114 parent: parent.as_ref().map(Arc::downgrade),
115 ancestor_items,
116 })
117 }
118
119 pub(crate) fn add_many(mut self, items: Vec<Item>) -> Self {
125 assert!(
126 self.items.is_empty(),
127 "add_many expects no items added via add"
128 );
129
130 self.inner = self.inner.add_many(&self.hasher, &items);
131 self.items = items;
132 self
133 }
134}
135
136#[derive(Clone, Debug)]
138pub struct MerkleizedBatch<F: Family, D: Digest, Item: Send + Sync, S: Strategy> {
139 pub(crate) inner: Arc<batch::MerkleizedBatch<F, D, S>>,
141 bagging: Bagging,
143 items: Arc<Vec<Item>>,
145 parent: Option<Weak<Self>>,
147 pub(crate) ancestor_items: Vec<Arc<Vec<Item>>>,
149}
150
151impl<F: Family, D: Digest, Item: Send + Sync, S: Strategy> MerkleizedBatch<F, D, Item, S> {
152 pub(crate) fn size(&self) -> u64 {
154 *self.inner.leaves()
155 }
156
157 pub fn root(
162 &self,
163 base: &Mem<F, D>,
164 hasher: &impl merkle::hasher::Hasher<F, Digest = D>,
165 inactive_peaks: usize,
166 ) -> Result<D, merkle::Error<F>> {
167 self.inner.root(base, hasher, inactive_peaks)
168 }
169
170 pub fn proof(
172 &self,
173 hasher: &impl merkle::hasher::Hasher<F, Digest = D>,
174 loc: Location<F>,
175 inactive_peaks: usize,
176 ) -> Result<Proof<F, D>, merkle::Error<F>> {
177 self.inner.proof(hasher, loc, inactive_peaks)
178 }
179
180 pub fn range_proof(
182 &self,
183 hasher: &impl merkle::hasher::Hasher<F, Digest = D>,
184 range: core::ops::Range<Location<F>>,
185 inactive_peaks: usize,
186 ) -> Result<Proof<F, D>, merkle::Error<F>> {
187 self.inner.range_proof(hasher, range, inactive_peaks)
188 }
189
190 pub(crate) const fn items(&self) -> &Arc<Vec<Item>> {
192 &self.items
193 }
194
195 pub fn new_batch<H: Hasher<Digest = D>>(self: &Arc<Self>) -> UnmerkleizedBatch<F, H, Item, S>
200 where
201 Item: Encode,
202 {
203 UnmerkleizedBatch {
204 inner: self.inner.new_batch(),
205 hasher: StandardHasher::new(self.bagging),
206 items: Vec::new(),
207 parent: Some(Arc::clone(self)),
208 }
209 }
210}
211
212impl<F: Family, D: Digest, Item: Send + Sync, S: Strategy> Readable
213 for MerkleizedBatch<F, D, Item, S>
214{
215 type Family = F;
216 type Digest = D;
217 type Error = merkle::Error<F>;
218
219 fn size(&self) -> Position<F> {
220 self.inner.size()
221 }
222
223 fn get_node(&self, pos: Position<F>) -> Option<D> {
224 self.inner.get_node(pos)
225 }
226
227 fn pruning_boundary(&self) -> Location<F> {
228 self.inner.pruning_boundary()
229 }
230}
231
232pub struct Journal<F, E, C, H, S>
237where
238 F: Family,
239 E: Context,
240 C: Contiguous<Item: EncodeShared>,
241 H: Hasher,
242 S: Strategy,
243{
244 pub(crate) merkle: Merkle<F, E, H::Digest, S>,
247
248 pub(crate) journal: C,
251
252 pub(crate) hasher: StandardHasher<H>,
253}
254
255impl<F, E, C, H, S> Journal<F, E, C, H, S>
256where
257 F: Family,
258 E: Context,
259 C: Contiguous<Item: EncodeShared>,
260 H: Hasher,
261 S: Strategy,
262{
263 pub fn size(&self) -> Location<F> {
265 Location::new(self.journal.bounds().end)
266 }
267
268 pub fn root(&self, inactive_peaks: usize) -> Result<H::Digest, Error<F>> {
271 self.merkle
272 .root(&self.hasher, inactive_peaks)
273 .map_err(Into::into)
274 }
275
276 fn map_error(error: Error<F>) -> JournalError {
278 match error {
279 Error::Journal(inner) => inner,
280 Error::Merkle(inner) => JournalError::Merkle(anyhow::Error::from(inner)),
281 }
282 }
283
284 pub const fn strategy(&self) -> &S {
286 self.merkle.strategy()
287 }
288
289 pub fn new_batch(&self) -> UnmerkleizedBatch<F, H, C::Item, S>
291 where
292 C::Item: Encode,
293 {
294 let root = self.merkle.to_batch();
295 UnmerkleizedBatch {
296 inner: root.new_batch(),
297 hasher: StandardHasher::new(self.hasher.root_bagging()),
298 items: Vec::new(),
299 parent: None,
300 }
301 }
302
303 pub(crate) async fn merkleize(
312 &self,
313 batch: UnmerkleizedBatch<F, H, C::Item, S>,
314 items: Vec<C::Item>,
315 inactive_peaks: usize,
316 ) -> Result<(MerkleizedBatchArc<F, H, C::Item, S>, H::Digest), merkle::Error<F>>
317 where
318 C::Item: 'static,
319 {
320 let mem = self.merkle.snapshot();
321 let hasher = self.hasher.clone();
322 let strategy = self.strategy().clone();
323 strategy
324 .spawn(move |_| {
325 let merkleized = batch.add_many(items).merkleize(&mem);
326 let root = merkleized.root(&mem, &hasher, inactive_peaks)?;
327 Ok((merkleized, root))
328 })
329 .await
330 }
331
332 pub(crate) fn to_merkleized_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, C::Item, S>> {
337 Arc::new(MerkleizedBatch {
338 inner: self.merkle.to_batch(),
339 bagging: self.hasher.root_bagging(),
340 items: Arc::new(Vec::new()),
341 parent: None,
342 ancestor_items: Vec::new(),
343 })
344 }
345}
346
347impl<F, E, C, H, S> Journal<F, E, C, H, S>
348where
349 F: Family,
350 E: Context,
351 C: Mutable<Item: EncodeShared>,
352 H: Hasher,
353 S: Strategy,
354{
355 pub async fn commit(&mut self) -> Result<(), Error<F>> {
359 try_join!(
362 self.journal.commit().map_err(Error::Journal),
363 self.merkle.flush().map_err(Error::Merkle)
364 )?;
365
366 Ok(())
367 }
368}
369
370impl<F, E, C, H, S> Journal<F, E, C, H, S>
371where
372 F: Family,
373 E: Context,
374 C: Mutable<Item: EncodeShared>,
375 H: Hasher,
376 S: Strategy,
377{
378 pub async fn from_components(
381 mut merkle: Merkle<F, E, H::Digest, S>,
382 journal: C,
383 hasher: StandardHasher<H>,
384 apply_batch_size: u64,
385 ) -> Result<Self, Error<F>> {
386 Self::align(&mut merkle, &journal, &hasher, apply_batch_size).await?;
387
388 merkle.sync().await?;
391
392 Ok(Self {
393 merkle,
394 journal,
395 hasher,
396 })
397 }
398
399 async fn align(
405 merkle: &mut Merkle<F, E, H::Digest, S>,
406 journal: &C,
407 hasher: &StandardHasher<H>,
408 apply_batch_size: u64,
409 ) -> Result<(), Error<F>> {
410 let journal_size = journal.bounds().end;
412 let mut merkle_leaves = merkle.leaves();
413 if merkle_leaves > journal_size {
414 let rewind_count = merkle_leaves - journal_size;
415 warn!(
416 journal_size,
417 ?rewind_count,
418 "rewinding Merkle structure to match journal"
419 );
420 merkle.rewind(*rewind_count as usize).await?;
421 merkle_leaves = Location::new(journal_size);
422 }
423
424 if merkle_leaves < journal_size {
426 let replay_count = journal_size - *merkle_leaves;
427 warn!(
428 ?journal_size,
429 replay_count, "Merkle structure lags behind journal, replaying journal to catch up"
430 );
431
432 while merkle_leaves < journal_size {
433 let count = apply_batch_size.min(journal_size - *merkle_leaves);
434 let mut items = Vec::with_capacity(count as usize);
435 for _ in 0..count {
436 items.push(journal.read(*merkle_leaves).await?);
437 merkle_leaves += 1;
438 }
439
440 let batch = merkle.new_batch().add_many(hasher, &items);
441 let batch = merkle.with_mem(|mem| batch.merkleize(mem, hasher));
442 merkle.apply_batch(&batch)?;
443 }
444 return Ok(());
445 }
446
447 assert_eq!(journal.bounds().end, *merkle.leaves());
449
450 Ok(())
451 }
452
453 pub async fn append(&mut self, item: &C::Item) -> Result<Location<F>, Error<F>> {
455 let encoded_item = item.encode();
456
457 let loc = self.journal.append(item).await?;
459 let unmerkleized_batch = self.merkle.new_batch().add(&self.hasher, &encoded_item);
460 let batch = self
461 .merkle
462 .with_mem(|mem| unmerkleized_batch.merkleize(mem, &self.hasher));
463 self.merkle.apply_batch(&batch)?;
464
465 Ok(Location::new(loc))
466 }
467
468 pub async fn apply_batch(
475 &mut self,
476 batch: &MerkleizedBatch<F, H::Digest, C::Item, S>,
477 ) -> Result<(), Error<F>> {
478 let merkle_size = self.merkle.size();
479 let base_size = batch.inner.base_size();
480
481 let skip_ancestors = if merkle_size == base_size {
487 false
488 } else if merkle_size > base_size && merkle_size < batch.inner.size() {
489 true
490 } else {
491 return Err(merkle::Error::StaleBatch {
494 expected: base_size,
495 actual: merkle_size,
496 }
497 .into());
498 };
499
500 let committed_leaves = self.journal.bounds().end;
505 let base_leaves = *Location::<F>::try_from(base_size)?;
506 let mut batch_leaf_end = base_leaves;
507 let mut batches: Vec<&[C::Item]> = Vec::with_capacity(batch.ancestor_items.len() + 1);
508 for ancestor in &batch.ancestor_items {
509 batch_leaf_end += ancestor.len() as u64;
510 if skip_ancestors && batch_leaf_end <= committed_leaves {
511 continue;
512 }
513 batches.push(ancestor);
514 }
515 if !batch.items.is_empty() {
516 batches.push(&batch.items);
517 }
518 if !batches.is_empty() {
519 self.journal.append_many(Many::Nested(&batches)).await?;
520 }
521
522 self.merkle.apply_batch(&batch.inner)?;
523 assert_eq!(*self.merkle.leaves(), self.journal.bounds().end);
524 Ok(())
525 }
526
527 pub async fn rewind(&mut self, size: u64) -> Result<(), Error<F>> {
529 self.journal.rewind(size).await?;
530
531 let leaves = *self.merkle.leaves();
532 if leaves > size {
533 self.merkle.rewind((leaves - size) as usize).await?;
534 }
535
536 Ok(())
537 }
538
539 pub async fn prune(&mut self, prune_loc: Location<F>) -> Result<Location<F>, Error<F>> {
544 self.prune_inner(prune_loc)
545 .await
546 .map(|(boundary, _)| boundary)
547 }
548
549 async fn prune_inner(
550 &mut self,
551 prune_loc: Location<F>,
552 ) -> Result<(Location<F>, bool), Error<F>> {
553 if self.merkle.size() == 0 {
554 return Ok((Location::new(self.journal.bounds().start), false));
556 }
557
558 try_join!(
564 self.journal.commit().map_err(Error::Journal),
565 self.merkle.sync().map_err(Error::Merkle)
566 )?;
567
568 let journal_pruned = self.journal.prune(*prune_loc).await?;
569 let bounds = self.journal.bounds();
570 let boundary = Location::new(bounds.start);
571 let merkle_boundary = self.merkle.bounds().start;
572
573 if boundary > merkle_boundary {
574 debug!(size = ?bounds.end, ?prune_loc, boundary = ?bounds.start, "pruned inactive ops");
575 self.merkle.prune(boundary).await?;
576 }
577
578 Ok((boundary, journal_pruned || boundary > merkle_boundary))
579 }
580}
581
582impl<F, E, C, H, S> Journal<F, E, C, H, S>
583where
584 F: Family,
585 E: Context,
586 C: Contiguous<Item: EncodeShared>,
587 H: Hasher,
588 S: Strategy,
589{
590 pub async fn proof(
604 &self,
605 start_loc: Location<F>,
606 max_ops: NonZeroU64,
607 inactive_peaks: usize,
608 ) -> Result<(Proof<F, H::Digest>, Vec<C::Item>), Error<F>> {
609 self.historical_proof(self.size(), start_loc, max_ops, inactive_peaks)
610 .await
611 }
612
613 pub async fn historical_proof(
626 &self,
627 historical_leaves: Location<F>,
628 start_loc: Location<F>,
629 max_ops: NonZeroU64,
630 inactive_peaks: usize,
631 ) -> Result<(Proof<F, H::Digest>, Vec<C::Item>), Error<F>> {
632 let bounds = self.journal.bounds();
633
634 if *historical_leaves > bounds.end {
635 return Err(merkle::Error::RangeOutOfBounds(Location::new(bounds.end)).into());
636 }
637 if start_loc >= historical_leaves {
638 return Err(merkle::Error::RangeOutOfBounds(start_loc).into());
639 }
640
641 let end_loc = std::cmp::min(historical_leaves, start_loc.saturating_add(max_ops.get()));
642
643 let hasher = self.hasher.clone();
644 let proof = self
645 .merkle
646 .historical_range_proof(
647 &hasher,
648 historical_leaves,
649 start_loc..end_loc,
650 inactive_peaks,
651 )
652 .await?;
653
654 let positions: Vec<u64> = (*start_loc..*end_loc).collect();
655 let ops = self.journal.read_many(&positions).await?;
656
657 Ok((proof, ops))
658 }
659}
660
661impl<F, E, C, H, S> Journal<F, E, C, H, S>
662where
663 F: Family,
664 E: Context,
665 C: Mutable<Item: EncodeShared>,
666 H: Hasher,
667 S: Strategy,
668{
669 #[boxed]
671 pub async fn destroy(self) -> Result<(), Error<F>> {
672 let Self {
675 journal, merkle, ..
676 } = self;
677 try_join!(
678 journal.destroy().map_err(Error::Journal),
679 merkle.destroy().map_err(Error::Merkle),
680 )?;
681
682 Ok(())
683 }
684
685 pub async fn sync(&mut self) -> Result<(), Error<F>> {
687 try_join!(
688 self.journal.sync().map_err(Error::Journal),
689 self.merkle.sync().map_err(Error::Merkle)
690 )?;
691
692 Ok(())
693 }
694}
695
696const APPLY_BATCH_SIZE: u64 = 1 << 16;
698
699macro_rules! impl_journal_new {
702 ($journal_mod:ident, $cfg_ty:ty, $codec_bound:path) => {
703 impl<F, E, O, H, S> Journal<F, E, $journal_mod::Journal<E, O>, H, S>
704 where
705 F: Family,
706 E: Context,
707 O: $codec_bound,
708 H: Hasher,
709 S: Strategy,
710 {
711 #[boxed]
716 pub async fn new(
717 context: E,
718 merkle_cfg: merkle::full::Config<S>,
719 journal_cfg: $cfg_ty,
720 rewind_predicate: fn(&O) -> bool,
721 bagging: merkle::Bagging,
722 ) -> Result<Self, Error<F>> {
723 let mut journal =
724 $journal_mod::Journal::init(context.child("journal"), journal_cfg).await?;
725 journal.rewind_to(rewind_predicate).await?;
726
727 let hasher = StandardHasher::<H>::new(bagging);
728 let mut merkle = Merkle::init(context.child("merkle"), &hasher, merkle_cfg).await?;
729 Self::align(&mut merkle, &journal, &hasher, APPLY_BATCH_SIZE).await?;
730
731 journal.sync().await?;
732 merkle.sync().await?;
733
734 Ok(Self {
735 merkle,
736 journal,
737 hasher,
738 })
739 }
740 }
741 };
742}
743
744impl_journal_new!(fixed, fixed::Config, CodecFixedShared);
745impl_journal_new!(variable, variable::Config<O::Cfg>, CodecShared);
746
747impl<F, E, C, H, S> Journal<F, E, C, H, S>
748where
749 F: Family,
750 E: Context,
751 C: Contiguous<Item: EncodeShared>,
752 H: Hasher,
753 S: Strategy,
754{
755 pub(crate) async fn read_many_sharded(
763 &self,
764 positions: &[u64],
765 ) -> Result<Vec<Vec<C::Item>>, JournalError> {
766 if positions.is_empty() {
769 return Ok(Vec::new());
770 }
771
772 assert!(
779 positions.is_sorted_by(|a, b| a < b),
780 "positions must be strictly increasing"
781 );
782 let strategy = self.strategy();
783 let journal = &self.journal;
784
785 let probe = |positions: &[u64]| -> (Vec<C::Item>, Vec<usize>) {
787 let probed = journal.try_read_many_sync(positions);
788 let mut hits = Vec::with_capacity(probed.len());
789 let mut missed = Vec::new();
790 for (idx, item) in probed.into_iter().enumerate() {
791 match item {
792 Some(item) => hits.push(item),
793 None => missed.push(idx),
794 }
795 }
796 (hits, missed)
797 };
798 let shards: Vec<(Vec<C::Item>, Vec<usize>)> = strategy.run(
799 positions.len(),
800 || vec![probe(positions)],
801 || {
802 let manual = strategy.manual();
803 let shard_len = positions.len().div_ceil(manual.parallelism());
804 manual.map_collect_vec(positions.chunks(shard_len).collect::<Vec<_>>(), &probe)
805 },
806 );
807
808 let mut misses: Vec<u64> = Vec::new();
812 let mut offset = 0;
813 for (hits, missed) in &shards {
814 misses.extend(missed.iter().map(|idx| positions[offset + idx]));
815 offset += hits.len() + missed.len();
816 }
817 if misses.is_empty() {
818 return Ok(shards.into_iter().map(|(hits, _)| hits).collect());
819 }
820 let mut fetched = journal.read_many(&misses).await?.into_iter();
821
822 let mut result = Vec::with_capacity(shards.len());
824 for (hits, missed) in shards {
825 if missed.is_empty() {
826 result.push(hits);
827 continue;
828 }
829 let total = hits.len() + missed.len();
830 let mut woven = Vec::with_capacity(total);
831 let mut hits = hits.into_iter();
832 let mut missed = missed.into_iter().peekable();
833 for idx in 0..total {
834 if missed.next_if_eq(&idx).is_some() {
835 woven.push(fetched.next().expect("one fetched item per miss"));
836 } else {
837 woven.push(hits.next().expect("one probed item per hit"));
838 }
839 }
840 result.push(woven);
841 }
842 Ok(result)
843 }
844}
845
846impl<F, E, C, H, S> Contiguous for Journal<F, E, C, H, S>
847where
848 F: Family,
849 E: Context,
850 C: Contiguous<Item: EncodeShared>,
851 H: Hasher,
852 S: Strategy,
853{
854 type Item = C::Item;
855
856 fn bounds(&self) -> Range<u64> {
857 self.journal.bounds()
858 }
859
860 async fn read(&self, position: u64) -> Result<C::Item, JournalError> {
861 self.journal.read(position).await
862 }
863
864 async fn read_many(&self, positions: &[u64]) -> Result<Vec<C::Item>, JournalError> {
865 let mut shards = self.read_many_sharded(positions).await?;
866 if shards.len() == 1 {
867 return Ok(shards.pop().expect("length checked"));
868 }
869 let mut items = Vec::with_capacity(positions.len());
870 for shard in shards {
871 items.extend(shard);
872 }
873 Ok(items)
874 }
875
876 fn try_read_sync(&self, position: u64) -> Option<C::Item> {
877 self.journal.try_read_sync(position)
878 }
879
880 fn try_read_many_sync(&self, positions: &[u64]) -> Vec<Option<C::Item>> {
881 self.journal.try_read_many_sync(positions)
882 }
883
884 async fn replay(
885 &self,
886 start_pos: u64,
887 buffer: NonZeroUsize,
888 ) -> Result<impl Stream<Item = Result<(u64, C::Item), JournalError>> + Send, JournalError> {
889 self.journal.replay(start_pos, buffer).await
890 }
891}
892
893impl<F, E, C, H, S> Mutable for Journal<F, E, C, H, S>
894where
895 F: Family,
896 E: Context,
897 C: Mutable<Item: EncodeShared>,
898 H: Hasher,
899 S: Strategy,
900{
901 async fn append(&mut self, item: &Self::Item) -> Result<u64, JournalError> {
902 let res = self.append(item).await.map_err(Self::map_error)?;
903
904 Ok(*res)
905 }
906
907 async fn append_many<'a>(
908 &'a mut self,
909 items: Many<'a, Self::Item>,
910 ) -> Result<u64, JournalError> {
911 if items.is_empty() {
914 return Err(JournalError::EmptyAppend);
915 }
916
917 let mut last_pos = self.journal.bounds().end;
921 match items {
922 Many::Flat(items) => {
923 for item in items {
924 last_pos = Mutable::append(self, item).await?;
925 }
926 }
927 Many::Nested(nested_items) => {
928 for items in nested_items {
929 for item in *items {
930 last_pos = Mutable::append(self, item).await?;
931 }
932 }
933 }
934 }
935 Ok(last_pos)
936 }
937
938 async fn prune(&mut self, min_position: u64) -> Result<bool, JournalError> {
939 let prune_to = {
940 let bounds = self.journal.bounds();
941 min_position.min(bounds.end)
942 };
943
944 let (_, pruned) = self
945 .prune_inner(Location::new(prune_to))
946 .await
947 .map_err(Self::map_error)?;
948 Ok(pruned)
949 }
950
951 async fn rewind(&mut self, size: u64) -> Result<(), JournalError> {
952 self.rewind(size).await.map_err(Self::map_error)
953 }
954
955 async fn commit(&mut self) -> Result<(), JournalError> {
956 Self::commit(self).await.map_err(Self::map_error)
957 }
958
959 async fn sync(&mut self) -> Result<(), JournalError> {
960 Self::sync(self).await.map_err(Self::map_error)
961 }
962
963 async fn destroy(self) -> Result<(), JournalError> {
964 Self::destroy(self).await.map_err(Self::map_error)
965 }
966}
967
968pub trait Inner<E: Context>: Mutable {
970 type Config: Clone + Send;
972
973 fn init<F: Family, H: Hasher, S: Strategy>(
975 context: E,
976 merkle_cfg: merkle::full::Config<S>,
977 journal_cfg: Self::Config,
978 rewind_predicate: fn(&Self::Item) -> bool,
979 bagging: merkle::Bagging,
980 ) -> impl core::future::Future<Output = Result<Journal<F, E, Self, H, S>, Error<F>>> + Send
981 where
982 Self: Sized,
983 Self::Item: EncodeShared;
984}
985
986#[cfg(test)]
987mod tests {
988 use super::*;
989 use crate::{
990 journal::contiguous::fixed::{Config as JConfig, Journal as ContiguousJournal},
991 merkle::{
992 full::{Config as MerkleConfig, Merkle},
993 mmb, mmr,
994 Bagging::{BackwardFold, ForwardFold},
995 },
996 qmdb::{
997 any::{
998 operation::{update::Unordered as Update, Unordered as Op},
999 value::FixedEncoding,
1000 },
1001 operation::Committable,
1002 },
1003 };
1004 use commonware_codec::Encode;
1005 use commonware_cryptography::{sha256::Digest, Sha256};
1006 use commonware_macros::test_traced;
1007 use commonware_parallel::{Manual, Rayon, Sequential};
1008 use commonware_runtime::{
1009 buffer::paged::CacheRef,
1010 deterministic::{self, Context},
1011 BufferPooler, Runner as _, Strategizer as _, Supervisor as _,
1012 };
1013 use commonware_utils::{NZUsize, NZU16, NZU64};
1014 use futures::StreamExt as _;
1015 use std::num::{NonZeroU16, NonZeroUsize};
1016
1017 const PAGE_SIZE: NonZeroU16 = NZU16!(101);
1018 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(11);
1019
1020 type TestOp<F> = Op<F, Digest, FixedEncoding<Digest>>;
1022
1023 type TestJournal<F> = Journal<
1025 F,
1026 deterministic::Context,
1027 ContiguousJournal<deterministic::Context, TestOp<F>>,
1028 Sha256,
1029 Sequential,
1030 >;
1031
1032 fn journal_root<F: Family>(journal: &TestJournal<F>) -> Digest {
1033 journal.root(0).unwrap()
1034 }
1035
1036 fn batch_root<F: Family>(
1037 journal: &TestJournal<F>,
1038 batch: &MerkleizedBatch<F, Digest, TestOp<F>, Sequential>,
1039 ) -> Digest {
1040 journal
1041 .merkle
1042 .with_mem(|mem| batch.root(mem, &journal.hasher, 0))
1043 .unwrap()
1044 }
1045
1046 fn merkleize_with<F: Family + PartialEq>(
1047 batch: UnmerkleizedBatch<F, Sha256, TestOp<F>, Sequential>,
1048 base: &Mem<F, Digest>,
1049 items: Vec<TestOp<F>>,
1050 ) -> MerkleizedBatchArc<F, Sha256, TestOp<F>, Sequential> {
1051 batch.add_many(items).merkleize(base)
1052 }
1053
1054 fn merkle_config_with<S: Strategy>(
1056 suffix: &str,
1057 pooler: &impl BufferPooler,
1058 strategy: S,
1059 ) -> MerkleConfig<S> {
1060 MerkleConfig {
1061 journal_partition: format!("mmr-journal-{suffix}"),
1062 metadata_partition: format!("mmr-metadata-{suffix}"),
1063 items_per_blob: NZU64!(11),
1064 write_buffer: NZUsize!(1024),
1065 strategy,
1066 page_cache: CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE),
1067 }
1068 }
1069
1070 fn merkle_config(suffix: &str, pooler: &impl BufferPooler) -> MerkleConfig<Sequential> {
1072 merkle_config_with(suffix, pooler, Sequential)
1073 }
1074
1075 fn journal_config(suffix: &str, pooler: &impl BufferPooler) -> JConfig {
1077 JConfig {
1078 partition: format!("journal-{suffix}"),
1079 items_per_blob: NZU64!(7),
1080 write_buffer: NZUsize!(1024),
1081 page_cache: CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE),
1082 }
1083 }
1084
1085 async fn create_empty_journal<F: Family + PartialEq>(
1087 context: Context,
1088 suffix: &str,
1089 ) -> TestJournal<F> {
1090 let merkle_cfg = merkle_config(suffix, &context);
1091 let journal_cfg = journal_config(suffix, &context);
1092 TestJournal::<F>::new(
1093 context,
1094 merkle_cfg,
1095 journal_cfg,
1096 |op: &TestOp<F>| op.is_commit(),
1097 ForwardFold,
1098 )
1099 .await
1100 .unwrap()
1101 }
1102
1103 #[test]
1104 fn test_batches_inherit_journal_bagging() {
1105 deterministic::Runner::default().start(|context| async move {
1106 let merkle_cfg = merkle_config("batch-bagging", &context);
1107 let journal_cfg = journal_config("batch-bagging", &context);
1108 let journal = TestJournal::<mmr::Family>::new(
1109 context,
1110 merkle_cfg,
1111 journal_cfg,
1112 |op: &TestOp<mmr::Family>| op.is_commit(),
1113 BackwardFold,
1114 )
1115 .await
1116 .unwrap();
1117
1118 let batch = journal.new_batch();
1119 assert_eq!(batch.hasher.root_bagging(), BackwardFold);
1120
1121 let merkleized = journal.merkle.with_mem(|mem| batch.merkleize(mem));
1122 let child: UnmerkleizedBatch<mmr::Family, Sha256, TestOp<mmr::Family>, Sequential> =
1123 merkleized.new_batch();
1124 assert_eq!(child.hasher.root_bagging(), BackwardFold);
1125 });
1126 }
1127
1128 #[test]
1130 fn test_read_many_shards_across_strategy_pool() {
1131 deterministic::Runner::default().start(|context| async move {
1132 let strategy = context.strategy(NZUsize!(2));
1137 let merkle_cfg = merkle_config_with("shard", &context, strategy);
1138 let journal_cfg = journal_config("shard", &context);
1139 type RayonJournal = Journal<
1140 mmr::Family,
1141 Context,
1142 ContiguousJournal<Context, TestOp<mmr::Family>>,
1143 Sha256,
1144 Rayon,
1145 >;
1146 let mut journal = RayonJournal::new(
1147 context,
1148 merkle_cfg,
1149 journal_cfg,
1150 |op: &TestOp<mmr::Family>| op.is_commit(),
1151 ForwardFold,
1152 )
1153 .await
1154 .unwrap();
1155
1156 let count = 4200u64;
1157 for i in 0..count {
1158 let op = create_operation::<mmr::Family>((i % 251) as u8);
1159 journal.append(&op).await.unwrap();
1160 }
1161 journal.sync().await.unwrap();
1162
1163 let positions: Vec<u64> = (0..count).collect();
1164 let batch = Contiguous::read_many(&journal, &positions).await.unwrap();
1165 assert_eq!(batch.len(), positions.len());
1166 for &pos in &positions {
1167 let single = Contiguous::read(&journal, pos).await.unwrap();
1168 assert_eq!(batch[pos as usize], single);
1169 }
1170
1171 assert!(Contiguous::read_many(&journal, &[])
1173 .await
1174 .unwrap()
1175 .is_empty());
1176 });
1177 }
1178
1179 #[test]
1181 #[should_panic(expected = "positions must be strictly increasing")]
1182 fn test_read_many_rejects_unsorted_positions() {
1183 deterministic::Runner::default().start(|context| async move {
1184 let mut journal = create_empty_journal::<mmr::Family>(context, "unsorted").await;
1185 for i in 0..2u8 {
1186 let op = create_operation::<mmr::Family>(i);
1187 journal.append(&op).await.unwrap();
1188 }
1189 journal.sync().await.unwrap();
1190
1191 let _ = Contiguous::read_many(&journal, &[1, 0]).await;
1192 });
1193 }
1194
1195 fn create_operation<F: Family + PartialEq>(index: u8) -> TestOp<F> {
1197 TestOp::<F>::Update(Update(
1198 Sha256::fill(index),
1199 Sha256::fill(index.wrapping_add(1)),
1200 ))
1201 }
1202
1203 async fn create_journal_with_ops<F: Family + PartialEq>(
1207 context: Context,
1208 suffix: &str,
1209 count: usize,
1210 ) -> TestJournal<F> {
1211 let mut journal = create_empty_journal::<F>(context, suffix).await;
1212
1213 for i in 0..count {
1214 let op = create_operation::<F>(i as u8);
1215 let loc = journal.append(&op).await.unwrap();
1216 assert_eq!(loc, Location::<F>::new(i as u64));
1217 }
1218
1219 journal.sync().await.unwrap();
1220 journal
1221 }
1222
1223 async fn create_components<F: Family + PartialEq>(
1229 context: Context,
1230 suffix: &str,
1231 ) -> (
1232 Merkle<F, deterministic::Context, Digest, Sequential>,
1233 ContiguousJournal<deterministic::Context, TestOp<F>>,
1234 StandardHasher<Sha256>,
1235 ) {
1236 let hasher = StandardHasher::new(ForwardFold);
1237 let merkle = Merkle::<F, _, Digest, Sequential>::init(
1238 context.child("mmr"),
1239 &hasher,
1240 merkle_config(suffix, &context),
1241 )
1242 .await
1243 .unwrap();
1244 let journal =
1245 ContiguousJournal::init(context.child("journal"), journal_config(suffix, &context))
1246 .await
1247 .unwrap();
1248 (merkle, journal, hasher)
1249 }
1250
1251 fn verify_proof<F: Family + PartialEq>(
1254 proof: &Proof<F, <Sha256 as commonware_cryptography::Hasher>::Digest>,
1255 operations: &[TestOp<F>],
1256 start_loc: Location<F>,
1257 root: &<Sha256 as commonware_cryptography::Hasher>::Digest,
1258 hasher: &StandardHasher<Sha256>,
1259 ) -> bool {
1260 let encoded_ops: Vec<_> = operations.iter().map(|op| op.encode()).collect();
1261 proof.verify_range_inclusion(hasher, &encoded_ops, start_loc, root)
1262 }
1263
1264 async fn test_new_creates_empty_journal_inner<F: Family + PartialEq>(context: Context) {
1266 let journal = create_empty_journal::<F>(context, "new-empty").await;
1267
1268 let bounds = journal.bounds();
1269 assert_eq!(bounds.end, 0);
1270 assert_eq!(bounds.start, 0);
1271 assert!(bounds.is_empty());
1272 }
1273
1274 #[test_traced("INFO")]
1275 fn test_new_creates_empty_journal_mmr() {
1276 let executor = deterministic::Runner::default();
1277 executor.start(test_new_creates_empty_journal_inner::<mmr::Family>);
1278 }
1279
1280 #[test_traced("INFO")]
1281 fn test_new_creates_empty_journal_mmb() {
1282 let executor = deterministic::Runner::default();
1283 executor.start(test_new_creates_empty_journal_inner::<mmb::Family>);
1284 }
1285
1286 async fn test_align_with_empty_mmr_and_journal_inner<F: Family + PartialEq>(context: Context) {
1288 let (mut merkle, journal, hasher) = create_components::<F>(context, "align-empty").await;
1289
1290 TestJournal::<F>::align(&mut merkle, &journal, &hasher, APPLY_BATCH_SIZE)
1291 .await
1292 .unwrap();
1293
1294 assert_eq!(merkle.leaves(), Location::<F>::new(0));
1295 assert_eq!(journal.size(), 0);
1296 }
1297
1298 #[test_traced("INFO")]
1299 fn test_align_with_empty_mmr_and_journal_mmr() {
1300 let executor = deterministic::Runner::default();
1301 executor.start(test_align_with_empty_mmr_and_journal_inner::<mmr::Family>);
1302 }
1303
1304 #[test_traced("INFO")]
1305 fn test_align_with_empty_mmr_and_journal_mmb() {
1306 let executor = deterministic::Runner::default();
1307 executor.start(test_align_with_empty_mmr_and_journal_inner::<mmb::Family>);
1308 }
1309
1310 async fn test_align_when_mmr_ahead_inner<F: Family + PartialEq>(context: Context) {
1312 let (mut merkle, mut journal, hasher) = create_components::<F>(context, "mmr-ahead").await;
1313
1314 {
1316 let batch = {
1317 let mut batch = merkle.new_batch();
1318 for i in 0..20 {
1319 let op = create_operation::<F>(i as u8);
1320 let encoded = op.encode();
1321 batch = batch.add(&hasher, &encoded);
1322 journal.append(&op).await.unwrap();
1323 }
1324 batch
1325 };
1326 let batch = merkle.with_mem(|mem| batch.merkleize(mem, &hasher));
1327 merkle.apply_batch(&batch).unwrap();
1328 }
1329
1330 let commit_op = TestOp::<F>::CommitFloor(None, Location::<F>::new(0));
1332 journal.append(&commit_op).await.unwrap();
1333 journal.sync().await.unwrap();
1334
1335 TestJournal::<F>::align(&mut merkle, &journal, &hasher, APPLY_BATCH_SIZE)
1337 .await
1338 .unwrap();
1339
1340 assert_eq!(merkle.leaves(), Location::<F>::new(21));
1342 assert_eq!(journal.size(), 21);
1343 }
1344
1345 #[test_traced("WARN")]
1346 fn test_align_when_mmr_ahead_mmr() {
1347 let executor = deterministic::Runner::default();
1348 executor.start(test_align_when_mmr_ahead_inner::<mmr::Family>);
1349 }
1350
1351 #[test_traced("WARN")]
1352 fn test_align_when_mmr_ahead_mmb() {
1353 let executor = deterministic::Runner::default();
1354 executor.start(test_align_when_mmr_ahead_inner::<mmb::Family>);
1355 }
1356
1357 async fn test_align_when_journal_ahead_inner<F: Family + PartialEq>(context: Context) {
1359 let (mut merkle, mut journal, hasher) =
1360 create_components::<F>(context, "journal-ahead").await;
1361
1362 for i in 0..20 {
1364 let op = create_operation::<F>(i as u8);
1365 journal.append(&op).await.unwrap();
1366 }
1367
1368 let commit_op = TestOp::<F>::CommitFloor(None, Location::<F>::new(0));
1370 journal.append(&commit_op).await.unwrap();
1371 journal.sync().await.unwrap();
1372
1373 TestJournal::<F>::align(&mut merkle, &journal, &hasher, APPLY_BATCH_SIZE)
1375 .await
1376 .unwrap();
1377
1378 assert_eq!(merkle.leaves(), Location::<F>::new(21));
1380 assert_eq!(journal.size(), 21);
1381 }
1382
1383 #[test_traced("WARN")]
1384 fn test_align_when_journal_ahead_mmr() {
1385 let executor = deterministic::Runner::default();
1386 executor.start(test_align_when_journal_ahead_inner::<mmr::Family>);
1387 }
1388
1389 #[test_traced("WARN")]
1390 fn test_align_when_journal_ahead_mmb() {
1391 let executor = deterministic::Runner::default();
1392 executor.start(test_align_when_journal_ahead_inner::<mmb::Family>);
1393 }
1394
1395 async fn test_align_replay_parallel_matches_serial_inner<F: Family + PartialEq>(
1397 context: Context,
1398 ) {
1399 type ParallelJournal<F> = Journal<
1400 F,
1401 deterministic::Context,
1402 ContiguousJournal<deterministic::Context, TestOp<F>>,
1403 Sha256,
1404 Manual<Rayon>,
1405 >;
1406
1407 let mut journal = ContiguousJournal::init(
1409 context.child("journal"),
1410 journal_config("replay-strategies", &context),
1411 )
1412 .await
1413 .unwrap();
1414 for i in 0..20 {
1415 journal
1416 .append(&create_operation::<F>(i as u8))
1417 .await
1418 .unwrap();
1419 }
1420 let commit_op = TestOp::<F>::CommitFloor(None, Location::<F>::new(0));
1421 journal.append(&commit_op).await.unwrap();
1422 journal.sync().await.unwrap();
1423
1424 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1429 let mut serial = Merkle::<F, _, Digest, Sequential>::init(
1430 context.child("mmr_serial"),
1431 &hasher,
1432 merkle_config("replay-serial", &context),
1433 )
1434 .await
1435 .unwrap();
1436 TestJournal::<F>::align(&mut serial, &journal, &hasher, 7)
1437 .await
1438 .unwrap();
1439
1440 let mut parallel = Merkle::<F, _, Digest, Manual<Rayon>>::init(
1441 context.child("mmr_parallel"),
1442 &hasher,
1443 merkle_config_with(
1444 "replay-parallel",
1445 &context,
1446 Rayon::new(NZUsize!(2)).unwrap().manual(),
1447 ),
1448 )
1449 .await
1450 .unwrap();
1451 ParallelJournal::<F>::align(&mut parallel, &journal, &hasher, 7)
1452 .await
1453 .unwrap();
1454
1455 assert_eq!(serial.leaves(), Location::<F>::new(21));
1456 assert_eq!(parallel.leaves(), Location::<F>::new(21));
1457 assert_eq!(
1458 serial.root(&hasher, 0).unwrap(),
1459 parallel.root(&hasher, 0).unwrap()
1460 );
1461 }
1462
1463 #[test_traced("WARN")]
1464 fn test_align_replay_parallel_matches_serial_mmr() {
1465 let executor = deterministic::Runner::default();
1466 executor.start(test_align_replay_parallel_matches_serial_inner::<mmr::Family>);
1467 }
1468
1469 #[test_traced("WARN")]
1470 fn test_align_replay_parallel_matches_serial_mmb() {
1471 let executor = deterministic::Runner::default();
1472 executor.start(test_align_replay_parallel_matches_serial_inner::<mmb::Family>);
1473 }
1474
1475 async fn test_align_with_mismatched_committed_ops_inner<F: Family + PartialEq>(
1477 context: Context,
1478 ) {
1479 let mut journal = create_empty_journal::<F>(context.child("first"), "mismatched").await;
1480
1481 for i in 0..20 {
1483 let loc = journal
1484 .append(&create_operation::<F>(i as u8))
1485 .await
1486 .unwrap();
1487 assert_eq!(loc, Location::<F>::new(i as u64));
1488 }
1489
1490 let size_before = journal.size();
1493 assert_eq!(size_before, 20);
1494
1495 journal.sync().await.unwrap();
1497 drop(journal);
1498 let journal = create_empty_journal::<F>(context.child("second"), "mismatched").await;
1499
1500 assert_eq!(journal.size(), 0);
1502 }
1503
1504 #[test_traced("INFO")]
1505 fn test_align_with_mismatched_committed_ops_mmr() {
1506 let executor = deterministic::Runner::default();
1507 executor.start(|context| {
1508 test_align_with_mismatched_committed_ops_inner::<mmr::Family>(context)
1509 });
1510 }
1511
1512 #[test_traced("INFO")]
1513 fn test_align_with_mismatched_committed_ops_mmb() {
1514 let executor = deterministic::Runner::default();
1515 executor.start(|context| {
1516 test_align_with_mismatched_committed_ops_inner::<mmb::Family>(context)
1517 });
1518 }
1519
1520 async fn test_rewind_inner<F: Family + PartialEq>(context: Context) {
1521 {
1523 let mut journal = ContiguousJournal::init(
1524 context.child("rewind_match"),
1525 journal_config("rewind-match", &context),
1526 )
1527 .await
1528 .unwrap();
1529
1530 for i in 0..3 {
1532 journal.append(&create_operation::<F>(i)).await.unwrap();
1533 }
1534 journal
1535 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(0)))
1536 .await
1537 .unwrap();
1538 for i in 4..7 {
1539 journal.append(&create_operation::<F>(i)).await.unwrap();
1540 }
1541
1542 let final_size = journal.rewind_to(|op| op.is_commit()).await.unwrap();
1544 assert_eq!(final_size, 4);
1545 assert_eq!(journal.size(), 4);
1546
1547 let op = journal.read(3).await.unwrap();
1549 assert!(op.is_commit());
1550 }
1551
1552 {
1554 let mut journal = ContiguousJournal::init(
1555 context.child("rewind_multiple"),
1556 journal_config("rewind-multiple", &context),
1557 )
1558 .await
1559 .unwrap();
1560
1561 journal.append(&create_operation::<F>(0)).await.unwrap();
1563 journal
1564 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(0)))
1565 .await
1566 .unwrap(); journal.append(&create_operation::<F>(2)).await.unwrap();
1568 journal
1569 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(1)))
1570 .await
1571 .unwrap(); journal.append(&create_operation::<F>(4)).await.unwrap();
1573
1574 let final_size = journal.rewind_to(|op| op.is_commit()).await.unwrap();
1576 assert_eq!(final_size, 4);
1577
1578 let op = journal.read(3).await.unwrap();
1580 assert!(op.is_commit());
1581
1582 assert!(journal.read(4).await.is_err());
1584 }
1585
1586 {
1588 let mut journal = ContiguousJournal::init(
1589 context.child("rewind_no_match"),
1590 journal_config("rewind-no-match", &context),
1591 )
1592 .await
1593 .unwrap();
1594
1595 for i in 0..10 {
1597 journal.append(&create_operation::<F>(i)).await.unwrap();
1598 }
1599
1600 let final_size = journal.rewind_to(|op| op.is_commit()).await.unwrap();
1602 assert_eq!(final_size, 0, "Should rewind to pruning boundary (0)");
1603 assert_eq!(journal.size(), 0);
1604 }
1605
1606 {
1608 let mut journal = ContiguousJournal::init(
1609 context.child("rewind_with_pruning"),
1610 journal_config("rewind-with-pruning", &context),
1611 )
1612 .await
1613 .unwrap();
1614
1615 for i in 0..10 {
1617 journal.append(&create_operation::<F>(i)).await.unwrap();
1618 }
1619 journal
1620 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(0)))
1621 .await
1622 .unwrap(); for i in 11..15 {
1624 journal.append(&create_operation::<F>(i)).await.unwrap();
1625 }
1626 journal.sync().await.unwrap();
1627
1628 journal.prune(8).await.unwrap();
1630 assert_eq!(journal.bounds().start, 7);
1631
1632 for i in 15..20 {
1634 journal.append(&create_operation::<F>(i)).await.unwrap();
1635 }
1636
1637 let final_size = journal.rewind_to(|op| op.is_commit()).await.unwrap();
1639 assert_eq!(final_size, 11);
1640
1641 let op = journal.read(10).await.unwrap();
1643 assert!(op.is_commit());
1644 }
1645
1646 {
1648 let mut journal = ContiguousJournal::init(
1649 context.child("rewind_no_match_pruned"),
1650 journal_config("rewind-no-match-pruned", &context),
1651 )
1652 .await
1653 .unwrap();
1654
1655 for i in 0..5 {
1657 journal.append(&create_operation::<F>(i)).await.unwrap();
1658 }
1659 journal
1660 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(0)))
1661 .await
1662 .unwrap(); for i in 6..10 {
1664 journal.append(&create_operation::<F>(i)).await.unwrap();
1665 }
1666 journal.sync().await.unwrap();
1667
1668 journal.prune(8).await.unwrap();
1671 assert_eq!(journal.bounds().start, 7);
1672
1673 for i in 10..14 {
1675 journal.append(&create_operation::<F>(i)).await.unwrap();
1676 }
1677
1678 let final_size = journal.rewind_to(|op| op.is_commit()).await.unwrap();
1681 assert_eq!(final_size, 7);
1682 }
1683
1684 {
1686 let mut journal = ContiguousJournal::init(
1687 context.child("rewind_empty"),
1688 journal_config("rewind-empty", &context),
1689 )
1690 .await
1691 .unwrap();
1692
1693 let final_size = journal
1695 .rewind_to(|op: &TestOp<F>| op.is_commit())
1696 .await
1697 .unwrap();
1698 assert_eq!(final_size, 0);
1699 assert_eq!(journal.size(), 0);
1700 }
1701
1702 {
1704 let merkle_cfg = merkle_config("rewind", &context);
1705 let journal_cfg = journal_config("rewind", &context);
1706 let mut journal = TestJournal::<F>::new(
1707 context,
1708 merkle_cfg,
1709 journal_cfg,
1710 |op| op.is_commit(),
1711 ForwardFold,
1712 )
1713 .await
1714 .unwrap();
1715
1716 for i in 0..5 {
1718 journal.append(&create_operation::<F>(i)).await.unwrap();
1719 }
1720 journal
1721 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(0)))
1722 .await
1723 .unwrap(); for i in 6..10 {
1725 journal.append(&create_operation::<F>(i)).await.unwrap();
1726 }
1727 assert_eq!(journal.size(), 10);
1728
1729 journal.rewind(2).await.unwrap();
1730 assert_eq!(journal.size(), 2);
1731 assert_eq!(journal.merkle.leaves(), 2);
1732 assert_eq!(journal.merkle.size(), 3);
1733 let bounds = journal.bounds();
1734 assert_eq!(bounds.start, 0);
1735 assert!(!bounds.is_empty());
1736
1737 assert!(matches!(
1738 journal.rewind(3).await,
1739 Err(Error::Journal(JournalError::InvalidRewind(_)))
1740 ));
1741
1742 journal.rewind(0).await.unwrap();
1743 assert_eq!(journal.size(), 0);
1744 assert_eq!(journal.merkle.leaves(), 0);
1745 assert_eq!(journal.merkle.size(), 0);
1746 let bounds = journal.bounds();
1747 assert_eq!(bounds.start, 0);
1748 assert!(bounds.is_empty());
1749
1750 for i in 0..255 {
1752 journal.append(&create_operation::<F>(i)).await.unwrap();
1753 }
1754 journal.prune(Location::<F>::new(100)).await.unwrap();
1755 assert_eq!(journal.bounds().start, 98);
1756 let res = journal.rewind(97).await;
1757 assert!(matches!(
1758 res,
1759 Err(Error::Journal(JournalError::ItemPruned(97)))
1760 ));
1761 journal.rewind(98).await.unwrap();
1762 let bounds = journal.bounds();
1763 assert_eq!(bounds.end, 98);
1764 assert_eq!(journal.merkle.leaves(), 98);
1765 assert_eq!(bounds.start, 98);
1766 assert!(bounds.is_empty());
1767 }
1768 }
1769
1770 #[test_traced("INFO")]
1771 fn test_rewind_mmr() {
1772 let executor = deterministic::Runner::default();
1773 executor.start(test_rewind_inner::<mmr::Family>);
1774 }
1775
1776 #[test_traced("INFO")]
1777 fn test_rewind_mmb() {
1778 let executor = deterministic::Runner::default();
1779 executor.start(test_rewind_inner::<mmb::Family>);
1780 }
1781
1782 async fn test_apply_op_and_read_operations_inner<F: Family + PartialEq>(context: Context) {
1785 let mut journal = create_empty_journal::<F>(context, "apply_op").await;
1786
1787 assert_eq!(journal.size(), 0);
1788
1789 let expected_ops: Vec<_> = (0..50).map(|i| create_operation::<F>(i as u8)).collect();
1791 for (i, op) in expected_ops.iter().enumerate() {
1792 let loc = journal.append(op).await.unwrap();
1793 assert_eq!(loc, Location::<F>::new(i as u64));
1794 assert_eq!(journal.size(), (i + 1) as u64);
1795 }
1796
1797 assert_eq!(journal.size(), 50);
1798
1799 journal.sync().await.unwrap();
1801 for (i, expected_op) in expected_ops.iter().enumerate() {
1802 let read_op = journal.read(*Location::<F>::new(i as u64)).await.unwrap();
1803 assert_eq!(read_op, *expected_op);
1804 }
1805 }
1806
1807 #[test_traced("INFO")]
1808 fn test_apply_op_and_read_operations_mmr() {
1809 let executor = deterministic::Runner::default();
1810 executor.start(test_apply_op_and_read_operations_inner::<mmr::Family>);
1811 }
1812
1813 #[test_traced("INFO")]
1814 fn test_apply_op_and_read_operations_mmb() {
1815 let executor = deterministic::Runner::default();
1816 executor.start(test_apply_op_and_read_operations_inner::<mmb::Family>);
1817 }
1818
1819 async fn test_read_operations_at_various_positions_inner<F: Family + PartialEq>(
1821 context: Context,
1822 ) {
1823 let journal = create_journal_with_ops::<F>(context, "read", 50).await;
1824
1825 let first_op = journal.read(*Location::<F>::new(0)).await.unwrap();
1827 assert_eq!(first_op, create_operation::<F>(0));
1828
1829 let middle_op = journal.read(*Location::<F>::new(25)).await.unwrap();
1831 assert_eq!(middle_op, create_operation::<F>(25));
1832
1833 let last_op = journal.read(*Location::<F>::new(49)).await.unwrap();
1835 assert_eq!(last_op, create_operation::<F>(49));
1836
1837 for i in 0..50 {
1839 let op = journal.read(*Location::<F>::new(i)).await.unwrap();
1840 assert_eq!(op, create_operation::<F>(i as u8));
1841 }
1842 }
1843
1844 #[test_traced("INFO")]
1845 fn test_read_operations_at_various_positions_mmr() {
1846 let executor = deterministic::Runner::default();
1847 executor.start(|context| {
1848 test_read_operations_at_various_positions_inner::<mmr::Family>(context)
1849 });
1850 }
1851
1852 #[test_traced("INFO")]
1853 fn test_read_operations_at_various_positions_mmb() {
1854 let executor = deterministic::Runner::default();
1855 executor.start(|context| {
1856 test_read_operations_at_various_positions_inner::<mmb::Family>(context)
1857 });
1858 }
1859
1860 async fn test_read_pruned_operation_returns_error_inner<F: Family + PartialEq>(
1862 context: Context,
1863 ) {
1864 let mut journal = create_journal_with_ops::<F>(context, "read_pruned", 100).await;
1865
1866 journal
1868 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
1869 .await
1870 .unwrap();
1871 journal.sync().await.unwrap();
1872 let pruned_boundary = journal.prune(Location::<F>::new(50)).await.unwrap();
1873
1874 let read_loc = Location::<F>::new(0);
1876 if read_loc < pruned_boundary {
1877 let result = journal.read(*read_loc).await;
1878 assert!(matches!(result, Err(crate::journal::Error::ItemPruned(_))));
1879 }
1880 }
1881
1882 #[test_traced("INFO")]
1883 fn test_read_pruned_operation_returns_error_mmr() {
1884 let executor = deterministic::Runner::default();
1885 executor.start(|context| {
1886 test_read_pruned_operation_returns_error_inner::<mmr::Family>(context)
1887 });
1888 }
1889
1890 #[test_traced("INFO")]
1891 fn test_read_pruned_operation_returns_error_mmb() {
1892 let executor = deterministic::Runner::default();
1893 executor.start(|context| {
1894 test_read_pruned_operation_returns_error_inner::<mmb::Family>(context)
1895 });
1896 }
1897
1898 async fn test_read_out_of_range_returns_error_inner<F: Family + PartialEq>(context: Context) {
1900 let journal = create_journal_with_ops::<F>(context, "read_oob", 3).await;
1901
1902 let result = journal.read(*Location::<F>::new(10)).await;
1904 assert!(matches!(
1905 result,
1906 Err(crate::journal::Error::ItemOutOfRange(_))
1907 ));
1908 }
1909
1910 #[test_traced("INFO")]
1911 fn test_read_out_of_range_returns_error_mmr() {
1912 let executor = deterministic::Runner::default();
1913 executor.start(test_read_out_of_range_returns_error_inner::<mmr::Family>);
1914 }
1915
1916 #[test_traced("INFO")]
1917 fn test_read_out_of_range_returns_error_mmb() {
1918 let executor = deterministic::Runner::default();
1919 executor.start(test_read_out_of_range_returns_error_inner::<mmb::Family>);
1920 }
1921
1922 async fn test_read_all_operations_back_correctly_inner<F: Family + PartialEq>(
1924 context: Context,
1925 ) {
1926 let journal = create_journal_with_ops::<F>(context, "read_all", 50).await;
1927
1928 assert_eq!(journal.size(), 50);
1929
1930 for i in 0..50 {
1932 let op = journal.read(*Location::<F>::new(i)).await.unwrap();
1933 assert_eq!(op, create_operation::<F>(i as u8));
1934 }
1935 }
1936
1937 #[test_traced("INFO")]
1938 fn test_read_all_operations_back_correctly_mmr() {
1939 let executor = deterministic::Runner::default();
1940 executor.start(test_read_all_operations_back_correctly_inner::<mmr::Family>);
1941 }
1942
1943 #[test_traced("INFO")]
1944 fn test_read_all_operations_back_correctly_mmb() {
1945 let executor = deterministic::Runner::default();
1946 executor.start(test_read_all_operations_back_correctly_inner::<mmb::Family>);
1947 }
1948
1949 async fn test_sync_inner<F: Family + PartialEq>(context: Context) {
1951 let mut journal = create_empty_journal::<F>(context.child("first"), "close_pending").await;
1952
1953 let expected_ops: Vec<_> = (0..20).map(|i| create_operation::<F>(i as u8)).collect();
1955 for (i, op) in expected_ops.iter().enumerate() {
1956 let loc = journal.append(op).await.unwrap();
1957 assert_eq!(loc, Location::<F>::new(i as u64),);
1958 }
1959
1960 let commit_loc = journal
1962 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(0)))
1963 .await
1964 .unwrap();
1965 assert_eq!(
1966 commit_loc,
1967 Location::<F>::new(20),
1968 "commit should be at location 20"
1969 );
1970 journal.sync().await.unwrap();
1971
1972 drop(journal);
1974 let journal = create_empty_journal::<F>(context.child("second"), "close_pending").await;
1975 assert_eq!(journal.size(), 21);
1976
1977 for (i, expected_op) in expected_ops.iter().enumerate() {
1979 let read_op = journal.read(*Location::<F>::new(i as u64)).await.unwrap();
1980 assert_eq!(read_op, *expected_op);
1981 }
1982 }
1983
1984 #[test_traced("INFO")]
1985 fn test_sync_mmr() {
1986 let executor = deterministic::Runner::default();
1987 executor.start(test_sync_inner::<mmr::Family>);
1988 }
1989
1990 #[test_traced("INFO")]
1991 fn test_sync_mmb() {
1992 let executor = deterministic::Runner::default();
1993 executor.start(test_sync_inner::<mmb::Family>);
1994 }
1995
1996 async fn test_prune_empty_journal_inner<F: Family + PartialEq>(context: Context) {
1998 let mut journal = create_empty_journal::<F>(context, "prune_empty").await;
1999
2000 let boundary = journal.prune(Location::<F>::new(0)).await.unwrap();
2001
2002 assert_eq!(boundary, Location::<F>::new(0));
2003 }
2004
2005 #[test_traced("INFO")]
2006 fn test_prune_empty_journal_mmr() {
2007 let executor = deterministic::Runner::default();
2008 executor.start(test_prune_empty_journal_inner::<mmr::Family>);
2009 }
2010
2011 #[test_traced("INFO")]
2012 fn test_prune_empty_journal_mmb() {
2013 let executor = deterministic::Runner::default();
2014 executor.start(test_prune_empty_journal_inner::<mmb::Family>);
2015 }
2016
2017 async fn test_prune_to_location_inner<F: Family + PartialEq>(context: Context) {
2019 let mut journal = create_journal_with_ops::<F>(context, "prune_to", 100).await;
2020
2021 journal
2023 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2024 .await
2025 .unwrap();
2026 journal.sync().await.unwrap();
2027
2028 let boundary = journal.prune(Location::<F>::new(50)).await.unwrap();
2029
2030 assert!(boundary <= Location::<F>::new(50));
2032 }
2033
2034 #[test_traced("INFO")]
2035 fn test_prune_to_location_mmr() {
2036 let executor = deterministic::Runner::default();
2037 executor.start(test_prune_to_location_inner::<mmr::Family>);
2038 }
2039
2040 #[test_traced("INFO")]
2041 fn test_prune_to_location_mmb() {
2042 let executor = deterministic::Runner::default();
2043 executor.start(test_prune_to_location_inner::<mmb::Family>);
2044 }
2045
2046 async fn test_prune_returns_actual_boundary_inner<F: Family + PartialEq>(context: Context) {
2048 let mut journal = create_journal_with_ops::<F>(context, "prune_boundary", 100).await;
2049
2050 journal
2051 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2052 .await
2053 .unwrap();
2054 journal.sync().await.unwrap();
2055
2056 let requested = Location::<F>::new(50);
2057 let actual = journal.prune(requested).await.unwrap();
2058
2059 let bounds = journal.bounds();
2061 assert!(!bounds.is_empty());
2062 assert_eq!(actual, bounds.start);
2063
2064 assert!(actual <= requested);
2066 }
2067
2068 #[test_traced("INFO")]
2069 fn test_prune_returns_actual_boundary_mmr() {
2070 let executor = deterministic::Runner::default();
2071 executor.start(test_prune_returns_actual_boundary_inner::<mmr::Family>);
2072 }
2073
2074 #[test_traced("INFO")]
2075 fn test_prune_returns_actual_boundary_mmb() {
2076 let executor = deterministic::Runner::default();
2077 executor.start(test_prune_returns_actual_boundary_inner::<mmb::Family>);
2078 }
2079
2080 async fn test_mutable_prune_updates_merkle_boundary_inner<F: Family + PartialEq>(
2082 context: Context,
2083 ) {
2084 let mut journal = create_journal_with_ops::<F>(context, "trait_prune", 100).await;
2085
2086 journal
2087 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2088 .await
2089 .unwrap();
2090 journal.sync().await.unwrap();
2091
2092 let pruned = <TestJournal<F> as Mutable>::prune(&mut journal, 50)
2093 .await
2094 .unwrap();
2095 assert!(pruned);
2096
2097 let item_boundary = journal.bounds().start;
2098 let merkle_boundary = journal.merkle.bounds().start;
2099 assert_eq!(Location::<F>::new(item_boundary), merkle_boundary);
2100 assert!(merkle_boundary > Location::<F>::new(0));
2101
2102 let pruned = <TestJournal<F> as Mutable>::prune(&mut journal, 50)
2103 .await
2104 .unwrap();
2105 assert!(!pruned);
2106 assert_eq!(journal.bounds().start, item_boundary);
2107 assert_eq!(journal.merkle.bounds().start, merkle_boundary);
2108 }
2109
2110 #[test_traced("INFO")]
2111 fn test_mutable_prune_updates_merkle_boundary_mmr() {
2112 let executor = deterministic::Runner::default();
2113 executor.start(test_mutable_prune_updates_merkle_boundary_inner::<mmr::Family>);
2114 }
2115
2116 #[test_traced("INFO")]
2117 fn test_mutable_prune_updates_merkle_boundary_mmb() {
2118 let executor = deterministic::Runner::default();
2119 executor.start(test_mutable_prune_updates_merkle_boundary_inner::<mmb::Family>);
2120 }
2121
2122 async fn test_prune_preserves_operation_count_inner<F: Family + PartialEq>(context: Context) {
2124 let mut journal = create_journal_with_ops::<F>(context, "prune_count", 100).await;
2125
2126 journal
2127 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2128 .await
2129 .unwrap();
2130 journal.sync().await.unwrap();
2131
2132 let count_before = journal.size();
2133 journal.prune(Location::<F>::new(50)).await.unwrap();
2134 let count_after = journal.size();
2135
2136 assert_eq!(count_before, count_after);
2137 }
2138
2139 #[test_traced("INFO")]
2140 fn test_prune_preserves_operation_count_mmr() {
2141 let executor = deterministic::Runner::default();
2142 executor.start(test_prune_preserves_operation_count_inner::<mmr::Family>);
2143 }
2144
2145 #[test_traced("INFO")]
2146 fn test_prune_preserves_operation_count_mmb() {
2147 let executor = deterministic::Runner::default();
2148 executor.start(test_prune_preserves_operation_count_inner::<mmb::Family>);
2149 }
2150
2151 async fn test_bounds_empty_and_pruned_inner<F: Family + PartialEq>(context: Context) {
2153 let journal = create_empty_journal::<F>(context.child("empty"), "oldest").await;
2155 assert!(journal.bounds().is_empty());
2156 journal.destroy().await.unwrap();
2157
2158 let journal = create_journal_with_ops::<F>(context.child("no_prune"), "oldest", 100).await;
2160 let bounds = journal.bounds();
2161 assert!(!bounds.is_empty());
2162 assert_eq!(bounds.start, 0);
2163 journal.destroy().await.unwrap();
2164
2165 let mut journal =
2167 create_journal_with_ops::<F>(context.child("pruned"), "oldest", 100).await;
2168 journal
2169 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2170 .await
2171 .unwrap();
2172 journal.sync().await.unwrap();
2173
2174 let pruned_boundary = journal.prune(Location::<F>::new(50)).await.unwrap();
2175
2176 let bounds = journal.bounds();
2178 assert!(!bounds.is_empty());
2179 assert_eq!(bounds.start, pruned_boundary);
2180 assert!(pruned_boundary <= 50);
2182 journal.destroy().await.unwrap();
2183 }
2184
2185 #[test_traced("INFO")]
2186 fn test_bounds_empty_and_pruned_mmr() {
2187 let executor = deterministic::Runner::default();
2188 executor.start(test_bounds_empty_and_pruned_inner::<mmr::Family>);
2189 }
2190
2191 #[test_traced("INFO")]
2192 fn test_bounds_empty_and_pruned_mmb() {
2193 let executor = deterministic::Runner::default();
2194 executor.start(test_bounds_empty_and_pruned_inner::<mmb::Family>);
2195 }
2196
2197 async fn test_bounds_start_after_prune_inner<F: Family + PartialEq>(context: Context) {
2199 let journal = create_empty_journal::<F>(context.child("empty"), "boundary").await;
2201 assert_eq!(journal.bounds().start, 0);
2202
2203 let journal =
2205 create_journal_with_ops::<F>(context.child("no_prune"), "boundary", 100).await;
2206 assert_eq!(journal.bounds().start, 0);
2207
2208 let mut journal =
2210 create_journal_with_ops::<F>(context.child("pruned"), "boundary", 100).await;
2211 journal
2212 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2213 .await
2214 .unwrap();
2215 journal.sync().await.unwrap();
2216
2217 let pruned_boundary = journal.prune(Location::<F>::new(50)).await.unwrap();
2218
2219 assert_eq!(journal.bounds().start, pruned_boundary);
2220 }
2221
2222 #[test_traced("INFO")]
2223 fn test_bounds_start_after_prune_mmr() {
2224 let executor = deterministic::Runner::default();
2225 executor.start(test_bounds_start_after_prune_inner::<mmr::Family>);
2226 }
2227
2228 #[test_traced("INFO")]
2229 fn test_bounds_start_after_prune_mmb() {
2230 let executor = deterministic::Runner::default();
2231 executor.start(test_bounds_start_after_prune_inner::<mmb::Family>);
2232 }
2233
2234 async fn test_mmr_prunes_to_journal_boundary_inner<F: Family + PartialEq>(context: Context) {
2236 let mut journal = create_journal_with_ops::<F>(context, "mmr_boundary", 50).await;
2237
2238 journal
2239 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(25)))
2240 .await
2241 .unwrap();
2242 journal.sync().await.unwrap();
2243
2244 let pruned_boundary = journal.prune(Location::<F>::new(25)).await.unwrap();
2245
2246 let bounds = journal.bounds();
2248 assert!(!bounds.is_empty());
2249 assert_eq!(pruned_boundary, bounds.start);
2250
2251 assert!(pruned_boundary <= Location::<F>::new(25));
2253
2254 assert_eq!(journal.size(), 51);
2256 }
2257
2258 #[test_traced("INFO")]
2259 fn test_mmr_prunes_to_journal_boundary_mmr() {
2260 let executor = deterministic::Runner::default();
2261 executor.start(test_mmr_prunes_to_journal_boundary_inner::<mmr::Family>);
2262 }
2263
2264 #[test_traced("INFO")]
2265 fn test_mmr_prunes_to_journal_boundary_mmb() {
2266 let executor = deterministic::Runner::default();
2267 executor.start(test_mmr_prunes_to_journal_boundary_inner::<mmb::Family>);
2268 }
2269
2270 async fn test_proof_multiple_operations_inner<F: Family + PartialEq>(context: Context) {
2272 let journal = create_journal_with_ops::<F>(context, "proof_multi", 50).await;
2273
2274 let (proof, ops) = journal
2275 .proof(Location::<F>::new(0), NZU64!(50), 0)
2276 .await
2277 .unwrap();
2278
2279 assert_eq!(ops.len(), 50);
2280 for (i, op) in ops.iter().enumerate() {
2281 assert_eq!(*op, create_operation::<F>(i as u8));
2282 }
2283
2284 let hasher = StandardHasher::new(ForwardFold);
2286 let root = journal_root(&journal);
2287 assert!(verify_proof(
2288 &proof,
2289 &ops,
2290 Location::<F>::new(0),
2291 &root,
2292 &hasher
2293 ));
2294 }
2295
2296 #[test_traced("INFO")]
2297 fn test_proof_multiple_operations_mmr() {
2298 let executor = deterministic::Runner::default();
2299 executor.start(test_proof_multiple_operations_inner::<mmr::Family>);
2300 }
2301
2302 #[test_traced("INFO")]
2303 fn test_proof_multiple_operations_mmb() {
2304 let executor = deterministic::Runner::default();
2305 executor.start(test_proof_multiple_operations_inner::<mmb::Family>);
2306 }
2307
2308 async fn test_historical_proof_limited_by_max_ops_inner<F: Family + PartialEq>(
2310 context: Context,
2311 ) {
2312 let journal = create_journal_with_ops::<F>(context, "proof_limit", 50).await;
2313
2314 let size = journal.size();
2315 let (proof, ops) = journal
2316 .historical_proof(size, Location::<F>::new(0), NZU64!(20), 0)
2317 .await
2318 .unwrap();
2319
2320 assert_eq!(ops.len(), 20);
2322 for (i, op) in ops.iter().enumerate() {
2323 assert_eq!(*op, create_operation::<F>(i as u8));
2324 }
2325
2326 let hasher = StandardHasher::new(ForwardFold);
2328 let root = journal_root(&journal);
2329 assert!(verify_proof(
2330 &proof,
2331 &ops,
2332 Location::<F>::new(0),
2333 &root,
2334 &hasher
2335 ));
2336 }
2337
2338 #[test_traced("INFO")]
2339 fn test_historical_proof_limited_by_max_ops_mmr() {
2340 let executor = deterministic::Runner::default();
2341 executor.start(|context| {
2342 test_historical_proof_limited_by_max_ops_inner::<mmr::Family>(context)
2343 });
2344 }
2345
2346 #[test_traced("INFO")]
2347 fn test_historical_proof_limited_by_max_ops_mmb() {
2348 let executor = deterministic::Runner::default();
2349 executor.start(|context| {
2350 test_historical_proof_limited_by_max_ops_inner::<mmb::Family>(context)
2351 });
2352 }
2353
2354 async fn test_historical_proof_at_end_of_journal_inner<F: Family + PartialEq>(
2356 context: Context,
2357 ) {
2358 let journal = create_journal_with_ops::<F>(context, "proof_end", 50).await;
2359
2360 let size = journal.size();
2361 let (proof, ops) = journal
2363 .historical_proof(size, Location::<F>::new(40), NZU64!(20), 0)
2364 .await
2365 .unwrap();
2366
2367 assert_eq!(ops.len(), 10);
2369 for (i, op) in ops.iter().enumerate() {
2370 assert_eq!(*op, create_operation::<F>((40 + i) as u8));
2371 }
2372
2373 let hasher = StandardHasher::new(ForwardFold);
2375 let root = journal_root(&journal);
2376 assert!(verify_proof(
2377 &proof,
2378 &ops,
2379 Location::<F>::new(40),
2380 &root,
2381 &hasher
2382 ));
2383 }
2384
2385 #[test_traced("INFO")]
2386 fn test_historical_proof_at_end_of_journal_mmr() {
2387 let executor = deterministic::Runner::default();
2388 executor.start(test_historical_proof_at_end_of_journal_inner::<mmr::Family>);
2389 }
2390
2391 #[test_traced("INFO")]
2392 fn test_historical_proof_at_end_of_journal_mmb() {
2393 let executor = deterministic::Runner::default();
2394 executor.start(test_historical_proof_at_end_of_journal_inner::<mmb::Family>);
2395 }
2396
2397 async fn test_historical_proof_out_of_range_returns_error_inner<F: Family + PartialEq>(
2399 context: Context,
2400 ) {
2401 let journal = create_journal_with_ops::<F>(context, "proof_oob", 5).await;
2402
2403 let result = journal
2405 .historical_proof(Location::<F>::new(10), Location::<F>::new(0), NZU64!(1), 0)
2406 .await;
2407
2408 assert!(matches!(
2409 result,
2410 Err(Error::Merkle(merkle::Error::RangeOutOfBounds(_)))
2411 ));
2412 }
2413
2414 #[test_traced("INFO")]
2415 fn test_historical_proof_out_of_range_returns_error_mmr() {
2416 let executor = deterministic::Runner::default();
2417 executor.start(|context| {
2418 test_historical_proof_out_of_range_returns_error_inner::<mmr::Family>(context)
2419 });
2420 }
2421
2422 #[test_traced("INFO")]
2423 fn test_historical_proof_out_of_range_returns_error_mmb() {
2424 let executor = deterministic::Runner::default();
2425 executor.start(|context| {
2426 test_historical_proof_out_of_range_returns_error_inner::<mmb::Family>(context)
2427 });
2428 }
2429
2430 async fn test_historical_proof_start_too_large_returns_error_inner<F: Family + PartialEq>(
2432 context: Context,
2433 ) {
2434 let journal = create_journal_with_ops::<F>(context, "proof_start_oob", 5).await;
2435
2436 let size = journal.size();
2437 let result = journal.historical_proof(size, size, NZU64!(1), 0).await;
2439
2440 assert!(matches!(
2441 result,
2442 Err(Error::Merkle(merkle::Error::RangeOutOfBounds(_)))
2443 ));
2444 }
2445
2446 #[test_traced("INFO")]
2447 fn test_historical_proof_start_too_large_returns_error_mmr() {
2448 let executor = deterministic::Runner::default();
2449 executor.start(|context| {
2450 test_historical_proof_start_too_large_returns_error_inner::<mmr::Family>(context)
2451 });
2452 }
2453
2454 #[test_traced("INFO")]
2455 fn test_historical_proof_start_too_large_returns_error_mmb() {
2456 let executor = deterministic::Runner::default();
2457 executor.start(|context| {
2458 test_historical_proof_start_too_large_returns_error_inner::<mmb::Family>(context)
2459 });
2460 }
2461
2462 async fn test_historical_proof_truly_historical_inner<F: Family + PartialEq>(context: Context) {
2464 let mut journal = create_journal_with_ops::<F>(context, "proof_historical", 50).await;
2466
2467 let hasher = StandardHasher::new(ForwardFold);
2469 let historical_root = journal_root(&journal);
2470 let historical_size = journal.size();
2471
2472 for i in 50..100 {
2474 journal
2475 .append(&create_operation::<F>(i as u8))
2476 .await
2477 .unwrap();
2478 }
2479 journal.sync().await.unwrap();
2480
2481 let (proof, ops) = journal
2483 .historical_proof(historical_size, Location::<F>::new(0), NZU64!(50), 0)
2484 .await
2485 .unwrap();
2486
2487 assert_eq!(ops.len(), 50);
2489 for (i, op) in ops.iter().enumerate() {
2490 assert_eq!(*op, create_operation::<F>(i as u8));
2491 }
2492
2493 assert!(verify_proof(
2495 &proof,
2496 &ops,
2497 Location::<F>::new(0),
2498 &historical_root,
2499 &hasher
2500 ));
2501 }
2502
2503 #[test_traced("INFO")]
2504 fn test_historical_proof_truly_historical_mmr() {
2505 let executor = deterministic::Runner::default();
2506 executor.start(test_historical_proof_truly_historical_inner::<mmr::Family>);
2507 }
2508
2509 #[test_traced("INFO")]
2510 fn test_historical_proof_truly_historical_mmb() {
2511 let executor = deterministic::Runner::default();
2512 executor.start(test_historical_proof_truly_historical_inner::<mmb::Family>);
2513 }
2514
2515 async fn test_historical_proof_pruned_location_returns_error_inner<F: Family + PartialEq>(
2517 context: Context,
2518 ) {
2519 let mut journal = create_journal_with_ops::<F>(context, "proof_pruned", 50).await;
2520
2521 journal
2522 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(25)))
2523 .await
2524 .unwrap();
2525 journal.sync().await.unwrap();
2526 let pruned_boundary = journal.prune(Location::<F>::new(25)).await.unwrap();
2527
2528 let size = journal.size();
2530 let start_loc = Location::<F>::new(0);
2531 if start_loc < pruned_boundary {
2532 let result = journal
2533 .historical_proof(size, start_loc, NZU64!(1), 0)
2534 .await;
2535
2536 assert!(result.is_err());
2538 }
2539 }
2540
2541 #[test_traced("INFO")]
2542 fn test_historical_proof_pruned_location_returns_error_mmr() {
2543 let executor = deterministic::Runner::default();
2544 executor.start(|context| {
2545 test_historical_proof_pruned_location_returns_error_inner::<mmr::Family>(context)
2546 });
2547 }
2548
2549 #[test_traced("INFO")]
2550 fn test_historical_proof_pruned_location_returns_error_mmb() {
2551 let executor = deterministic::Runner::default();
2552 executor.start(|context| {
2553 test_historical_proof_pruned_location_returns_error_inner::<mmb::Family>(context)
2554 });
2555 }
2556
2557 async fn test_replay_operations_inner<F: Family + PartialEq>(context: Context) {
2559 let journal = create_empty_journal::<F>(context.child("empty"), "replay").await;
2561 let stream = journal.replay(0, NZUsize!(10)).await.unwrap();
2562 futures::pin_mut!(stream);
2563 assert!(stream.next().await.is_none());
2564
2565 let journal = create_journal_with_ops::<F>(context.child("with_ops"), "replay", 50).await;
2567 let stream = journal.replay(0, NZUsize!(100)).await.unwrap();
2568 futures::pin_mut!(stream);
2569
2570 for i in 0..50 {
2571 let (pos, op) = stream.next().await.unwrap().unwrap();
2572 assert_eq!(pos, i);
2573 assert_eq!(op, create_operation::<F>(i as u8));
2574 }
2575
2576 assert!(stream.next().await.is_none());
2577 }
2578
2579 #[test_traced("INFO")]
2580 fn test_replay_operations_mmr() {
2581 let executor = deterministic::Runner::default();
2582 executor.start(test_replay_operations_inner::<mmr::Family>);
2583 }
2584
2585 #[test_traced("INFO")]
2586 fn test_replay_operations_mmb() {
2587 let executor = deterministic::Runner::default();
2588 executor.start(test_replay_operations_inner::<mmb::Family>);
2589 }
2590
2591 async fn test_replay_from_middle_inner<F: Family + PartialEq>(context: Context) {
2593 let journal = create_journal_with_ops::<F>(context, "replay_middle", 50).await;
2594 let stream = journal.replay(25, NZUsize!(100)).await.unwrap();
2595 futures::pin_mut!(stream);
2596
2597 let mut count = 0;
2598 while let Some(result) = stream.next().await {
2599 let (pos, op) = result.unwrap();
2600 assert_eq!(pos, 25 + count);
2601 assert_eq!(op, create_operation::<F>((25 + count) as u8));
2602 count += 1;
2603 }
2604
2605 assert_eq!(count, 25);
2607 }
2608
2609 #[test_traced("INFO")]
2610 fn test_replay_from_middle_mmr() {
2611 let executor = deterministic::Runner::default();
2612 executor.start(test_replay_from_middle_inner::<mmr::Family>);
2613 }
2614
2615 #[test_traced("INFO")]
2616 fn test_replay_from_middle_mmb() {
2617 let executor = deterministic::Runner::default();
2618 executor.start(test_replay_from_middle_inner::<mmb::Family>);
2619 }
2620
2621 async fn test_speculative_batch_inner<F: Family + PartialEq>(context: Context) {
2623 let mut journal = create_journal_with_ops::<F>(context, "speculative_batch", 10).await;
2624 let original_root = journal_root(&journal);
2625
2626 let b1 = journal.new_batch();
2628 let b2 = journal.new_batch();
2629
2630 let op_a = create_operation::<F>(100);
2632 let op_b = create_operation::<F>(200);
2633 let b1 = b1.add(op_a.clone());
2634 let b2 = b2.add(op_b);
2635
2636 let m1 = journal.merkle.with_mem(|mem| b1.merkleize(mem));
2638 let m2 = journal.merkle.with_mem(|mem| b2.merkleize(mem));
2639 assert_ne!(batch_root(&journal, &m1), batch_root(&journal, &m2));
2640 assert_ne!(batch_root(&journal, &m1), original_root);
2641 assert_ne!(batch_root(&journal, &m2), original_root);
2642
2643 assert_eq!(journal_root(&journal), original_root);
2645
2646 let expected_root = batch_root(&journal, &m1);
2648 journal.apply_batch(&m1).await.unwrap();
2649
2650 assert_eq!(journal_root(&journal), expected_root);
2652 assert_eq!(*journal.size(), 11);
2653 }
2654
2655 #[test_traced("INFO")]
2656 fn test_speculative_batch_mmr() {
2657 let executor = deterministic::Runner::default();
2658 executor.start(test_speculative_batch_inner::<mmr::Family>);
2659 }
2660
2661 #[test_traced("INFO")]
2662 fn test_speculative_batch_mmb() {
2663 let executor = deterministic::Runner::default();
2664 executor.start(test_speculative_batch_inner::<mmb::Family>);
2665 }
2666
2667 async fn test_speculative_batch_stacking_inner<F: Family + PartialEq>(context: Context) {
2670 let mut journal = create_journal_with_ops::<F>(context, "batch_stacking", 10).await;
2671
2672 let op_a = create_operation::<F>(100);
2673 let op_b = create_operation::<F>(200);
2674
2675 let (merkleized_a, merkleized_b) = {
2676 let batch_a = journal.new_batch().add(op_a.clone());
2677 let merkleized_a = journal.merkle.with_mem(|mem| batch_a.merkleize(mem));
2678
2679 let batch_b = merkleized_a.new_batch::<Sha256>().add(op_b.clone());
2680 let merkleized_b = journal.merkle.with_mem(|mem| batch_b.merkleize(mem));
2681 (merkleized_a, merkleized_b)
2682 };
2683
2684 let expected_root = batch_root(&journal, &merkleized_b);
2685 journal.apply_batch(&merkleized_b).await.unwrap();
2686 drop(merkleized_a);
2687
2688 assert_eq!(journal_root(&journal), expected_root);
2689 assert_eq!(*journal.size(), 12);
2690
2691 let read_a = journal.read(*Location::<F>::new(10)).await.unwrap();
2693 assert_eq!(read_a, op_a);
2694 let read_b = journal.read(*Location::<F>::new(11)).await.unwrap();
2695 assert_eq!(read_b, op_b);
2696 }
2697
2698 #[test_traced("INFO")]
2699 fn test_speculative_batch_stacking_mmr() {
2700 let executor = deterministic::Runner::default();
2701 executor.start(test_speculative_batch_stacking_inner::<mmr::Family>);
2702 }
2703
2704 #[test_traced("INFO")]
2705 fn test_speculative_batch_stacking_mmb() {
2706 let executor = deterministic::Runner::default();
2707 executor.start(test_speculative_batch_stacking_inner::<mmb::Family>);
2708 }
2709
2710 async fn test_speculative_batch_sequential_inner<F: Family + PartialEq>(context: Context) {
2713 let mut journal = create_journal_with_ops::<F>(context, "batch_sequential", 10).await;
2714
2715 let op_a = create_operation::<F>(100);
2716 let op_b = create_operation::<F>(200);
2717
2718 let batch_a = journal.new_batch().add(op_a.clone());
2720 let merkleized_a = journal.merkle.with_mem(|mem| batch_a.merkleize(mem));
2721 journal.apply_batch(&merkleized_a).await.unwrap();
2722 assert_eq!(*journal.size(), 11);
2723
2724 let batch_b = journal.new_batch().add(op_b.clone());
2726 let merkleized_b = journal.merkle.with_mem(|mem| batch_b.merkleize(mem));
2727 let expected_root = batch_root(&journal, &merkleized_b);
2728 journal.apply_batch(&merkleized_b).await.unwrap();
2729
2730 assert_eq!(journal_root(&journal), expected_root);
2731 assert_eq!(*journal.size(), 12);
2732
2733 let read_a = journal.read(*Location::<F>::new(10)).await.unwrap();
2735 assert_eq!(read_a, op_a);
2736 let read_b = journal.read(*Location::<F>::new(11)).await.unwrap();
2737 assert_eq!(read_b, op_b);
2738 }
2739
2740 #[test_traced("INFO")]
2741 fn test_speculative_batch_sequential_mmr() {
2742 let executor = deterministic::Runner::default();
2743 executor.start(test_speculative_batch_sequential_inner::<mmr::Family>);
2744 }
2745
2746 #[test_traced("INFO")]
2747 fn test_speculative_batch_sequential_mmb() {
2748 let executor = deterministic::Runner::default();
2749 executor.start(test_speculative_batch_sequential_inner::<mmb::Family>);
2750 }
2751
2752 async fn test_stale_batch_sibling_inner<F: Family + PartialEq>(context: Context) {
2753 let mut journal = create_empty_journal::<F>(context, "stale-sibling").await;
2754 let op_a = create_operation::<F>(1);
2755 let op_b = create_operation::<F>(2);
2756
2757 let batch_a = journal.new_batch().add(op_a.clone());
2759 let merkleized_a = journal.merkle.with_mem(|mem| batch_a.merkleize(mem));
2760 let batch_b = journal.new_batch().add(op_b);
2761 let merkleized_b = journal.merkle.with_mem(|mem| batch_b.merkleize(mem));
2762
2763 journal.apply_batch(&merkleized_a).await.unwrap();
2765 let expected_root = journal_root(&journal);
2766 let expected_size = journal.size();
2767
2768 let result = journal.apply_batch(&merkleized_b).await;
2770 assert!(
2771 matches!(
2772 result,
2773 Err(super::Error::Merkle(merkle::Error::StaleBatch { .. }))
2774 ),
2775 "expected StaleBatch, got {result:?}"
2776 );
2777
2778 assert_eq!(journal_root(&journal), expected_root);
2780 assert_eq!(journal.size(), expected_size);
2781 let (_, ops) = journal
2782 .proof(Location::<F>::new(0), NZU64!(1), 0)
2783 .await
2784 .unwrap();
2785 assert_eq!(ops, vec![op_a]);
2786 }
2787
2788 #[test_traced("INFO")]
2789 fn test_stale_batch_sibling_mmr() {
2790 let executor = deterministic::Runner::default();
2791 executor.start(test_stale_batch_sibling_inner::<mmr::Family>);
2792 }
2793
2794 #[test_traced("INFO")]
2795 fn test_stale_batch_sibling_mmb() {
2796 let executor = deterministic::Runner::default();
2797 executor.start(test_stale_batch_sibling_inner::<mmb::Family>);
2798 }
2799
2800 async fn test_stale_batch_chained_inner<F: Family + PartialEq>(context: Context) {
2801 let mut journal = create_journal_with_ops::<F>(context, "stale-chained", 5).await;
2802
2803 let parent_batch = journal.new_batch().add(create_operation::<F>(10));
2805 let parent = journal.merkle.with_mem(|mem| parent_batch.merkleize(mem));
2806 let batch_a = parent.new_batch::<Sha256>().add(create_operation::<F>(20));
2807 let child_a = journal.merkle.with_mem(|mem| batch_a.merkleize(mem));
2808 let batch_b = parent.new_batch::<Sha256>().add(create_operation::<F>(30));
2809 let child_b = journal.merkle.with_mem(|mem| batch_b.merkleize(mem));
2810
2811 journal.apply_batch(&child_a).await.unwrap();
2813 let result = journal.apply_batch(&child_b).await;
2814 drop(parent);
2815 assert!(
2816 matches!(
2817 result,
2818 Err(super::Error::Merkle(merkle::Error::StaleBatch { .. }))
2819 ),
2820 "expected StaleBatch for sibling, got {result:?}"
2821 );
2822 }
2823
2824 #[test_traced("INFO")]
2825 fn test_stale_batch_chained_mmr() {
2826 let executor = deterministic::Runner::default();
2827 executor.start(test_stale_batch_chained_inner::<mmr::Family>);
2828 }
2829
2830 #[test_traced("INFO")]
2831 fn test_stale_batch_chained_mmb() {
2832 let executor = deterministic::Runner::default();
2833 executor.start(test_stale_batch_chained_inner::<mmb::Family>);
2834 }
2835
2836 async fn test_stale_batch_parent_before_child_inner<F: Family + PartialEq>(context: Context) {
2837 let mut journal = create_empty_journal::<F>(context, "stale-parent-first").await;
2838
2839 let parent_batch = journal.new_batch().add(create_operation::<F>(1));
2841 let parent = journal.merkle.with_mem(|mem| parent_batch.merkleize(mem));
2842 let child_batch = parent.new_batch::<Sha256>().add(create_operation::<F>(2));
2843 let child = journal.merkle.with_mem(|mem| child_batch.merkleize(mem));
2844
2845 let expected_root = batch_root(&journal, &child);
2846
2847 journal.apply_batch(&parent).await.unwrap();
2849 journal.apply_batch(&child).await.unwrap();
2850
2851 assert_eq!(journal_root(&journal), expected_root);
2852 assert_eq!(*journal.size(), 2);
2853 }
2854
2855 #[test_traced("INFO")]
2856 fn test_stale_batch_parent_before_child_mmr() {
2857 let executor = deterministic::Runner::default();
2858 executor.start(test_stale_batch_parent_before_child_inner::<mmr::Family>);
2859 }
2860
2861 #[test_traced("INFO")]
2862 fn test_stale_batch_parent_before_child_mmb() {
2863 let executor = deterministic::Runner::default();
2864 executor.start(test_stale_batch_parent_before_child_inner::<mmb::Family>);
2865 }
2866
2867 async fn test_stale_batch_child_before_parent_inner<F: Family + PartialEq>(context: Context) {
2868 let mut journal = create_empty_journal::<F>(context, "stale-child-first").await;
2869
2870 let parent_batch = journal.new_batch().add(create_operation::<F>(1));
2872 let parent = journal.merkle.with_mem(|mem| parent_batch.merkleize(mem));
2873 let child_batch = parent.new_batch::<Sha256>().add(create_operation::<F>(2));
2874 let child = journal.merkle.with_mem(|mem| child_batch.merkleize(mem));
2875
2876 journal.apply_batch(&child).await.unwrap();
2878 let result = journal.apply_batch(&parent).await;
2879 assert!(
2880 matches!(
2881 result,
2882 Err(super::Error::Merkle(merkle::Error::StaleBatch { .. }))
2883 ),
2884 "expected StaleBatch for parent after child applied, got {result:?}"
2885 );
2886 }
2887
2888 #[test_traced("INFO")]
2889 fn test_stale_batch_child_before_parent_mmr() {
2890 let executor = deterministic::Runner::default();
2891 executor.start(test_stale_batch_child_before_parent_inner::<mmr::Family>);
2892 }
2893
2894 #[test_traced("INFO")]
2895 fn test_stale_batch_child_before_parent_mmb() {
2896 let executor = deterministic::Runner::default();
2897 executor.start(test_stale_batch_child_before_parent_inner::<mmb::Family>);
2898 }
2899
2900 async fn test_apply_batch_skip_ancestor_items_inner<F: Family + PartialEq>(context: Context) {
2902 let mut journal = create_journal_with_ops::<F>(context, "rp-skip", 3).await;
2903
2904 let parent_batch = journal
2906 .new_batch()
2907 .add(create_operation::<F>(10))
2908 .add(create_operation::<F>(11));
2909 let parent = journal.merkle.with_mem(|mem| parent_batch.merkleize(mem));
2910
2911 let child_batch = parent
2913 .new_batch::<Sha256>()
2914 .add(create_operation::<F>(20))
2915 .add(create_operation::<F>(21))
2916 .add(create_operation::<F>(22));
2917 let child = journal.merkle.with_mem(|mem| child_batch.merkleize(mem));
2918
2919 journal.apply_batch(&parent).await.unwrap();
2921
2922 journal.apply_batch(&child).await.unwrap();
2924
2925 let (_, ops) = journal
2927 .proof(Location::<F>::new(3), NZU64!(5), 0)
2928 .await
2929 .unwrap();
2930 assert_eq!(ops.len(), 5);
2931 }
2932
2933 #[test_traced("INFO")]
2934 fn test_apply_batch_skip_ancestor_items_mmr() {
2935 let executor = deterministic::Runner::default();
2936 executor.start(test_apply_batch_skip_ancestor_items_inner::<mmr::Family>);
2937 }
2938
2939 #[test_traced("INFO")]
2940 fn test_apply_batch_skip_ancestor_items_mmb() {
2941 let executor = deterministic::Runner::default();
2942 executor.start(test_apply_batch_skip_ancestor_items_inner::<mmb::Family>);
2943 }
2944
2945 async fn test_apply_batch_cross_batch_inner<F: Family + PartialEq>(context: Context) {
2947 let mut journal = create_journal_with_ops::<F>(context, "rp-cross", 2).await;
2948
2949 let grandparent_batch = journal
2951 .new_batch()
2952 .add(create_operation::<F>(3))
2953 .add(create_operation::<F>(4))
2954 .add(create_operation::<F>(5));
2955 let grandparent = journal
2956 .merkle
2957 .with_mem(|mem| grandparent_batch.merkleize(mem));
2958
2959 let parent_batch = grandparent
2961 .new_batch::<Sha256>()
2962 .add(create_operation::<F>(6))
2963 .add(create_operation::<F>(7));
2964 let parent = journal.merkle.with_mem(|mem| parent_batch.merkleize(mem));
2965
2966 let child_batch = parent.new_batch::<Sha256>().add(create_operation::<F>(8));
2968 let child = journal.merkle.with_mem(|mem| child_batch.merkleize(mem));
2969
2970 journal.apply_batch(&grandparent).await.unwrap();
2972
2973 journal.apply_batch(&parent).await.unwrap();
2975
2976 journal.apply_batch(&child).await.unwrap();
2978
2979 assert_eq!(*journal.size(), 8);
2981
2982 let (_, ops) = journal
2984 .proof(Location::<F>::new(2), NZU64!(6), 0)
2985 .await
2986 .unwrap();
2987 for (i, op) in ops.iter().enumerate() {
2988 assert_eq!(*op, create_operation::<F>((i + 3) as u8));
2989 }
2990 }
2991
2992 #[test_traced("INFO")]
2993 fn test_apply_batch_cross_batch_mmr() {
2994 let executor = deterministic::Runner::default();
2995 executor.start(test_apply_batch_cross_batch_inner::<mmr::Family>);
2996 }
2997
2998 #[test_traced("INFO")]
2999 fn test_apply_batch_cross_batch_mmb() {
3000 let executor = deterministic::Runner::default();
3001 executor.start(test_apply_batch_cross_batch_inner::<mmb::Family>);
3002 }
3003
3004 async fn test_merkleize_with_matches_add_inner<F: Family + PartialEq>(context: Context) {
3006 let journal = create_journal_with_ops::<F>(context, "mw-matches", 5).await;
3007
3008 let ops = vec![
3009 create_operation::<F>(10),
3010 create_operation::<F>(11),
3011 create_operation::<F>(12),
3012 ];
3013
3014 let mut batch = journal.new_batch();
3016 for op in &ops {
3017 batch = batch.add(op.clone());
3018 }
3019 let expected = journal.merkle.with_mem(|mem| batch.merkleize(mem));
3020
3021 let batch = journal.new_batch();
3023 let actual = journal
3024 .merkle
3025 .with_mem(|mem| merkleize_with(batch, mem, ops));
3026
3027 assert_eq!(
3028 batch_root(&journal, &actual),
3029 batch_root(&journal, &expected)
3030 );
3031 }
3032
3033 #[test_traced("INFO")]
3034 fn test_merkleize_with_matches_add_mmr() {
3035 let executor = deterministic::Runner::default();
3036 executor.start(test_merkleize_with_matches_add_inner::<mmr::Family>);
3037 }
3038
3039 #[test_traced("INFO")]
3040 fn test_merkleize_with_matches_add_mmb() {
3041 let executor = deterministic::Runner::default();
3042 executor.start(test_merkleize_with_matches_add_inner::<mmb::Family>);
3043 }
3044
3045 async fn test_merkleize_with_apply_inner<F: Family + PartialEq>(context: Context) {
3047 let mut journal = create_journal_with_ops::<F>(context, "mw-apply", 5).await;
3048
3049 let ops = vec![create_operation::<F>(10), create_operation::<F>(11)];
3050 let batch = journal.new_batch();
3051 let merkleized = journal
3052 .merkle
3053 .with_mem(|mem| merkleize_with(batch, mem, ops.clone()));
3054
3055 let expected_root = batch_root(&journal, &merkleized);
3056 journal.apply_batch(&merkleized).await.unwrap();
3057
3058 assert_eq!(journal_root(&journal), expected_root);
3059 assert_eq!(*journal.size(), 7);
3060
3061 assert_eq!(journal.read(5).await.unwrap(), ops[0]);
3062 assert_eq!(journal.read(6).await.unwrap(), ops[1]);
3063 }
3064
3065 #[test_traced("INFO")]
3066 fn test_merkleize_with_apply_mmr() {
3067 let executor = deterministic::Runner::default();
3068 executor.start(test_merkleize_with_apply_inner::<mmr::Family>);
3069 }
3070
3071 #[test_traced("INFO")]
3072 fn test_merkleize_with_apply_mmb() {
3073 let executor = deterministic::Runner::default();
3074 executor.start(test_merkleize_with_apply_inner::<mmb::Family>);
3075 }
3076
3077 async fn test_apply_batch_skips_only_committed_ancestor_items_inner<F: Family + PartialEq>(
3080 context: Context,
3081 ) {
3082 let mut journal = create_empty_journal::<F>(context.child("storage"), "skip-partial").await;
3083
3084 let a_batch = journal.new_batch().add(create_operation::<F>(1));
3086 let a = journal.merkle.with_mem(|mem| a_batch.merkleize(mem));
3087 let b_batch = a.new_batch::<Sha256>().add(create_operation::<F>(2));
3088 let b = journal.merkle.with_mem(|mem| b_batch.merkleize(mem));
3089 let c_batch = b.new_batch::<Sha256>().add(create_operation::<F>(3));
3090 let c = journal.merkle.with_mem(|mem| c_batch.merkleize(mem));
3091
3092 journal.apply_batch(&a).await.unwrap();
3094 journal.apply_batch(&c).await.unwrap();
3095
3096 assert_eq!(*journal.size(), 3);
3098
3099 let mut reference =
3101 create_empty_journal::<F>(context.child("ref"), "skip-partial-ref").await;
3102 for i in 1..=3u8 {
3103 reference.append(&create_operation::<F>(i)).await.unwrap();
3104 }
3105 assert_eq!(journal_root(&journal), journal_root(&reference));
3106 }
3107
3108 #[test_traced("INFO")]
3109 fn test_apply_batch_skips_only_committed_ancestor_items_mmr() {
3110 let executor = deterministic::Runner::default();
3111 executor.start(test_apply_batch_skips_only_committed_ancestor_items_inner::<mmr::Family>);
3112 }
3113
3114 #[test_traced("INFO")]
3115 fn test_apply_batch_skips_only_committed_ancestor_items_mmb() {
3116 let executor = deterministic::Runner::default();
3117 executor.start(test_apply_batch_skips_only_committed_ancestor_items_inner::<mmb::Family>);
3118 }
3119}