1use crate::{
16 Context,
17 journal::{
18 Error as JournalError,
19 contiguous::{Batch, Contiguous, Many, Mutable},
20 },
21 merkle::{
22 self, Bagging, Family, Location, Position, Proof, Readable, batch, full::Merkle,
23 hasher::Standard as StandardHasher, mem::Mem,
24 },
25};
26use alloc::{
27 sync::{Arc, Weak},
28 vec::Vec,
29};
30use commonware_codec::{Encode, EncodeShared};
31use commonware_cryptography::{Digest, Hasher};
32use commonware_macros::boxed;
33use commonware_parallel::Strategy;
34use commonware_runtime::{Handle, ReadOptions};
35use commonware_utils::NZU64;
36use core::{
37 num::{NonZeroU64, NonZeroUsize},
38 ops::Range,
39};
40use futures::{Stream, TryFutureExt as _, try_join};
41use thiserror::Error;
42use tracing::{debug, warn};
43
44#[derive(Error, Debug)]
46pub enum Error<F: Family> {
47 #[error("merkle error: {0}")]
48 Merkle(#[from] merkle::Error<F>),
49
50 #[error("journal error: {0}")]
51 Journal(#[from] super::Error),
52}
53
54type MerkleizedParent<F, H, Item, S> = Arc<MerkleizedBatch<F, <H as Hasher>::Digest, Item, S>>;
56
57pub struct UnmerkleizedBatch<F: Family, H: Hasher, Item: Send + Sync, S: Strategy> {
60 inner: batch::UnmerkleizedBatch<F, H::Digest, S>,
62 hasher: StandardHasher<H>,
64 items: Vec<Item>,
66 parent: Option<MerkleizedParent<F, H, Item, S>>,
68}
69
70type MerkleizedBatchArc<F, H, Item, S> = Arc<MerkleizedBatch<F, <H as Hasher>::Digest, Item, S>>;
71
72impl<F: Family, H: Hasher, Item: Encode + Send + Sync, S: Strategy>
73 UnmerkleizedBatch<F, H, Item, S>
74{
75 #[allow(clippy::should_implement_trait)]
77 pub fn add(mut self, item: Item) -> Self {
78 let encoded = item.encode();
79 self.inner = self.inner.add(&self.hasher, &encoded);
80 self.items.push(item);
81 self
82 }
83
84 fn collect_ancestor_items(parent: &MerkleizedParent<F, H, Item, S>) -> Vec<Arc<Vec<Item>>> {
87 let mut items = Vec::new();
88 if !parent.items.is_empty() {
89 items.push(Arc::clone(&parent.items));
90 }
91 let mut current = parent.parent.as_ref().and_then(Weak::upgrade);
92 while let Some(batch) = current {
93 if !batch.items.is_empty() {
94 items.push(Arc::clone(&batch.items));
95 }
96 current = batch.parent.as_ref().and_then(Weak::upgrade);
97 }
98 items.reverse();
99 items
100 }
101
102 pub fn merkleize(self, base: &Mem<F, H::Digest>) -> MerkleizedBatchArc<F, H, Item, S> {
105 let Self {
106 inner,
107 hasher,
108 items,
109 parent,
110 } = self;
111
112 let ancestor_items = parent
113 .as_ref()
114 .map_or_else(Vec::new, Self::collect_ancestor_items);
115 let items = Arc::new(items);
116 let merkle = inner.merkleize(base, &hasher);
117 Arc::new(MerkleizedBatch {
118 inner: merkle,
119 bagging: hasher.root_bagging(),
120 items,
121 parent: parent.as_ref().map(Arc::downgrade),
122 ancestor_items,
123 })
124 }
125
126 pub(crate) fn add_many(mut self, items: Vec<Item>) -> Self {
132 assert!(
133 self.items.is_empty(),
134 "add_many expects no items added via add"
135 );
136
137 self.inner = self.inner.add_many(&self.hasher, &items);
138 self.items = items;
139 self
140 }
141}
142
143#[derive(Clone, Debug)]
145pub struct MerkleizedBatch<F: Family, D: Digest, Item: Send + Sync, S: Strategy> {
146 pub(crate) inner: Arc<batch::MerkleizedBatch<F, D, S>>,
148 bagging: Bagging,
150 items: Arc<Vec<Item>>,
152 parent: Option<Weak<Self>>,
154 pub(crate) ancestor_items: Vec<Arc<Vec<Item>>>,
156}
157
158impl<F: Family, D: Digest, Item: Send + Sync, S: Strategy> MerkleizedBatch<F, D, Item, S> {
159 pub(crate) fn size(&self) -> u64 {
161 *self.inner.leaves()
162 }
163
164 pub fn root(
167 &self,
168 hasher: &impl merkle::hasher::Hasher<F, Digest = D>,
169 inactive_peaks: usize,
170 ) -> Result<D, merkle::Error<F>> {
171 self.inner.root(hasher, inactive_peaks)
172 }
173
174 pub fn proof(
176 &self,
177 base: &Mem<F, D>,
178 hasher: &impl merkle::hasher::Hasher<F, Digest = D>,
179 loc: Location<F>,
180 inactive_peaks: usize,
181 ) -> Result<Proof<F, D>, merkle::Error<F>> {
182 self.inner.proof(base, hasher, loc, inactive_peaks)
183 }
184
185 pub fn range_proof(
187 &self,
188 base: &Mem<F, D>,
189 hasher: &impl merkle::hasher::Hasher<F, Digest = D>,
190 range: core::ops::Range<Location<F>>,
191 inactive_peaks: usize,
192 ) -> Result<Proof<F, D>, merkle::Error<F>> {
193 self.inner.range_proof(base, hasher, range, inactive_peaks)
194 }
195
196 pub(crate) const fn items(&self) -> &Arc<Vec<Item>> {
198 &self.items
199 }
200
201 pub fn new_batch<H: Hasher<Digest = D>>(self: &Arc<Self>) -> UnmerkleizedBatch<F, H, Item, S>
206 where
207 Item: Encode,
208 {
209 UnmerkleizedBatch {
210 inner: self.inner.new_batch(),
211 hasher: StandardHasher::new(self.bagging),
212 items: Vec::new(),
213 parent: Some(Arc::clone(self)),
214 }
215 }
216}
217
218impl<F: Family, D: Digest, Item: Send + Sync, S: Strategy> Readable
219 for MerkleizedBatch<F, D, Item, S>
220{
221 type Family = F;
222 type Digest = D;
223
224 fn size(&self) -> Position<F> {
225 self.inner.size()
226 }
227
228 fn get_node(&self, pos: Position<F>) -> Option<D> {
229 self.inner.get_node(pos)
230 }
231}
232
233pub struct Journal<F, E, C, H, S>
238where
239 F: Family,
240 E: Context,
241 C: Contiguous<Item: EncodeShared>,
242 H: Hasher,
243 S: Strategy,
244{
245 pub(crate) merkle: Merkle<F, E, H::Digest, S>,
248
249 pub(crate) journal: C,
252
253 pub(crate) hasher: StandardHasher<H>,
254}
255
256impl<F, E, C, H, S> core::fmt::Debug for Journal<F, E, C, H, S>
257where
258 F: Family,
259 E: Context,
260 C: Contiguous<Item: EncodeShared>,
261 H: Hasher,
262 S: Strategy,
263{
264 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
265 f.debug_struct("Journal")
266 .field("size", &self.size())
267 .finish_non_exhaustive()
268 }
269}
270
271impl<F, E, C, H, S> Journal<F, E, C, H, S>
272where
273 F: Family,
274 E: Context,
275 C: Contiguous<Item: EncodeShared>,
276 H: Hasher,
277 S: Strategy,
278{
279 pub fn size(&self) -> Location<F> {
281 Location::new(self.journal.bounds().end)
282 }
283
284 pub fn root(&self, inactive_peaks: usize) -> Result<H::Digest, Error<F>> {
287 self.merkle
288 .root(&self.hasher, inactive_peaks)
289 .map_err(Into::into)
290 }
291
292 fn map_error(error: Error<F>) -> JournalError {
294 match error {
295 Error::Journal(inner) => inner,
296 Error::Merkle(inner) => JournalError::Merkle(anyhow::Error::from(inner)),
297 }
298 }
299
300 pub const fn strategy(&self) -> &S {
302 self.merkle.strategy()
303 }
304
305 pub fn new_batch(&self) -> UnmerkleizedBatch<F, H, C::Item, S> {
307 let root = self.merkle.to_batch();
308 UnmerkleizedBatch {
309 inner: root.new_batch(),
310 hasher: StandardHasher::new(self.hasher.root_bagging()),
311 items: Vec::new(),
312 parent: None,
313 }
314 }
315
316 pub(crate) async fn merkleize(
324 &self,
325 batch: UnmerkleizedBatch<F, H, C::Item, S>,
326 items: Vec<C::Item>,
327 inactive_peaks: usize,
328 ) -> Result<(MerkleizedBatchArc<F, H, C::Item, S>, H::Digest), merkle::Error<F>>
329 where
330 C::Item: 'static,
331 {
332 let ancestors = batch.inner.retain_ancestors();
333 let mem = self.merkle.snapshot();
334 let hasher = self.hasher.clone();
335 let strategy = self.strategy().clone();
336 strategy
337 .spawn(items.len(), move |_| {
338 let merkleized = batch.add_many(items).merkleize(&mem);
339 let root = merkleized.root(&hasher, inactive_peaks)?;
340 drop(ancestors);
341 Ok((merkleized, root))
342 })
343 .await
344 }
345
346 pub(crate) fn to_merkleized_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, C::Item, S>> {
351 Arc::new(MerkleizedBatch {
352 inner: self.merkle.to_batch(),
353 bagging: self.hasher.root_bagging(),
354 items: Arc::new(Vec::new()),
355 parent: None,
356 ancestor_items: Vec::new(),
357 })
358 }
359
360 pub async fn proof(
372 &self,
373 start_loc: Location<F>,
374 max_ops: NonZeroU64,
375 inactive_peaks: usize,
376 ) -> Result<(Proof<F, H::Digest>, Vec<C::Item>), Error<F>> {
377 self.historical_proof(self.size(), start_loc, max_ops, inactive_peaks)
378 .await
379 }
380
381 pub fn speculative_proof(
387 &self,
388 batch: &MerkleizedBatch<F, H::Digest, C::Item, S>,
389 inactive_peaks: usize,
390 ) -> Result<Proof<F, H::Digest>, Error<F>> {
391 let end = batch.size();
392 let start = Location::new(end - batch.items().len() as u64);
393 batch
394 .range_proof(
395 self.merkle.mem(),
396 &self.hasher,
397 start..Location::new(end),
398 inactive_peaks,
399 )
400 .map_err(Error::Merkle)
401 }
402
403 pub fn speculative_pinned_nodes(
409 &self,
410 batch: &MerkleizedBatch<F, H::Digest, C::Item, S>,
411 ) -> Result<Vec<H::Digest>, Error<F>> {
412 let start = Location::new(batch.size() - batch.items().len() as u64);
413 let mem = self.merkle.mem();
414 F::nodes_to_pin(start)
415 .map(|pos| {
416 batch
417 .get_node(pos)
418 .or_else(|| mem.get_node(pos))
419 .ok_or(merkle::Error::ElementPruned(pos))
420 })
421 .collect::<Result<Vec<_>, _>>()
422 .map_err(Error::Merkle)
423 }
424
425 pub async fn historical_proof(
438 &self,
439 historical_leaves: Location<F>,
440 start_loc: Location<F>,
441 max_ops: NonZeroU64,
442 inactive_peaks: usize,
443 ) -> Result<(Proof<F, H::Digest>, Vec<C::Item>), Error<F>> {
444 let bounds = self.journal.bounds();
445
446 if *historical_leaves > bounds.end {
447 return Err(merkle::Error::RangeOutOfBounds(Location::new(bounds.end)).into());
448 }
449 if start_loc >= historical_leaves {
450 return Err(merkle::Error::RangeOutOfBounds(start_loc).into());
451 }
452
453 let end_loc = std::cmp::min(historical_leaves, start_loc.saturating_add(max_ops.get()));
454
455 let hasher = self.hasher.clone();
456 let proof = self
457 .merkle
458 .historical_range_proof(
459 &hasher,
460 historical_leaves,
461 start_loc..end_loc,
462 inactive_peaks,
463 )
464 .await?;
465
466 let positions: Vec<u64> = (*start_loc..*end_loc).collect();
467 let ops = self.journal.read_many(&positions).await?;
468
469 Ok((proof, ops))
470 }
471
472 pub(crate) async fn read_many_sharded(
480 &self,
481 positions: &[u64],
482 ) -> Result<Vec<Vec<C::Item>>, JournalError> {
483 if positions.is_empty() {
484 return Ok(Vec::new());
485 }
486
487 assert!(
494 positions.is_sorted_by(|a, b| a < b),
495 "positions must be strictly increasing"
496 );
497 let strategy = self.strategy();
498 let journal = &self.journal;
499
500 let probe = |positions: &[u64]| -> (Vec<C::Item>, Vec<usize>) {
502 let probed = journal.try_read_many_sync(positions);
503 let mut hits = Vec::with_capacity(probed.len());
504 let mut missed = Vec::new();
505 for (idx, item) in probed.into_iter().enumerate() {
506 match item {
507 Some(item) => hits.push(item),
508 None => missed.push(idx),
509 }
510 }
511 (hits, missed)
512 };
513 let shards: Vec<(Vec<C::Item>, Vec<usize>)> =
514 strategy.run_batches(positions.len(), NonZeroUsize::MIN, 1, |batches| {
515 batches.map_collect_vec(|ranges| ranges, |range| probe(&positions[range]))
516 });
517
518 let mut misses: Vec<u64> = Vec::new();
522 let mut offset = 0;
523 for (hits, missed) in &shards {
524 misses.extend(missed.iter().map(|idx| positions[offset + idx]));
525 offset += hits.len() + missed.len();
526 }
527 if misses.is_empty() {
528 return Ok(shards.into_iter().map(|(hits, _)| hits).collect());
529 }
530 let mut fetched = journal.read_many(&misses).await?.into_iter();
531
532 let mut result = Vec::with_capacity(shards.len());
534 for (hits, missed) in shards {
535 if missed.is_empty() {
536 result.push(hits);
537 continue;
538 }
539 let total = hits.len() + missed.len();
540 let mut woven = Vec::with_capacity(total);
541 let mut hits = hits.into_iter();
542 let mut missed = missed.into_iter().peekable();
543 for idx in 0..total {
544 if missed.next_if_eq(&idx).is_some() {
545 woven.push(fetched.next().expect("one fetched item per miss"));
546 } else {
547 woven.push(hits.next().expect("one probed item per hit"));
548 }
549 }
550 result.push(woven);
551 }
552 Ok(result)
553 }
554}
555
556impl<F, E, C, H, S> Journal<F, E, C, H, S>
557where
558 F: Family,
559 E: Context,
560 C: Mutable<Item: EncodeShared>,
561 H: Hasher,
562 S: Strategy,
563{
564 pub async fn start_sync(mut self) -> Result<(Self, Handle<()>), Error<F>> {
570 let (journal_handle, merkle_handle);
571 ((self.journal, journal_handle), (self.merkle, merkle_handle)) = try_join!(
572 self.journal.start_sync().map_err(Error::Journal),
573 self.merkle.start_sync().map_err(Error::Merkle)
574 )?;
575
576 let handle =
577 Handle::from_future(
578 async move { try_join!(journal_handle, merkle_handle).map(|_| ()) },
579 );
580 Ok((self, handle))
581 }
582
583 pub async fn commit(mut self) -> Result<Self, Error<F>> {
587 (self.journal, self.merkle) = try_join!(
590 self.journal.commit().map_err(Error::Journal),
591 self.merkle.flush().map_err(Error::Merkle)
592 )?;
593
594 Ok(self)
595 }
596
597 #[boxed]
601 pub async fn from_components(
602 merkle: Merkle<F, E, H::Digest, S>,
603 journal: C,
604 hasher: StandardHasher<H>,
605 apply_batch_size: NonZeroU64,
606 ) -> Result<Self, Error<F>> {
607 let merkle = Self::align(merkle, &journal, &hasher, apply_batch_size).await?;
608
609 let merkle = merkle.sync().await?;
612
613 Ok(Self {
614 merkle,
615 journal,
616 hasher,
617 })
618 }
619
620 async fn align(
626 mut merkle: Merkle<F, E, H::Digest, S>,
627 journal: &C,
628 hasher: &StandardHasher<H>,
629 apply_batch_size: NonZeroU64,
630 ) -> Result<Merkle<F, E, H::Digest, S>, Error<F>> {
631 let journal_size = journal.bounds().end;
632 let mut merkle_leaves = merkle.leaves();
633 if merkle_leaves > journal_size {
634 return Err(Error::Journal(JournalError::Corruption(
635 "Merkle size exceeds the initialized operation journal".into(),
636 )));
637 }
638
639 if merkle_leaves < journal_size {
641 let replay_count = journal_size - *merkle_leaves;
642 warn!(
643 ?journal_size,
644 replay_count, "Merkle structure lags behind journal, replaying journal to catch up"
645 );
646
647 while merkle_leaves < journal_size {
648 let count = apply_batch_size.get().min(journal_size - *merkle_leaves);
649 let positions: Vec<u64> = (*merkle_leaves..*merkle_leaves + count).collect();
650 let items = journal.read_many(&positions).await?;
651 merkle_leaves += count;
652
653 let batch = merkle.new_batch().add_many(hasher, &items);
654 let batch = batch.merkleize(merkle.mem(), hasher);
655 merkle = merkle.apply_batch(&batch)?;
656 }
657 return Ok(merkle);
658 }
659
660 assert_eq!(journal.bounds().end, *merkle.leaves());
662
663 Ok(merkle)
664 }
665
666 pub async fn append(mut self, item: &C::Item) -> Result<(Self, Location<F>), Error<F>> {
668 let encoded_item = item.encode();
669
670 let loc;
672 (self.journal, loc) = self.journal.append(item).await?;
673 let unmerkleized_batch = self.merkle.new_batch().add(&self.hasher, &encoded_item);
674 let batch = unmerkleized_batch.merkleize(self.merkle.mem(), &self.hasher);
675 self.merkle = self.merkle.apply_batch(&batch)?;
676
677 Ok((self, Location::new(loc)))
678 }
679
680 pub async fn apply_batch(
687 mut self,
688 batch: &MerkleizedBatch<F, H::Digest, C::Item, S>,
689 ) -> Result<Self, Error<F>> {
690 let start = self.merkle.mem().validate_batch(&batch.inner)?;
692
693 let remaining = batch.inner.ancestor_count() - start;
699 let first = batch.ancestor_items.len().checked_sub(remaining).ok_or(
700 merkle::Error::AncestorDropped {
701 expected: batch.inner.size(),
702 actual: self.merkle.size(),
703 },
704 )?;
705
706 let mut batches: Vec<&[C::Item]> = Vec::with_capacity(remaining + 1);
709 batches.extend(
710 batch.ancestor_items[first..]
711 .iter()
712 .map(|items| items.as_slice()),
713 );
714 if !batch.items.is_empty() {
715 batches.push(&batch.items);
716 }
717 if !batches.is_empty() {
718 (self.journal, _) = self.journal.append_many(Many::Nested(&batches)).await?;
719 }
720
721 self.merkle = self.merkle.apply_batch(&batch.inner)?;
722 assert_eq!(*self.merkle.leaves(), self.journal.bounds().end);
723 Ok(self)
724 }
725
726 #[boxed]
733 pub async fn prune(self, prune_loc: Location<F>) -> Result<(Self, Location<F>), Error<F>> {
734 let (journal, boundary, _) = self.prune_inner(prune_loc).await?;
735 Ok((journal, boundary))
736 }
737
738 async fn prune_inner(
739 mut self,
740 prune_loc: Location<F>,
741 ) -> Result<(Self, Location<F>, bool), Error<F>> {
742 if self.merkle.size() == 0 {
743 let boundary = Location::new(self.journal.bounds().start);
745 return Ok((self, boundary, false));
746 }
747
748 (self.journal, self.merkle) = try_join!(
754 self.journal.commit().map_err(Error::Journal),
755 self.merkle.sync().map_err(Error::Merkle)
756 )?;
757
758 let journal_pruned;
759 (self.journal, journal_pruned) = self.journal.prune(*prune_loc).await?;
760 let bounds = self.journal.bounds();
761 let boundary = Location::new(bounds.start);
762 let merkle_boundary = self.merkle.bounds().start;
763
764 if boundary > merkle_boundary {
765 debug!(size = ?bounds.end, ?prune_loc, boundary = ?bounds.start, "pruned inactive ops");
766 self.merkle = self.merkle.prune(boundary).await?;
767 }
768
769 Ok((self, boundary, journal_pruned || boundary > merkle_boundary))
770 }
771
772 #[boxed]
774 pub async fn destroy(self) -> Result<(), Error<F>> {
775 let Self {
778 journal, merkle, ..
779 } = self;
780 try_join!(
781 journal.destroy().map_err(Error::Journal),
782 merkle.destroy().map_err(Error::Merkle),
783 )?;
784
785 Ok(())
786 }
787
788 pub async fn sync(mut self) -> Result<Self, Error<F>> {
790 (self.journal, self.merkle) = try_join!(
791 self.journal.sync().map_err(Error::Journal),
792 self.merkle.sync().map_err(Error::Merkle)
793 )?;
794
795 Ok(self)
796 }
797}
798
799pub(crate) struct Recovery<F, E, C, H, S>
801where
802 F: Family,
803 E: Context,
804 C: Backing<E>,
805 H: Hasher,
806 S: Strategy,
807{
808 journal: C::Recovery,
810 merkle: merkle::full::Recovery<F, E, H::Digest, S>,
812 hasher: StandardHasher<H>,
814 selected_end: u64,
816}
817
818impl<F, E, C, H, S> Recovery<F, E, C, H, S>
819where
820 F: Family,
821 E: Context,
822 C: Backing<E, Item: EncodeShared>,
823 H: Hasher,
824 S: Strategy,
825{
826 pub(crate) fn bounds(&self) -> Range<u64> {
828 self.journal.bounds().start..self.selected_end
829 }
830
831 pub(crate) fn is_fresh(&self) -> bool {
833 self.journal.bounds() == (0..0)
834 }
835
836 pub(crate) async fn read(&self, pos: u64) -> Result<C::Item, JournalError> {
838 self.journal.read(pos).await
839 }
840
841 pub(crate) async fn finish(self) -> Result<Journal<F, E, C, H, S>, Error<F>> {
843 let bounds = self.journal.bounds();
846 if self.selected_end < bounds.end {
847 warn!(
848 journal_size = bounds.end,
849 rewound_items = bounds.end - self.selected_end,
850 "rewinding journal items"
851 );
852 }
853 let journal = self.journal.finish(self.selected_end).await?;
854 let journal = journal.sync().await?;
855
856 let merkle = self.merkle.finish().await?;
858 let merkle =
859 Journal::<F, E, C, H, S>::align(merkle, &journal, &self.hasher, APPLY_BATCH_SIZE)
860 .await?;
861 let merkle = merkle.sync().await?;
862 Ok(Journal {
863 journal,
864 merkle,
865 hasher: self.hasher,
866 })
867 }
868}
869
870const APPLY_BATCH_SIZE: NonZeroU64 = NZU64!(1 << 16);
872
873impl<F, E, C, H, S> Journal<F, E, C, H, S>
874where
875 F: Family,
876 E: Context,
877 C: Backing<E, Item: EncodeShared>,
878 H: Hasher,
879 S: Strategy,
880{
881 #[boxed]
885 pub async fn new(
886 context: E,
887 merkle_cfg: merkle::full::Config<S>,
888 journal_cfg: C::Config,
889 predicate: fn(&C::Item) -> bool,
890 bagging: merkle::Bagging,
891 ) -> Result<Self, Error<F>> {
892 Self::prepare(context, merkle_cfg, journal_cfg, None, predicate, bagging)
893 .await?
894 .finish()
895 .await
896 }
897
898 #[boxed]
903 pub async fn init_at_most(
904 context: E,
905 merkle_cfg: merkle::full::Config<S>,
906 journal_cfg: C::Config,
907 max_size: u64,
908 predicate: fn(&C::Item) -> bool,
909 bagging: merkle::Bagging,
910 ) -> Result<Self, Error<F>> {
911 Self::prepare(
912 context,
913 merkle_cfg,
914 journal_cfg,
915 Some(max_size),
916 predicate,
917 bagging,
918 )
919 .await?
920 .finish()
921 .await
922 }
923
924 pub(crate) async fn prepare(
926 context: E,
927 merkle_cfg: merkle::full::Config<S>,
928 journal_cfg: C::Config,
929 max_size: Option<u64>,
930 predicate: fn(&C::Item) -> bool,
931 bagging: merkle::Bagging,
932 ) -> Result<Recovery<F, E, C, H, S>, Error<F>> {
933 let journal = C::recover(context.child("journal"), journal_cfg, max_size).await?;
935 let bounds = journal.bounds();
936
937 let selected_end = if max_size.is_none() && bounds.is_empty() {
940 bounds.end
941 } else {
942 journal
943 .last_matching(max_size.unwrap_or(u64::MAX), predicate)
944 .await?
945 };
946
947 let hasher = StandardHasher::<H>::new(bagging);
950 let merkle = Merkle::prepare(
951 context.child("merkle"),
952 &hasher,
953 merkle_cfg,
954 Some(Location::new(selected_end)),
955 )
956 .await?;
957 if *merkle.leaves() < bounds.start {
958 return Err(JournalError::ItemPruned(*merkle.leaves()).into());
959 }
960 Ok(Recovery {
961 journal,
962 merkle,
963 hasher,
964 selected_end,
965 })
966 }
967}
968
969impl<F, E, C, H, S> Contiguous for Journal<F, E, C, H, S>
970where
971 F: Family,
972 E: Context,
973 C: Contiguous<Item: EncodeShared>,
974 H: Hasher,
975 S: Strategy,
976{
977 type Item = C::Item;
978
979 fn bounds(&self) -> Range<u64> {
980 self.journal.bounds()
981 }
982
983 async fn read(&self, position: u64) -> Result<C::Item, JournalError> {
984 self.journal.read(position).await
985 }
986
987 async fn read_many(&self, positions: &[u64]) -> Result<Vec<C::Item>, JournalError> {
988 let mut shards = self.read_many_sharded(positions).await?;
989 if shards.len() == 1 {
990 return Ok(shards.pop().expect("length checked"));
991 }
992 let mut items = Vec::with_capacity(positions.len());
993 for shard in shards {
994 items.extend(shard);
995 }
996 Ok(items)
997 }
998
999 fn try_read_sync(&self, position: u64) -> Option<C::Item> {
1000 self.journal.try_read_sync(position)
1001 }
1002
1003 fn try_read_many_sync(&self, positions: &[u64]) -> Vec<Option<C::Item>> {
1004 self.journal.try_read_many_sync(positions)
1005 }
1006
1007 async fn replay(
1008 &self,
1009 range: Range<u64>,
1010 buffer: NonZeroUsize,
1011 read_options: ReadOptions,
1012 ) -> Result<impl Stream<Item = Result<Batch<C::Item>, JournalError>> + Send, JournalError> {
1013 self.journal.replay(range, buffer, read_options).await
1014 }
1015}
1016
1017impl<F, E, C, H, S> Mutable for Journal<F, E, C, H, S>
1018where
1019 F: Family,
1020 E: Context,
1021 C: Mutable<Item: EncodeShared>,
1022 H: Hasher,
1023 S: Strategy,
1024{
1025 async fn append(self, item: &Self::Item) -> Result<(Self, u64), JournalError> {
1026 let (journal, loc) = Self::append(self, item).await.map_err(Self::map_error)?;
1027
1028 Ok((journal, *loc))
1029 }
1030
1031 async fn append_many(
1032 mut self,
1033 items: Many<'_, Self::Item>,
1034 ) -> Result<(Self, u64), JournalError> {
1035 if items.is_empty() {
1038 return Err(JournalError::EmptyAppend);
1039 }
1040
1041 let mut last_pos = self.journal.bounds().end;
1045 match items {
1046 Many::Flat(items) => {
1047 for item in items {
1048 let (journal, loc) = Self::append(self, item).await.map_err(Self::map_error)?;
1049 self = journal;
1050 last_pos = *loc;
1051 }
1052 }
1053 Many::Nested(nested_items) => {
1054 for items in nested_items {
1055 for item in *items {
1056 let (journal, loc) =
1057 Self::append(self, item).await.map_err(Self::map_error)?;
1058 self = journal;
1059 last_pos = *loc;
1060 }
1061 }
1062 }
1063 }
1064 Ok((self, last_pos))
1065 }
1066
1067 async fn prune(self, min_position: u64) -> Result<(Self, bool), JournalError> {
1068 let prune_to = {
1069 let bounds = self.journal.bounds();
1070 min_position.min(bounds.end)
1071 };
1072
1073 let (journal, _, pruned) = self
1074 .prune_inner(Location::new(prune_to))
1075 .await
1076 .map_err(Self::map_error)?;
1077 Ok((journal, pruned))
1078 }
1079
1080 async fn start_sync(self) -> Result<(Self, Handle<()>), JournalError> {
1081 Self::start_sync(self).await.map_err(Self::map_error)
1082 }
1083
1084 async fn commit(self) -> Result<Self, JournalError> {
1085 Self::commit(self).await.map_err(Self::map_error)
1086 }
1087
1088 async fn sync(self) -> Result<Self, JournalError> {
1089 Self::sync(self).await.map_err(Self::map_error)
1090 }
1091
1092 async fn destroy(self) -> Result<(), JournalError> {
1093 Self::destroy(self).await.map_err(Self::map_error)
1094 }
1095}
1096
1097pub trait BackingRecovery: Send + Sync + Sized {
1102 type Journal: Mutable;
1104
1105 fn bounds(&self) -> Range<u64>;
1107
1108 fn read(
1110 &self,
1111 position: u64,
1112 ) -> impl Future<Output = Result<<Self::Journal as Contiguous>::Item, JournalError>> + Send;
1113
1114 fn reset(self, size: u64) -> impl Future<Output = Result<Self, JournalError>> + Send;
1116
1117 fn finish(self, size: u64) -> impl Future<Output = Result<Self::Journal, JournalError>> + Send;
1119
1120 fn last_matching<P>(
1122 &self,
1123 ceiling: u64,
1124 mut predicate: P,
1125 ) -> impl Future<Output = Result<u64, JournalError>> + Send
1126 where
1127 P: FnMut(&<Self::Journal as Contiguous>::Item) -> bool + Send,
1128 {
1129 async move {
1130 let bounds = self.bounds();
1131
1132 if ceiling < bounds.start {
1134 return Err(JournalError::ItemPruned(ceiling));
1135 }
1136
1137 let mut end = bounds.end.min(ceiling);
1139 while end > bounds.start {
1140 if predicate(&self.read(end - 1).await?) {
1141 return Ok(end);
1142 }
1143 end -= 1;
1144 }
1145
1146 if bounds.start != 0 {
1148 return Err(JournalError::ItemPruned(bounds.start));
1149 }
1150 Ok(0)
1151 }
1152 }
1153}
1154
1155pub trait Backing<E: Context>: Mutable {
1157 type Config: Clone + Send + Sync;
1159
1160 type Recovery: BackingRecovery<Journal = Self>;
1162
1163 fn recover(
1167 context: E,
1168 cfg: Self::Config,
1169 max_size: Option<u64>,
1170 ) -> impl Future<Output = Result<Self::Recovery, JournalError>> + Send;
1171
1172 fn clear(
1175 context: E,
1176 cfg: Self::Config,
1177 size: u64,
1178 ) -> impl Future<Output = Result<Self::Recovery, JournalError>> + Send;
1179
1180 fn span(
1185 context: E,
1186 cfg: &Self::Config,
1187 ) -> impl Future<Output = Result<Range<u64>, JournalError>> + Send;
1188}
1189
1190fn covers(span: &Range<u64>, position: u64) -> bool {
1192 position == span.start || span.contains(&position)
1193}
1194
1195pub(crate) enum Stored<E: Context, J: Backing<E>> {
1197 Opened(J::Recovery),
1199
1200 Unopened { context: E, cfg: J::Config },
1202}
1203
1204impl<E: Context, J: Backing<E>> Stored<E, J> {
1205 pub(crate) async fn open(
1208 context: E,
1209 cfg: J::Config,
1210 position: u64,
1211 max_size: u64,
1212 ) -> Result<Self, JournalError> {
1213 if !covers(&J::span(context.child("span"), &cfg).await?, position) {
1214 return Ok(Self::Unopened { context, cfg });
1215 }
1216 let recovery = J::recover(context, cfg, Some(max_size)).await?;
1217 Ok(Self::Opened(recovery))
1218 }
1219}
1220
1221pub(crate) async fn init_sync<E: Context, J: Backing<E>>(
1223 context: E,
1224 cfg: J::Config,
1225 range: Range<u64>,
1226) -> Result<J, JournalError> {
1227 assert!(!range.is_empty(), "range must not be empty");
1228
1229 let pending = match Stored::<E, J>::open(context, cfg, range.start, range.end).await? {
1233 Stored::Opened(pending) if covers(&pending.bounds(), range.start) => pending,
1234 Stored::Opened(pending) => pending.reset(range.start).await?,
1235 Stored::Unopened { context, cfg } => J::clear(context, cfg, range.start).await?,
1236 };
1237
1238 let journal = pending.finish(range.end).await?;
1240 let (journal, _) = journal.prune(range.start).await?;
1241 Ok(journal)
1242}
1243
1244#[cfg(test)]
1245mod tests {
1246 use super::*;
1247 use crate::{
1248 journal::contiguous::{
1249 fixed::{Config as JConfig, Journal as ContiguousJournal},
1250 tests::replay_items,
1251 },
1252 merkle::{
1253 Bagging::{BackwardFold, ForwardFold},
1254 full::{Config as MerkleConfig, Merkle},
1255 mmb, mmr,
1256 },
1257 qmdb::{
1258 any::{
1259 operation::{Unordered as Op, update::Unordered as Update},
1260 value::FixedEncoding,
1261 },
1262 operation::Committable,
1263 },
1264 utils::detached::{DropMonitor, block_strategy},
1265 };
1266 use commonware_codec::{Encode, FixedSize};
1267 use commonware_cryptography::{Sha256, sha256::Digest};
1268 use commonware_macros::{test_collect_traces, test_traced};
1269 use commonware_parallel::{
1270 Rayon, Sequential,
1271 mocks::{Explorer, explore},
1272 };
1273 use commonware_runtime::{
1274 Runner as _, Spawner as _, Strategizer as _, Supervisor as _,
1275 buffer::paged::CacheRef,
1276 deterministic::{self, Context},
1277 mocks::{
1278 DelayedSyncContext, PendingSyncs, RecordingContext, drive_pending_syncs,
1279 fail_pending_syncs, next_pending_sync,
1280 },
1281 reschedule,
1282 telemetry::traces::collector::TraceStorage,
1283 };
1284 use commonware_utils::{NZU16, NZU64, NZUsize, probability};
1285 use futures::StreamExt as _;
1286 use std::{
1287 future::Future,
1288 num::{NonZeroU16, NonZeroU64, NonZeroUsize},
1289 time::Duration,
1290 };
1291
1292 const PAGE_SIZE: NonZeroU16 = NZU16!(101);
1293 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(11);
1294
1295 type TestOp<F> = Op<F, Digest, FixedEncoding<Digest>>;
1297
1298 type TestJournal<F> = Journal<
1300 F,
1301 deterministic::Context,
1302 ContiguousJournal<deterministic::Context, TestOp<F>>,
1303 Sha256,
1304 Sequential,
1305 >;
1306
1307 type RecordingTestJournal<F> = Journal<
1308 F,
1309 RecordingContext<deterministic::Context>,
1310 ContiguousJournal<RecordingContext<deterministic::Context>, TestOp<F>>,
1311 Sha256,
1312 Sequential,
1313 >;
1314
1315 fn journal_root<F: Family>(journal: &TestJournal<F>) -> Digest {
1316 journal.root(0).unwrap()
1317 }
1318
1319 fn batch_root<F: Family>(
1320 journal: &TestJournal<F>,
1321 batch: &MerkleizedBatch<F, Digest, TestOp<F>, Sequential>,
1322 ) -> Digest {
1323 batch.root(&journal.hasher, 0).unwrap()
1324 }
1325
1326 fn merkleize_with<F: Family + PartialEq>(
1327 batch: UnmerkleizedBatch<F, Sha256, TestOp<F>, Sequential>,
1328 base: &Mem<F, Digest>,
1329 items: Vec<TestOp<F>>,
1330 ) -> MerkleizedBatchArc<F, Sha256, TestOp<F>, Sequential> {
1331 batch.add_many(items).merkleize(base)
1332 }
1333
1334 #[test]
1335 fn test_initialization_syncs_operations_before_merkle_repair() {
1336 deterministic::Runner::default().start(|context| async move {
1337 let operation_syncs = PendingSyncs::default();
1339 let merkle_syncs = PendingSyncs::default();
1340 operation_syncs.unblock();
1341 merkle_syncs.unblock();
1342 let operation_context = DelayedSyncContext {
1343 inner: context.child("operations"),
1344 pending: operation_syncs.clone(),
1345 };
1346 let merkle_context = DelayedSyncContext {
1347 inner: context.child("merkle"),
1348 pending: merkle_syncs.clone(),
1349 };
1350
1351 let cfg = merkle_config("sync-order");
1353 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1354 let merkle = Merkle::<mmr::Family, _, Digest, Sequential>::init(
1355 merkle_context.child("seed"),
1356 &hasher,
1357 cfg.clone(),
1358 )
1359 .await
1360 .unwrap();
1361 let batch = merkle.new_batch().add(&hasher, &Sha256::fill(1));
1362 let batch = batch.merkleize(merkle.mem(), &hasher);
1363 _ = merkle.apply_batch(&batch).unwrap().sync().await.unwrap();
1364 let merkle = Merkle::<mmr::Family, _, Digest, Sequential>::prepare(
1365 merkle_context,
1366 &hasher,
1367 cfg,
1368 Some(Location::new(0)),
1369 )
1370 .await
1371 .unwrap();
1372 let journal = ContiguousJournal::<_, TestOp<mmr::Family>>::recover(
1373 operation_context,
1374 journal_config("sync-order"),
1375 None,
1376 )
1377 .await
1378 .unwrap();
1379 let journal = Box::new(journal)
1380 .truncate(0)
1381 .await
1382 .unwrap()
1383 .append(&create_operation::<mmr::Family>(1))
1384 .await
1385 .unwrap();
1386 let pending = Recovery::<
1387 mmr::Family,
1388 _,
1389 ContiguousJournal<_, TestOp<mmr::Family>>,
1390 Sha256,
1391 Sequential,
1392 > {
1393 journal: *journal,
1394 merkle,
1395 hasher,
1396 selected_end: 1,
1397 };
1398
1399 operation_syncs.arm();
1401 merkle_syncs.arm();
1402 let finish = pending.finish();
1403 futures::pin_mut!(finish);
1404 assert!(futures::poll!(&mut finish).is_pending());
1405 assert_eq!(operation_syncs.calls(), 1);
1406 assert_eq!(merkle_syncs.calls(), 0);
1407
1408 operation_syncs.unblock();
1410 merkle_syncs.unblock();
1411 let journal = finish.await.unwrap();
1412 assert_eq!(journal.bounds(), 0..1);
1413 journal.destroy().await.unwrap();
1414 });
1415 }
1416
1417 fn merkle_config_with<S: Strategy>(suffix: &str, strategy: S) -> MerkleConfig<S> {
1419 MerkleConfig {
1420 journal_partition: format!("mmr-journal-{suffix}"),
1421 metadata_partition: format!("mmr-metadata-{suffix}"),
1422 items_per_blob: NZU64!(11),
1423 write_buffer: NZUsize!(1024),
1424 replay_buffer: NZUsize!(1024),
1425 strategy,
1426 page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
1427 }
1428 }
1429
1430 fn merkle_config(suffix: &str) -> MerkleConfig<Sequential> {
1432 merkle_config_with(suffix, Sequential)
1433 }
1434
1435 fn journal_config(suffix: &str) -> JConfig {
1437 JConfig {
1438 partition: format!("journal-{suffix}"),
1439 items_per_blob: NZU64!(7),
1440 write_buffer: NZUsize!(1024),
1441 replay_buffer: NZUsize!(1024),
1442 page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
1443 }
1444 }
1445
1446 async fn create_empty_journal<F: Family + PartialEq>(
1448 context: Context,
1449 suffix: &str,
1450 ) -> TestJournal<F> {
1451 let merkle_cfg = merkle_config(suffix);
1452 let journal_cfg = journal_config(suffix);
1453 TestJournal::<F>::new(
1454 context,
1455 merkle_cfg,
1456 journal_cfg,
1457 |op: &TestOp<F>| op.is_commit(),
1458 ForwardFold,
1459 )
1460 .await
1461 .unwrap()
1462 }
1463
1464 #[test]
1465 fn test_batches_inherit_journal_bagging() {
1466 deterministic::Runner::default().start(|context| async move {
1467 let merkle_cfg = merkle_config("batch-bagging");
1468 let journal_cfg = journal_config("batch-bagging");
1469 let journal = TestJournal::<mmr::Family>::new(
1470 context,
1471 merkle_cfg,
1472 journal_cfg,
1473 |op: &TestOp<mmr::Family>| op.is_commit(),
1474 BackwardFold,
1475 )
1476 .await
1477 .unwrap();
1478
1479 let batch = journal.new_batch();
1480 assert_eq!(batch.hasher.root_bagging(), BackwardFold);
1481
1482 let merkleized = batch.merkleize(journal.merkle.mem());
1483 let child: UnmerkleizedBatch<mmr::Family, Sha256, TestOp<mmr::Family>, Sequential> =
1484 merkleized.new_batch();
1485 assert_eq!(child.hasher.root_bagging(), BackwardFold);
1486 });
1487 }
1488
1489 #[test]
1491 fn test_read_many_shards_across_strategy_pool() {
1492 deterministic::Runner::default().start(|context| async move {
1493 let strategy = Explorer::parallel(&context.strategy(NZUsize!(2)));
1497 let merkle_cfg = merkle_config_with("shard", strategy);
1498 let journal_cfg = journal_config("shard");
1499 type ShardedJournal = Journal<
1500 mmr::Family,
1501 Context,
1502 ContiguousJournal<Context, TestOp<mmr::Family>>,
1503 Sha256,
1504 Explorer,
1505 >;
1506 let mut journal = ShardedJournal::new(
1507 context,
1508 merkle_cfg,
1509 journal_cfg,
1510 |op: &TestOp<mmr::Family>| op.is_commit(),
1511 ForwardFold,
1512 )
1513 .await
1514 .unwrap();
1515
1516 let count = 4200u64;
1517 for i in 0..count {
1518 let op = create_operation::<mmr::Family>((i % 251) as u8);
1519 (journal, _) = journal.append(&op).await.unwrap();
1520 }
1521 let journal = journal.sync().await.unwrap();
1522
1523 let positions: Vec<u64> = (0..count).collect();
1524 let shards = journal.read_many_sharded(&positions).await.unwrap();
1525 assert_eq!(shards.len(), 2, "the probe must run in two shards");
1526 let batch = Contiguous::read_many(&journal, &positions).await.unwrap();
1527 assert_eq!(batch.len(), positions.len());
1528 for &pos in &positions {
1529 let single = Contiguous::read(&journal, pos).await.unwrap();
1530 assert_eq!(batch[pos as usize], single);
1531 }
1532
1533 assert!(
1535 Contiguous::read_many(&journal, &[])
1536 .await
1537 .unwrap()
1538 .is_empty()
1539 );
1540 });
1541 }
1542
1543 #[test]
1545 #[should_panic(expected = "positions must be strictly increasing")]
1546 fn test_read_many_rejects_unsorted_positions() {
1547 deterministic::Runner::default().start(|context| async move {
1548 let mut journal = create_empty_journal::<mmr::Family>(context, "unsorted").await;
1549 for i in 0..2u8 {
1550 let op = create_operation::<mmr::Family>(i);
1551 (journal, _) = journal.append(&op).await.unwrap();
1552 }
1553 let journal = journal.sync().await.unwrap();
1554
1555 let _ = Contiguous::read_many(&journal, &[1, 0]).await;
1556 });
1557 }
1558
1559 fn create_operation<F: Family + PartialEq>(index: u8) -> TestOp<F> {
1561 TestOp::<F>::Update(Update(
1562 Sha256::fill(index),
1563 Sha256::fill(index.wrapping_add(1)),
1564 ))
1565 }
1566
1567 async fn create_journal_with_ops<F: Family + PartialEq>(
1571 context: Context,
1572 suffix: &str,
1573 count: usize,
1574 ) -> TestJournal<F> {
1575 let mut journal = create_empty_journal::<F>(context, suffix).await;
1576
1577 for i in 0..count {
1578 let op = create_operation::<F>(i as u8);
1579 let loc;
1580 (journal, loc) = journal.append(&op).await.unwrap();
1581 assert_eq!(loc, Location::<F>::new(i as u64));
1582 }
1583
1584 journal = journal.sync().await.unwrap();
1585 journal
1586 }
1587
1588 async fn create_components<F: Family + PartialEq>(
1594 context: Context,
1595 suffix: &str,
1596 ) -> (
1597 Merkle<F, deterministic::Context, Digest, Sequential>,
1598 ContiguousJournal<deterministic::Context, TestOp<F>>,
1599 StandardHasher<Sha256>,
1600 ) {
1601 let hasher = StandardHasher::new(ForwardFold);
1602 let merkle = Merkle::<F, _, Digest, Sequential>::init(
1603 context.child("mmr"),
1604 &hasher,
1605 merkle_config(suffix),
1606 )
1607 .await
1608 .unwrap();
1609 let journal = ContiguousJournal::init(context.child("journal"), journal_config(suffix))
1610 .await
1611 .unwrap();
1612 (merkle, journal, hasher)
1613 }
1614
1615 fn verify_proof<F: Family + PartialEq>(
1618 proof: &Proof<F, <Sha256 as commonware_cryptography::Hasher>::Digest>,
1619 operations: &[TestOp<F>],
1620 start_loc: Location<F>,
1621 root: &<Sha256 as commonware_cryptography::Hasher>::Digest,
1622 hasher: &StandardHasher<Sha256>,
1623 ) -> bool {
1624 let encoded_ops: Vec<_> = operations.iter().map(|op| op.encode()).collect();
1625 proof.verify_range_inclusion(hasher, &encoded_ops, start_loc, root)
1626 }
1627
1628 async fn test_new_creates_empty_journal_inner<F: Family + PartialEq>(context: Context) {
1630 let journal = create_empty_journal::<F>(context, "new-empty").await;
1631
1632 let bounds = journal.bounds();
1633 assert_eq!(bounds.end, 0);
1634 assert_eq!(bounds.start, 0);
1635 assert!(bounds.is_empty());
1636 }
1637
1638 #[test_traced("INFO")]
1639 fn test_new_creates_empty_journal_mmr() {
1640 let executor = deterministic::Runner::default();
1641 executor.start(test_new_creates_empty_journal_inner::<mmr::Family>);
1642 }
1643
1644 #[test_traced("INFO")]
1645 fn test_new_creates_empty_journal_mmb() {
1646 let executor = deterministic::Runner::default();
1647 executor.start(test_new_creates_empty_journal_inner::<mmb::Family>);
1648 }
1649
1650 async fn test_align_with_empty_mmr_and_journal_inner<F: Family + PartialEq>(context: Context) {
1652 let (merkle, journal, hasher) = create_components::<F>(context, "align-empty").await;
1653
1654 let merkle = TestJournal::<F>::align(merkle, &journal, &hasher, APPLY_BATCH_SIZE)
1655 .await
1656 .unwrap();
1657
1658 assert_eq!(merkle.leaves(), Location::<F>::new(0));
1659 assert_eq!(journal.size(), 0);
1660 }
1661
1662 #[test_traced("INFO")]
1663 fn test_align_with_empty_mmr_and_journal_mmr() {
1664 let executor = deterministic::Runner::default();
1665 executor.start(test_align_with_empty_mmr_and_journal_inner::<mmr::Family>);
1666 }
1667
1668 #[test_traced("INFO")]
1669 fn test_align_with_empty_mmr_and_journal_mmb() {
1670 let executor = deterministic::Runner::default();
1671 executor.start(test_align_with_empty_mmr_and_journal_inner::<mmb::Family>);
1672 }
1673
1674 async fn test_from_components_rejects_merkle_ahead_inner<F: Family + PartialEq>(
1676 context: Context,
1677 ) {
1678 let (mut merkle, mut journal, hasher) = create_components::<F>(context, "mmr-ahead").await;
1679
1680 {
1682 let batch = {
1683 let mut batch = merkle.new_batch();
1684 for i in 0..20 {
1685 let op = create_operation::<F>(i as u8);
1686 let encoded = op.encode();
1687 batch = batch.add(&hasher, &encoded);
1688 if i < 19 {
1689 (journal, _) = journal.append(&op).await.unwrap();
1690 }
1691 }
1692 batch
1693 };
1694 let batch = batch.merkleize(merkle.mem(), &hasher);
1695 merkle = merkle.apply_batch(&batch).unwrap();
1696 }
1697
1698 let journal = journal.sync().await.unwrap();
1699 let result =
1700 TestJournal::<F>::from_components(merkle, journal, hasher, APPLY_BATCH_SIZE).await;
1701 assert!(matches!(
1702 result,
1703 Err(Error::Journal(JournalError::Corruption(_)))
1704 ));
1705 }
1706
1707 #[test_traced("WARN")]
1708 fn test_from_components_rejects_merkle_ahead_mmr() {
1709 let executor = deterministic::Runner::default();
1710 executor.start(test_from_components_rejects_merkle_ahead_inner::<mmr::Family>);
1711 }
1712
1713 #[test_traced("WARN")]
1714 fn test_from_components_rejects_merkle_ahead_mmb() {
1715 let executor = deterministic::Runner::default();
1716 executor.start(test_from_components_rejects_merkle_ahead_inner::<mmb::Family>);
1717 }
1718
1719 async fn test_align_when_journal_ahead_inner<F: Family + PartialEq>(context: Context) {
1721 let (merkle, mut journal, hasher) = create_components::<F>(context, "journal-ahead").await;
1722
1723 for i in 0..20 {
1725 let op = create_operation::<F>(i as u8);
1726 (journal, _) = journal.append(&op).await.unwrap();
1727 }
1728
1729 let commit_op = TestOp::<F>::CommitFloor(None, Location::<F>::new(0));
1731 let (journal, _) = journal.append(&commit_op).await.unwrap();
1732 let journal = journal.sync().await.unwrap();
1733
1734 let merkle = TestJournal::<F>::align(merkle, &journal, &hasher, APPLY_BATCH_SIZE)
1736 .await
1737 .unwrap();
1738
1739 assert_eq!(merkle.leaves(), Location::<F>::new(21));
1741 assert_eq!(journal.size(), 21);
1742 }
1743
1744 #[test_traced("WARN")]
1745 fn test_align_when_journal_ahead_mmr() {
1746 let executor = deterministic::Runner::default();
1747 executor.start(test_align_when_journal_ahead_inner::<mmr::Family>);
1748 }
1749
1750 #[test_traced("WARN")]
1751 fn test_align_when_journal_ahead_mmb() {
1752 let executor = deterministic::Runner::default();
1753 executor.start(test_align_when_journal_ahead_inner::<mmb::Family>);
1754 }
1755
1756 async fn test_align_replay_parallel_matches_serial_inner<F: Family + PartialEq>(
1759 context: Context,
1760 strategy: Explorer,
1761 ) -> (Location<F>, Digest) {
1762 type ReplayJournal<F> = Journal<
1763 F,
1764 deterministic::Context,
1765 ContiguousJournal<deterministic::Context, TestOp<F>>,
1766 Sha256,
1767 Explorer,
1768 >;
1769
1770 let mut journal = ContiguousJournal::init(
1772 context.child("journal"),
1773 journal_config("replay-strategies"),
1774 )
1775 .await
1776 .unwrap();
1777 for i in 0..20 {
1778 (journal, _) = journal
1779 .append(&create_operation::<F>(i as u8))
1780 .await
1781 .unwrap();
1782 }
1783 let commit_op = TestOp::<F>::CommitFloor(None, Location::<F>::new(0));
1784 let (journal, _) = journal.append(&commit_op).await.unwrap();
1785 let journal = journal.sync().await.unwrap();
1786
1787 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1790 let merkle = Merkle::<F, _, Digest, Explorer>::init(
1791 context.child("mmr_replay"),
1792 &hasher,
1793 merkle_config_with("replay", strategy),
1794 )
1795 .await
1796 .unwrap();
1797 let merkle = ReplayJournal::<F>::align(merkle, &journal, &hasher, NZU64!(7))
1798 .await
1799 .unwrap();
1800
1801 assert_eq!(merkle.leaves(), Location::<F>::new(21));
1802 (merkle.leaves(), merkle.root(&hasher, 0).unwrap())
1803 }
1804
1805 #[test_traced("WARN")]
1806 fn test_align_replay_parallel_matches_serial_mmr() {
1807 explore(|strategy| {
1808 deterministic::Runner::default().start(|context| {
1809 test_align_replay_parallel_matches_serial_inner::<mmr::Family>(
1810 context,
1811 strategy.clone(),
1812 )
1813 })
1814 });
1815 }
1816
1817 #[test_traced("WARN")]
1818 fn test_align_replay_parallel_matches_serial_mmb() {
1819 explore(|strategy| {
1820 deterministic::Runner::default().start(|context| {
1821 test_align_replay_parallel_matches_serial_inner::<mmb::Family>(
1822 context,
1823 strategy.clone(),
1824 )
1825 })
1826 });
1827 }
1828
1829 async fn test_align_with_mismatched_committed_ops_inner<F: Family + PartialEq>(
1831 context: Context,
1832 ) {
1833 let mut journal = create_empty_journal::<F>(context.child("first"), "mismatched").await;
1834
1835 for i in 0..20 {
1837 let loc;
1838 (journal, loc) = journal
1839 .append(&create_operation::<F>(i as u8))
1840 .await
1841 .unwrap();
1842 assert_eq!(loc, Location::<F>::new(i as u64));
1843 }
1844
1845 let size_before = journal.size();
1846 assert_eq!(size_before, 20);
1847
1848 journal.sync().await.unwrap();
1850 let journal = create_empty_journal::<F>(context.child("second"), "mismatched").await;
1851
1852 assert_eq!(journal.size(), 0);
1854 }
1855
1856 #[test_collect_traces("INFO")]
1857 fn test_align_with_mismatched_committed_ops_mmr(traces: TraceStorage) {
1858 let executor = deterministic::Runner::default();
1859 executor.start(|context| {
1860 test_align_with_mismatched_committed_ops_inner::<mmr::Family>(context)
1861 });
1862 traces
1863 .get_by_level(tracing::Level::WARN)
1864 .expect_event(|event| {
1865 let metadata = &event.metadata;
1866 metadata.content == "rewinding journal items"
1867 && metadata.expect_field_exact("journal_size", "20").is_ok()
1868 && metadata.expect_field_exact("rewound_items", "20").is_ok()
1869 })
1870 .unwrap();
1871 }
1872
1873 #[test_traced("INFO")]
1874 fn test_align_with_mismatched_committed_ops_mmb() {
1875 let executor = deterministic::Runner::default();
1876 executor.start(|context| {
1877 test_align_with_mismatched_committed_ops_inner::<mmb::Family>(context)
1878 });
1879 }
1880
1881 async fn test_initialization_selection_inner<F: Family + PartialEq>(context: Context) {
1882 for start in [0, 7] {
1884 for cap in [0, 1, 2, 3, 4, 7, 8, 11, 12, 14, 15, 100] {
1885 let suffix = format!("select-{start}-{cap}");
1886 let cfg = journal_config(&suffix);
1887 let mut journal = ContiguousJournal::init(context.child("create"), cfg.clone())
1888 .await
1889 .unwrap();
1890 for i in 0..15 {
1891 let op = if [1, 3, 10, 13].contains(&i) {
1892 TestOp::<F>::CommitFloor(None, Location::new(0))
1893 } else {
1894 create_operation::<F>(i)
1895 };
1896 (journal, _) = journal.append(&op).await.unwrap();
1897 }
1898 journal = journal.sync().await.unwrap();
1899 if start > 0 {
1900 (journal, _) = journal.prune(start).await.unwrap();
1901 }
1902 drop(journal);
1903 let pending = <ContiguousJournal<_, TestOp<F>> as Backing<_>>::recover(
1904 context.child("select"),
1905 cfg.clone(),
1906 Some(cap),
1907 )
1908 .await;
1909
1910 let expected = [2, 4, 11, 14]
1912 .into_iter()
1913 .filter(|end| *end <= cap && *end > start)
1914 .max();
1915 if cap < start {
1916 assert!(matches!(pending, Err(JournalError::ItemPruned(_))));
1917 continue;
1918 }
1919 let pending = pending.unwrap();
1920 let selected = pending.last_matching(cap, |op| op.is_commit()).await;
1921 if expected.is_none() && start > 0 {
1922 assert!(matches!(selected, Err(JournalError::ItemPruned(_))));
1923 continue;
1924 }
1925 let size = selected.unwrap();
1926 assert_eq!(size, expected.unwrap_or(0));
1927
1928 drop(pending.finish(size).await.unwrap());
1930 let journal =
1931 ContiguousJournal::<_, TestOp<F>>::init(context.child("restart"), cfg)
1932 .await
1933 .unwrap();
1934 assert_eq!(journal.size(), size);
1935 }
1936 }
1937
1938 for cap in [0, 1, 2, 3, 4, 5, 6, 7, 8, 100] {
1940 let suffix = format!("authenticated-cap-{cap}");
1941 let mc = merkle_config(&suffix);
1942 let jc = journal_config(&suffix);
1943 let mut journal = TestJournal::<F>::new(
1944 context.child("create_auth"),
1945 mc.clone(),
1946 jc.clone(),
1947 |_| true,
1948 ForwardFold,
1949 )
1950 .await
1951 .unwrap();
1952 let mut roots = vec![journal_root(&journal)];
1953 for i in 0..8 {
1954 (journal, _) = journal.append(&create_operation::<F>(i)).await.unwrap();
1955 roots.push(journal_root(&journal));
1956 }
1957 _ = journal.sync().await.unwrap();
1958 let journal = TestJournal::<F>::init_at_most(
1959 context.child("cap_auth"),
1960 mc.clone(),
1961 jc.clone(),
1962 cap,
1963 |_| true,
1964 ForwardFold,
1965 )
1966 .await
1967 .unwrap();
1968 let size = cap.min(8);
1969 assert_eq!(*journal.size(), size);
1970 assert_eq!(journal_root(&journal), roots[size as usize]);
1971 drop(journal);
1972 let journal =
1973 TestJournal::<F>::new(context.child("restart_auth"), mc, jc, |_| true, ForwardFold)
1974 .await
1975 .unwrap();
1976 assert_eq!(*journal.size(), size);
1977 assert_eq!(journal_root(&journal), roots[size as usize]);
1978 }
1979 }
1980
1981 #[test_traced("INFO")]
1982 fn test_initialization_selection_mmr() {
1983 let executor = deterministic::Runner::default();
1984 executor.start(test_initialization_selection_inner::<mmr::Family>);
1985 }
1986
1987 #[test_traced("INFO")]
1988 fn test_initialization_selection_mmb() {
1989 let executor = deterministic::Runner::default();
1990 executor.start(test_initialization_selection_inner::<mmb::Family>);
1991 }
1992
1993 fn init_at_most_equal_length_branch_crash_inner<F: Family + PartialEq>() {
1997 const A: u64 = 4;
1998
1999 fn is_commit<F: Family>(op: &TestOp<F>) -> bool {
2000 op.is_commit()
2001 }
2002
2003 fn journal_cfg<F: Family + PartialEq>(suffix: &str) -> JConfig {
2006 let page = NonZeroU16::new(<TestOp<F> as FixedSize>::SIZE as u16).unwrap();
2007 JConfig {
2008 partition: format!("journal-{suffix}"),
2009 items_per_blob: NZU64!(1000),
2010 write_buffer: NZUsize!(1),
2011 replay_buffer: NZUsize!(1024),
2012 page_cache: CacheRef::new(page, PAGE_CACHE_SIZE),
2013 }
2014 }
2015
2016 fn merkle_cfg(suffix: &str) -> MerkleConfig<Sequential> {
2018 let page = NonZeroU16::new(Digest::SIZE as u16).unwrap();
2019 MerkleConfig {
2020 journal_partition: format!("mmr-journal-{suffix}"),
2021 metadata_partition: format!("mmr-metadata-{suffix}"),
2022 items_per_blob: NZU64!(1000),
2023 write_buffer: NZUsize!(1024),
2024 replay_buffer: NZUsize!(1024),
2025 strategy: Sequential,
2026 page_cache: CacheRef::new(page, PAGE_CACHE_SIZE),
2027 }
2028 }
2029
2030 fn branch<F: Family + PartialEq>(first: u8, len: u64) -> Vec<TestOp<F>> {
2032 let mut ops: Vec<TestOp<F>> = (0..len - 1)
2033 .map(|i| create_operation::<F>(first + i as u8))
2034 .collect();
2035 ops.push(TestOp::<F>::CommitFloor(None, Location::new(0)));
2036 ops
2037 }
2038
2039 for len in [3u64, 5] {
2042 let suffix = format!("equal-length-branch-{len}");
2043 let crash_suffix = suffix.clone();
2044 let (roots, checkpoint) =
2045 deterministic::Runner::default().start_and_recover(move |context| async move {
2046 let mc = merkle_cfg(&crash_suffix);
2047 let jc = journal_cfg::<F>(&crash_suffix);
2048 let mut journal = TestJournal::<F>::new(
2049 context.child("create"),
2050 mc.clone(),
2051 jc.clone(),
2052 is_commit::<F>,
2053 ForwardFold,
2054 )
2055 .await
2056 .unwrap();
2057 for op in branch::<F>(0, A) {
2058 (journal, _) = journal.append(&op).await.unwrap();
2059 }
2060 let mut journal = journal.sync().await.unwrap();
2061 let root_a = journal_root(&journal);
2062 for op in branch::<F>(50, len) {
2063 (journal, _) = journal.append(&op).await.unwrap();
2064 }
2065 let journal = journal.sync().await.unwrap();
2066 assert_eq!(*journal.size(), A + len);
2067 drop(journal);
2068
2069 *context.storage_fault_config().write() = deterministic::FaultConfig {
2071 write_rate: Some(deterministic::WriteConfig {
2072 failure_rate: probability!(0.0),
2073 retention_rate: probability!(1.0),
2074 mode: deterministic::PartialWriteMode::Prefix,
2075 }),
2076 resize_rate: Some(deterministic::ResizeConfig {
2077 failure_rate: probability!(0.0),
2078 partial_rate: probability!(0.0),
2079 }),
2080 ..Default::default()
2081 };
2082 let mut journal = TestJournal::<F>::init_at_most(
2083 context.child("cap"),
2084 mc,
2085 jc,
2086 A,
2087 is_commit::<F>,
2088 ForwardFold,
2089 )
2090 .await
2091 .unwrap();
2092 assert_eq!(*journal.size(), A);
2093 assert_eq!(journal_root(&journal), root_a);
2094 let mut roots = vec![root_a];
2095 for op in branch::<F>(100, len) {
2096 (journal, _) = journal.append(&op).await.unwrap();
2097 roots.push(journal_root(&journal));
2098 }
2099 drop(journal);
2100 roots
2101 });
2102
2103 deterministic::Runner::from(checkpoint).start(move |context| async move {
2106 *context.storage_fault_config().write() = deterministic::FaultConfig::default();
2107 let journal = TestJournal::<F>::new(
2108 context.child("reopen"),
2109 merkle_cfg(&suffix),
2110 journal_cfg::<F>(&suffix),
2111 is_commit::<F>,
2112 ForwardFold,
2113 )
2114 .await
2115 .unwrap();
2116 let size = *journal.size();
2117 assert!(
2118 (A..=A + len).contains(&size),
2119 "recovered size {size} is not a prefix of the new branch"
2120 );
2121 let expected = branch::<F>(100, len);
2122 for pos in A..size {
2123 assert_eq!(
2124 journal.read(pos).await.unwrap(),
2125 expected[(pos - A) as usize],
2126 "operation at retained position {pos} was never written there"
2127 );
2128 }
2129
2130 let root = journal_root(&journal);
2132 assert_eq!(
2133 root,
2134 roots[(size - A) as usize],
2135 "root does not match the recovered operations"
2136 );
2137 let (proof, ops) = journal
2138 .proof(Location::new(0), NonZeroU64::new(size).unwrap(), 0)
2139 .await
2140 .unwrap();
2141 assert_eq!(ops.len() as u64, size);
2142 assert!(verify_proof(
2143 &proof,
2144 &ops,
2145 Location::new(0),
2146 &root,
2147 &StandardHasher::new(ForwardFold)
2148 ));
2149 journal.destroy().await.unwrap();
2150 });
2151 }
2152 }
2153
2154 #[test_traced("INFO")]
2155 fn test_init_at_most_equal_length_branch_crash_mmr() {
2156 init_at_most_equal_length_branch_crash_inner::<mmr::Family>();
2157 }
2158
2159 #[test_traced("INFO")]
2160 fn test_init_at_most_equal_length_branch_crash_mmb() {
2161 init_at_most_equal_length_branch_crash_inner::<mmb::Family>();
2162 }
2163
2164 async fn test_apply_op_and_read_operations_inner<F: Family + PartialEq>(context: Context) {
2167 let mut journal = create_empty_journal::<F>(context, "apply_op").await;
2168
2169 assert_eq!(journal.size(), 0);
2170
2171 let expected_ops: Vec<_> = (0..50).map(|i| create_operation::<F>(i as u8)).collect();
2173 for (i, op) in expected_ops.iter().enumerate() {
2174 let loc;
2175 (journal, loc) = journal.append(op).await.unwrap();
2176 assert_eq!(loc, Location::<F>::new(i as u64));
2177 assert_eq!(journal.size(), (i + 1) as u64);
2178 }
2179
2180 assert_eq!(journal.size(), 50);
2181
2182 journal = journal.sync().await.unwrap();
2184 for (i, expected_op) in expected_ops.iter().enumerate() {
2185 let read_op = journal.read(*Location::<F>::new(i as u64)).await.unwrap();
2186 assert_eq!(read_op, *expected_op);
2187 }
2188 }
2189
2190 #[test_traced("INFO")]
2191 fn test_apply_op_and_read_operations_mmr() {
2192 let executor = deterministic::Runner::default();
2193 executor.start(test_apply_op_and_read_operations_inner::<mmr::Family>);
2194 }
2195
2196 #[test_traced("INFO")]
2197 fn test_apply_op_and_read_operations_mmb() {
2198 let executor = deterministic::Runner::default();
2199 executor.start(test_apply_op_and_read_operations_inner::<mmb::Family>);
2200 }
2201
2202 async fn test_read_operations_at_various_positions_inner<F: Family + PartialEq>(
2204 context: Context,
2205 ) {
2206 let journal = create_journal_with_ops::<F>(context, "read", 50).await;
2207
2208 let first_op = journal.read(*Location::<F>::new(0)).await.unwrap();
2210 assert_eq!(first_op, create_operation::<F>(0));
2211
2212 let middle_op = journal.read(*Location::<F>::new(25)).await.unwrap();
2214 assert_eq!(middle_op, create_operation::<F>(25));
2215
2216 let last_op = journal.read(*Location::<F>::new(49)).await.unwrap();
2218 assert_eq!(last_op, create_operation::<F>(49));
2219
2220 for i in 0..50 {
2222 let op = journal.read(*Location::<F>::new(i)).await.unwrap();
2223 assert_eq!(op, create_operation::<F>(i as u8));
2224 }
2225 }
2226
2227 #[test_traced("INFO")]
2228 fn test_read_operations_at_various_positions_mmr() {
2229 let executor = deterministic::Runner::default();
2230 executor.start(|context| {
2231 test_read_operations_at_various_positions_inner::<mmr::Family>(context)
2232 });
2233 }
2234
2235 #[test_traced("INFO")]
2236 fn test_read_operations_at_various_positions_mmb() {
2237 let executor = deterministic::Runner::default();
2238 executor.start(|context| {
2239 test_read_operations_at_various_positions_inner::<mmb::Family>(context)
2240 });
2241 }
2242
2243 async fn test_read_pruned_operation_returns_error_inner<F: Family + PartialEq>(
2245 context: Context,
2246 ) {
2247 let mut journal = create_journal_with_ops::<F>(context, "read_pruned", 100).await;
2248
2249 (journal, _) = journal
2251 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2252 .await
2253 .unwrap();
2254 journal = journal.sync().await.unwrap();
2255 let pruned_boundary;
2256 (journal, pruned_boundary) = journal.prune(Location::<F>::new(50)).await.unwrap();
2257
2258 let read_loc = Location::<F>::new(0);
2260 if read_loc < pruned_boundary {
2261 let result = journal.read(*read_loc).await;
2262 assert!(matches!(result, Err(crate::journal::Error::ItemPruned(_))));
2263 }
2264 }
2265
2266 #[test_traced("INFO")]
2267 fn test_read_pruned_operation_returns_error_mmr() {
2268 let executor = deterministic::Runner::default();
2269 executor.start(|context| {
2270 test_read_pruned_operation_returns_error_inner::<mmr::Family>(context)
2271 });
2272 }
2273
2274 #[test_traced("INFO")]
2275 fn test_read_pruned_operation_returns_error_mmb() {
2276 let executor = deterministic::Runner::default();
2277 executor.start(|context| {
2278 test_read_pruned_operation_returns_error_inner::<mmb::Family>(context)
2279 });
2280 }
2281
2282 async fn test_read_out_of_range_returns_error_inner<F: Family + PartialEq>(context: Context) {
2284 let journal = create_journal_with_ops::<F>(context, "read_oob", 3).await;
2285
2286 let result = journal.read(*Location::<F>::new(10)).await;
2288 assert!(matches!(
2289 result,
2290 Err(crate::journal::Error::ItemOutOfRange(_))
2291 ));
2292 }
2293
2294 #[test_traced("INFO")]
2295 fn test_read_out_of_range_returns_error_mmr() {
2296 let executor = deterministic::Runner::default();
2297 executor.start(test_read_out_of_range_returns_error_inner::<mmr::Family>);
2298 }
2299
2300 #[test_traced("INFO")]
2301 fn test_read_out_of_range_returns_error_mmb() {
2302 let executor = deterministic::Runner::default();
2303 executor.start(test_read_out_of_range_returns_error_inner::<mmb::Family>);
2304 }
2305
2306 async fn test_read_all_operations_back_correctly_inner<F: Family + PartialEq>(
2308 context: Context,
2309 ) {
2310 let journal = create_journal_with_ops::<F>(context, "read_all", 50).await;
2311
2312 assert_eq!(journal.size(), 50);
2313
2314 for i in 0..50 {
2316 let op = journal.read(*Location::<F>::new(i)).await.unwrap();
2317 assert_eq!(op, create_operation::<F>(i as u8));
2318 }
2319 }
2320
2321 #[test_traced("INFO")]
2322 fn test_read_all_operations_back_correctly_mmr() {
2323 let executor = deterministic::Runner::default();
2324 executor.start(test_read_all_operations_back_correctly_inner::<mmr::Family>);
2325 }
2326
2327 #[test_traced("INFO")]
2328 fn test_read_all_operations_back_correctly_mmb() {
2329 let executor = deterministic::Runner::default();
2330 executor.start(test_read_all_operations_back_correctly_inner::<mmb::Family>);
2331 }
2332
2333 async fn test_sync_inner<F: Family + PartialEq>(context: Context) {
2335 let mut journal = create_empty_journal::<F>(context.child("first"), "close_pending").await;
2336
2337 let expected_ops: Vec<_> = (0..20).map(|i| create_operation::<F>(i as u8)).collect();
2339 for (i, op) in expected_ops.iter().enumerate() {
2340 let loc;
2341 (journal, loc) = journal.append(op).await.unwrap();
2342 assert_eq!(loc, Location::<F>::new(i as u64),);
2343 }
2344
2345 let commit_loc;
2347 (journal, commit_loc) = journal
2348 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(0)))
2349 .await
2350 .unwrap();
2351 assert_eq!(
2352 commit_loc,
2353 Location::<F>::new(20),
2354 "commit should be at location 20"
2355 );
2356 journal.sync().await.unwrap();
2357
2358 let journal = create_empty_journal::<F>(context.child("second"), "close_pending").await;
2360 assert_eq!(journal.size(), 21);
2361
2362 for (i, expected_op) in expected_ops.iter().enumerate() {
2364 let read_op = journal.read(*Location::<F>::new(i as u64)).await.unwrap();
2365 assert_eq!(read_op, *expected_op);
2366 }
2367 }
2368
2369 #[test_traced("INFO")]
2370 fn test_sync_mmr() {
2371 let executor = deterministic::Runner::default();
2372 executor.start(test_sync_inner::<mmr::Family>);
2373 }
2374
2375 #[test_traced("INFO")]
2376 fn test_sync_mmb() {
2377 let executor = deterministic::Runner::default();
2378 executor.start(test_sync_inner::<mmb::Family>);
2379 }
2380
2381 async fn test_start_sync_durability_inner<F: Family + PartialEq>(context: Context) {
2384 let mut journal = create_empty_journal::<F>(context.child("first"), "start_sync").await;
2385 let expected_ops: Vec<_> = (0..5).map(|i| create_operation::<F>(i as u8)).collect();
2386 for op in expected_ops.iter() {
2387 (journal, _) = journal.append(op).await.unwrap();
2388 }
2389 (journal, _) = journal
2390 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(0)))
2391 .await
2392 .unwrap();
2393
2394 let handle;
2395 (journal, handle) = journal.start_sync().await.unwrap();
2396 handle.await.unwrap();
2397 let root = journal_root(&journal);
2398 drop(journal);
2399
2400 let journal = create_empty_journal::<F>(context.child("second"), "start_sync").await;
2401 assert_eq!(journal.size(), 6);
2402 assert_eq!(journal_root(&journal), root);
2403 for (i, expected_op) in expected_ops.iter().enumerate() {
2404 let read_op = journal.read(*Location::<F>::new(i as u64)).await.unwrap();
2405 assert_eq!(read_op, *expected_op);
2406 }
2407 }
2408
2409 #[test_traced]
2412 fn test_reopen_persists_merkle_watermark() {
2413 deterministic::Runner::default().start(|context| async move {
2414 let pending = PendingSyncs::default();
2416 let open = open_delayed_journal(&context, "first", "watermark", &pending);
2417 let mut journal = drive_pending_syncs(&pending, open).await.unwrap();
2418 for i in 0..5u8 {
2419 (journal, _) = journal
2420 .append(&create_operation::<mmr::Family>(i))
2421 .await
2422 .unwrap();
2423 }
2424 (journal, _) = journal
2425 .append(&TestOp::<mmr::Family>::CommitFloor(None, Location::new(0)))
2426 .await
2427 .unwrap();
2428 let handle;
2429 (journal, handle) = journal.start_sync().await.unwrap();
2430 drive_pending_syncs(&pending, handle).await.unwrap();
2431 let size = *journal.merkle.size();
2432 drop(journal);
2433
2434 let before = ContiguousJournal::<_, Digest>::persisted_watermark(
2436 context.child("p0"),
2437 "mmr-journal-watermark",
2438 )
2439 .await
2440 .unwrap();
2441 assert_eq!(before, Some(0));
2442
2443 let journal =
2445 create_empty_journal::<mmr::Family>(context.child("second"), "watermark").await;
2446 assert_eq!(*journal.merkle.size(), size);
2447 drop(journal);
2448 let after = ContiguousJournal::<_, Digest>::persisted_watermark(
2449 context.child("p1"),
2450 "mmr-journal-watermark",
2451 )
2452 .await
2453 .unwrap();
2454 assert_eq!(after, Some(size));
2455 });
2456 }
2457
2458 #[test_traced("INFO")]
2459 fn test_start_sync_durability_mmr() {
2460 let executor = deterministic::Runner::default();
2461 executor.start(test_start_sync_durability_inner::<mmr::Family>);
2462 }
2463
2464 #[test_traced("INFO")]
2465 fn test_start_sync_durability_mmb() {
2466 let executor = deterministic::Runner::default();
2467 executor.start(test_start_sync_durability_inner::<mmb::Family>);
2468 }
2469
2470 type DelayedCtx = DelayedSyncContext<deterministic::Context>;
2472
2473 type DelayedTestJournal<F> =
2475 Journal<F, DelayedCtx, ContiguousJournal<DelayedCtx, TestOp<F>>, Sha256, Sequential>;
2476
2477 fn open_delayed_journal(
2482 context: &Context,
2483 label: &'static str,
2484 suffix: &str,
2485 pending: &PendingSyncs,
2486 ) -> impl Future<Output = Result<DelayedTestJournal<mmr::Family>, Error<mmr::Family>>> {
2487 DelayedTestJournal::<mmr::Family>::new(
2488 DelayedCtx {
2489 inner: context.child(label),
2490 pending: pending.clone(),
2491 },
2492 merkle_config(suffix),
2493 journal_config(suffix),
2494 |op: &TestOp<mmr::Family>| op.is_commit(),
2495 ForwardFold,
2496 )
2497 }
2498
2499 #[test_traced("INFO")]
2501 fn test_start_sync_overlaps_work() {
2502 let executor = deterministic::Runner::default();
2503 executor.start(|context| async move {
2504 let pending = PendingSyncs::default();
2505 let open = open_delayed_journal(&context, "first", "start_sync_overlap", &pending);
2506 let mut journal = drive_pending_syncs(&pending, open).await.unwrap();
2507 for i in 0..4 {
2508 (journal, _) = journal
2509 .append(&create_operation::<mmr::Family>(i))
2510 .await
2511 .unwrap();
2512 }
2513
2514 let starts_before = pending.starts();
2515 let entered_before = pending.entered();
2516 let completions_before = pending.completions();
2517 let handle;
2518 (journal, handle) = journal.start_sync().await.unwrap();
2519 assert!(pending.starts() > starts_before);
2520 assert_eq!(pending.completions(), completions_before);
2521
2522 let waiter = context
2524 .child("await_sync")
2525 .spawn(|_| async move { handle.await.unwrap() });
2526 while pending.entered() == entered_before {
2527 reschedule().await;
2528 }
2529
2530 (journal, _) = journal
2532 .append(&create_operation::<mmr::Family>(4))
2533 .await
2534 .unwrap();
2535 let read_op = journal.read(0).await.unwrap();
2536 assert_eq!(read_op, create_operation::<mmr::Family>(0));
2537 assert_eq!(
2538 pending.completions(),
2539 completions_before,
2540 "the journal made progress while the sync was still in flight"
2541 );
2542
2543 pending.unblock();
2544 waiter.await.unwrap();
2545
2546 (journal, _) = journal
2548 .append(&TestOp::<mmr::Family>::CommitFloor(None, Location::new(0)))
2549 .await
2550 .unwrap();
2551 let handle;
2552 (journal, handle) = journal.start_sync().await.unwrap();
2553 handle.await.unwrap();
2554 let root = journal.root(0).unwrap();
2555 drop(journal);
2556
2557 let journal = open_delayed_journal(&context, "second", "start_sync_overlap", &pending)
2558 .await
2559 .unwrap();
2560 assert_eq!(journal.size(), 6);
2561 assert_eq!(journal.root(0).unwrap(), root);
2562 });
2563 }
2564
2565 #[test_traced("INFO")]
2568 fn test_start_sync_failure_propagates() {
2569 let executor = deterministic::Runner::default();
2570 executor.start(|context| async move {
2571 let pending = PendingSyncs::default();
2573 pending.unblock();
2574 let mut journal = open_delayed_journal(&context, "first", "start_sync_fail", &pending)
2575 .await
2576 .unwrap();
2577 for i in 0..4 {
2578 (journal, _) = journal
2579 .append(&create_operation::<mmr::Family>(i))
2580 .await
2581 .unwrap();
2582 }
2583
2584 pending.arm_fail();
2586
2587 let handle;
2588 (journal, handle) = journal.start_sync().await.unwrap();
2589 assert!(
2590 handle.await.is_err(),
2591 "the sync handle surfaces the failure"
2592 );
2593 let starts_before = pending.starts();
2594 assert!(
2596 matches!(
2597 journal.commit().await,
2598 Err(Error::Journal(JournalError::Runtime(_)))
2599 ),
2600 "the next durability op surfaces the failed in-flight sync"
2601 );
2602 assert_eq!(
2603 pending.starts(),
2604 starts_before,
2605 "the surfaced error is the retained failure, not a fresh sync's"
2606 );
2607 });
2608 }
2609
2610 #[test_traced("INFO")]
2613 fn test_start_sync_merkle_failure_fails_handle() {
2614 let executor = deterministic::Runner::default();
2615 executor.start(|context| async move {
2616 let pending = PendingSyncs::default();
2617 let open = open_delayed_journal(&context, "first", "merkle_fail", &pending);
2618 let mut journal = drive_pending_syncs(&pending, open).await.unwrap();
2619 for i in 0..4 {
2620 (journal, _) = journal
2621 .append(&create_operation::<mmr::Family>(i))
2622 .await
2623 .unwrap();
2624 }
2625
2626 let handle;
2629 (journal, handle) = journal.start_sync().await.unwrap();
2630 drive_pending_syncs(&pending, handle).await.unwrap();
2631 for i in 4..6 {
2632 (journal, _) = journal
2633 .append(&create_operation::<mmr::Family>(i))
2634 .await
2635 .unwrap();
2636 }
2637 journal = drive_pending_syncs(&pending, journal.commit())
2638 .await
2639 .unwrap();
2640
2641 let handle;
2644 (journal, handle) = journal.start_sync().await.unwrap();
2645 let ops_watermark = next_pending_sync(&pending);
2646 ops_watermark.release.send(Ok(())).unwrap();
2647 fail_pending_syncs(&pending);
2648 assert!(
2649 handle.await.is_err(),
2650 "a merkle-only failure surfaces on the joined handle"
2651 );
2652
2653 assert!(drive_pending_syncs(&pending, journal.sync()).await.is_err());
2655 });
2656 }
2657
2658 async fn test_prune_empty_journal_inner<F: Family + PartialEq>(context: Context) {
2660 let journal = create_empty_journal::<F>(context, "prune_empty").await;
2661
2662 let (_, boundary) = journal.prune(Location::<F>::new(0)).await.unwrap();
2663
2664 assert_eq!(boundary, Location::<F>::new(0));
2665 }
2666
2667 #[test_traced("INFO")]
2668 fn test_prune_empty_journal_mmr() {
2669 let executor = deterministic::Runner::default();
2670 executor.start(test_prune_empty_journal_inner::<mmr::Family>);
2671 }
2672
2673 #[test_traced("INFO")]
2674 fn test_prune_empty_journal_mmb() {
2675 let executor = deterministic::Runner::default();
2676 executor.start(test_prune_empty_journal_inner::<mmb::Family>);
2677 }
2678
2679 async fn test_prune_to_location_inner<F: Family + PartialEq>(context: Context) {
2681 let mut journal = create_journal_with_ops::<F>(context, "prune_to", 100).await;
2682
2683 (journal, _) = journal
2685 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2686 .await
2687 .unwrap();
2688 journal = journal.sync().await.unwrap();
2689
2690 let (_, boundary) = journal.prune(Location::<F>::new(50)).await.unwrap();
2691
2692 assert!(boundary <= Location::<F>::new(50));
2694 }
2695
2696 #[test_traced("INFO")]
2697 fn test_prune_to_location_mmr() {
2698 let executor = deterministic::Runner::default();
2699 executor.start(test_prune_to_location_inner::<mmr::Family>);
2700 }
2701
2702 #[test_traced("INFO")]
2703 fn test_prune_to_location_mmb() {
2704 let executor = deterministic::Runner::default();
2705 executor.start(test_prune_to_location_inner::<mmb::Family>);
2706 }
2707
2708 async fn test_prune_returns_actual_boundary_inner<F: Family + PartialEq>(context: Context) {
2710 let mut journal = create_journal_with_ops::<F>(context, "prune_boundary", 100).await;
2711
2712 (journal, _) = journal
2713 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2714 .await
2715 .unwrap();
2716 journal = journal.sync().await.unwrap();
2717
2718 let requested = Location::<F>::new(50);
2719 let (journal, actual) = journal.prune(requested).await.unwrap();
2720
2721 let bounds = journal.bounds();
2723 assert!(!bounds.is_empty());
2724 assert_eq!(actual, bounds.start);
2725
2726 assert!(actual <= requested);
2728 }
2729
2730 #[test_traced("INFO")]
2731 fn test_prune_returns_actual_boundary_mmr() {
2732 let executor = deterministic::Runner::default();
2733 executor.start(test_prune_returns_actual_boundary_inner::<mmr::Family>);
2734 }
2735
2736 #[test_traced("INFO")]
2737 fn test_prune_returns_actual_boundary_mmb() {
2738 let executor = deterministic::Runner::default();
2739 executor.start(test_prune_returns_actual_boundary_inner::<mmb::Family>);
2740 }
2741
2742 async fn test_mutable_prune_updates_merkle_boundary_inner<F: Family + PartialEq>(
2744 context: Context,
2745 ) {
2746 let mut journal = create_journal_with_ops::<F>(context, "trait_prune", 100).await;
2747
2748 (journal, _) = journal
2749 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2750 .await
2751 .unwrap();
2752 journal = journal.sync().await.unwrap();
2753
2754 let (journal, pruned) = <TestJournal<F> as Mutable>::prune(journal, 50)
2755 .await
2756 .unwrap();
2757 assert!(pruned);
2758
2759 let item_boundary = journal.bounds().start;
2760 let merkle_boundary = journal.merkle.bounds().start;
2761 assert_eq!(Location::<F>::new(item_boundary), merkle_boundary);
2762 assert!(merkle_boundary > Location::<F>::new(0));
2763
2764 let (journal, pruned) = <TestJournal<F> as Mutable>::prune(journal, 50)
2765 .await
2766 .unwrap();
2767 assert!(!pruned);
2768 assert_eq!(journal.bounds().start, item_boundary);
2769 assert_eq!(journal.merkle.bounds().start, merkle_boundary);
2770 }
2771
2772 #[test_traced("INFO")]
2773 fn test_mutable_prune_updates_merkle_boundary_mmr() {
2774 let executor = deterministic::Runner::default();
2775 executor.start(test_mutable_prune_updates_merkle_boundary_inner::<mmr::Family>);
2776 }
2777
2778 #[test_traced("INFO")]
2779 fn test_mutable_prune_updates_merkle_boundary_mmb() {
2780 let executor = deterministic::Runner::default();
2781 executor.start(test_mutable_prune_updates_merkle_boundary_inner::<mmb::Family>);
2782 }
2783
2784 async fn test_prune_preserves_operation_count_inner<F: Family + PartialEq>(context: Context) {
2786 let mut journal = create_journal_with_ops::<F>(context, "prune_count", 100).await;
2787
2788 (journal, _) = journal
2789 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2790 .await
2791 .unwrap();
2792 journal = journal.sync().await.unwrap();
2793
2794 let count_before = journal.size();
2795 let (journal, _) = journal.prune(Location::<F>::new(50)).await.unwrap();
2796 let count_after = journal.size();
2797
2798 assert_eq!(count_before, count_after);
2799 }
2800
2801 #[test_traced("INFO")]
2802 fn test_prune_preserves_operation_count_mmr() {
2803 let executor = deterministic::Runner::default();
2804 executor.start(test_prune_preserves_operation_count_inner::<mmr::Family>);
2805 }
2806
2807 #[test_traced("INFO")]
2808 fn test_prune_preserves_operation_count_mmb() {
2809 let executor = deterministic::Runner::default();
2810 executor.start(test_prune_preserves_operation_count_inner::<mmb::Family>);
2811 }
2812
2813 async fn test_bounds_empty_and_pruned_inner<F: Family + PartialEq>(context: Context) {
2815 let journal = create_empty_journal::<F>(context.child("empty"), "oldest").await;
2817 assert!(journal.bounds().is_empty());
2818 journal.destroy().await.unwrap();
2819
2820 let journal = create_journal_with_ops::<F>(context.child("no_prune"), "oldest", 100).await;
2822 let bounds = journal.bounds();
2823 assert!(!bounds.is_empty());
2824 assert_eq!(bounds.start, 0);
2825 journal.destroy().await.unwrap();
2826
2827 let mut journal =
2829 create_journal_with_ops::<F>(context.child("pruned"), "oldest", 100).await;
2830 (journal, _) = journal
2831 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2832 .await
2833 .unwrap();
2834 journal = journal.sync().await.unwrap();
2835
2836 let (journal, pruned_boundary) = journal.prune(Location::<F>::new(50)).await.unwrap();
2837
2838 let bounds = journal.bounds();
2840 assert!(!bounds.is_empty());
2841 assert_eq!(bounds.start, pruned_boundary);
2842 assert!(pruned_boundary <= 50);
2844 journal.destroy().await.unwrap();
2845 }
2846
2847 #[test_traced("INFO")]
2848 fn test_bounds_empty_and_pruned_mmr() {
2849 let executor = deterministic::Runner::default();
2850 executor.start(test_bounds_empty_and_pruned_inner::<mmr::Family>);
2851 }
2852
2853 #[test_traced("INFO")]
2854 fn test_bounds_empty_and_pruned_mmb() {
2855 let executor = deterministic::Runner::default();
2856 executor.start(test_bounds_empty_and_pruned_inner::<mmb::Family>);
2857 }
2858
2859 async fn test_bounds_start_after_prune_inner<F: Family + PartialEq>(context: Context) {
2861 let journal = create_empty_journal::<F>(context.child("empty"), "boundary").await;
2863 assert_eq!(journal.bounds().start, 0);
2864
2865 drop(journal);
2867 let journal =
2868 create_journal_with_ops::<F>(context.child("no_prune"), "boundary", 100).await;
2869 assert_eq!(journal.bounds().start, 0);
2870
2871 drop(journal);
2873 let mut journal =
2874 create_journal_with_ops::<F>(context.child("pruned"), "boundary", 100).await;
2875 (journal, _) = journal
2876 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(50)))
2877 .await
2878 .unwrap();
2879 journal = journal.sync().await.unwrap();
2880
2881 let (journal, pruned_boundary) = journal.prune(Location::<F>::new(50)).await.unwrap();
2882
2883 assert_eq!(journal.bounds().start, pruned_boundary);
2884 }
2885
2886 #[test_traced("INFO")]
2887 fn test_bounds_start_after_prune_mmr() {
2888 let executor = deterministic::Runner::default();
2889 executor.start(test_bounds_start_after_prune_inner::<mmr::Family>);
2890 }
2891
2892 #[test_traced("INFO")]
2893 fn test_bounds_start_after_prune_mmb() {
2894 let executor = deterministic::Runner::default();
2895 executor.start(test_bounds_start_after_prune_inner::<mmb::Family>);
2896 }
2897
2898 async fn test_mmr_prunes_to_journal_boundary_inner<F: Family + PartialEq>(context: Context) {
2900 let mut journal = create_journal_with_ops::<F>(context, "mmr_boundary", 50).await;
2901
2902 (journal, _) = journal
2903 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(25)))
2904 .await
2905 .unwrap();
2906 journal = journal.sync().await.unwrap();
2907
2908 let (journal, pruned_boundary) = journal.prune(Location::<F>::new(25)).await.unwrap();
2909
2910 let bounds = journal.bounds();
2912 assert!(!bounds.is_empty());
2913 assert_eq!(pruned_boundary, bounds.start);
2914
2915 assert!(pruned_boundary <= Location::<F>::new(25));
2917
2918 assert_eq!(journal.size(), 51);
2920 }
2921
2922 #[test_traced("INFO")]
2923 fn test_mmr_prunes_to_journal_boundary_mmr() {
2924 let executor = deterministic::Runner::default();
2925 executor.start(test_mmr_prunes_to_journal_boundary_inner::<mmr::Family>);
2926 }
2927
2928 #[test_traced("INFO")]
2929 fn test_mmr_prunes_to_journal_boundary_mmb() {
2930 let executor = deterministic::Runner::default();
2931 executor.start(test_mmr_prunes_to_journal_boundary_inner::<mmb::Family>);
2932 }
2933
2934 async fn test_proof_multiple_operations_inner<F: Family + PartialEq>(context: Context) {
2936 let journal = create_journal_with_ops::<F>(context, "proof_multi", 50).await;
2937
2938 let (proof, ops) = journal
2939 .proof(Location::<F>::new(0), NZU64!(50), 0)
2940 .await
2941 .unwrap();
2942
2943 assert_eq!(ops.len(), 50);
2944 for (i, op) in ops.iter().enumerate() {
2945 assert_eq!(*op, create_operation::<F>(i as u8));
2946 }
2947
2948 let hasher = StandardHasher::new(ForwardFold);
2950 let root = journal_root(&journal);
2951 assert!(verify_proof(
2952 &proof,
2953 &ops,
2954 Location::<F>::new(0),
2955 &root,
2956 &hasher
2957 ));
2958 }
2959
2960 #[test_traced("INFO")]
2961 fn test_proof_multiple_operations_mmr() {
2962 let executor = deterministic::Runner::default();
2963 executor.start(test_proof_multiple_operations_inner::<mmr::Family>);
2964 }
2965
2966 #[test_traced("INFO")]
2967 fn test_proof_multiple_operations_mmb() {
2968 let executor = deterministic::Runner::default();
2969 executor.start(test_proof_multiple_operations_inner::<mmb::Family>);
2970 }
2971
2972 async fn test_historical_proof_limited_by_max_ops_inner<F: Family + PartialEq>(
2974 context: Context,
2975 ) {
2976 let journal = create_journal_with_ops::<F>(context, "proof_limit", 50).await;
2977
2978 let size = journal.size();
2979 let (proof, ops) = journal
2980 .historical_proof(size, Location::<F>::new(0), NZU64!(20), 0)
2981 .await
2982 .unwrap();
2983
2984 assert_eq!(ops.len(), 20);
2986 for (i, op) in ops.iter().enumerate() {
2987 assert_eq!(*op, create_operation::<F>(i as u8));
2988 }
2989
2990 let hasher = StandardHasher::new(ForwardFold);
2992 let root = journal_root(&journal);
2993 assert!(verify_proof(
2994 &proof,
2995 &ops,
2996 Location::<F>::new(0),
2997 &root,
2998 &hasher
2999 ));
3000 }
3001
3002 #[test_traced("INFO")]
3003 fn test_historical_proof_limited_by_max_ops_mmr() {
3004 let executor = deterministic::Runner::default();
3005 executor.start(|context| {
3006 test_historical_proof_limited_by_max_ops_inner::<mmr::Family>(context)
3007 });
3008 }
3009
3010 #[test_traced("INFO")]
3011 fn test_historical_proof_limited_by_max_ops_mmb() {
3012 let executor = deterministic::Runner::default();
3013 executor.start(|context| {
3014 test_historical_proof_limited_by_max_ops_inner::<mmb::Family>(context)
3015 });
3016 }
3017
3018 async fn test_historical_proof_at_end_of_journal_inner<F: Family + PartialEq>(
3020 context: Context,
3021 ) {
3022 let journal = create_journal_with_ops::<F>(context, "proof_end", 50).await;
3023
3024 let size = journal.size();
3025 let (proof, ops) = journal
3027 .historical_proof(size, Location::<F>::new(40), NZU64!(20), 0)
3028 .await
3029 .unwrap();
3030
3031 assert_eq!(ops.len(), 10);
3033 for (i, op) in ops.iter().enumerate() {
3034 assert_eq!(*op, create_operation::<F>((40 + i) as u8));
3035 }
3036
3037 let hasher = StandardHasher::new(ForwardFold);
3039 let root = journal_root(&journal);
3040 assert!(verify_proof(
3041 &proof,
3042 &ops,
3043 Location::<F>::new(40),
3044 &root,
3045 &hasher
3046 ));
3047 }
3048
3049 #[test_traced("INFO")]
3050 fn test_historical_proof_at_end_of_journal_mmr() {
3051 let executor = deterministic::Runner::default();
3052 executor.start(test_historical_proof_at_end_of_journal_inner::<mmr::Family>);
3053 }
3054
3055 #[test_traced("INFO")]
3056 fn test_historical_proof_at_end_of_journal_mmb() {
3057 let executor = deterministic::Runner::default();
3058 executor.start(test_historical_proof_at_end_of_journal_inner::<mmb::Family>);
3059 }
3060
3061 async fn test_historical_proof_out_of_range_returns_error_inner<F: Family + PartialEq>(
3063 context: Context,
3064 ) {
3065 let journal = create_journal_with_ops::<F>(context, "proof_oob", 5).await;
3066
3067 let result = journal
3069 .historical_proof(Location::<F>::new(10), Location::<F>::new(0), NZU64!(1), 0)
3070 .await;
3071
3072 assert!(matches!(
3073 result,
3074 Err(Error::Merkle(merkle::Error::RangeOutOfBounds(_)))
3075 ));
3076 }
3077
3078 #[test_traced("INFO")]
3079 fn test_historical_proof_out_of_range_returns_error_mmr() {
3080 let executor = deterministic::Runner::default();
3081 executor.start(|context| {
3082 test_historical_proof_out_of_range_returns_error_inner::<mmr::Family>(context)
3083 });
3084 }
3085
3086 #[test_traced("INFO")]
3087 fn test_historical_proof_out_of_range_returns_error_mmb() {
3088 let executor = deterministic::Runner::default();
3089 executor.start(|context| {
3090 test_historical_proof_out_of_range_returns_error_inner::<mmb::Family>(context)
3091 });
3092 }
3093
3094 async fn test_historical_proof_start_too_large_returns_error_inner<F: Family + PartialEq>(
3096 context: Context,
3097 ) {
3098 let journal = create_journal_with_ops::<F>(context, "proof_start_oob", 5).await;
3099
3100 let size = journal.size();
3101 let result = journal.historical_proof(size, size, NZU64!(1), 0).await;
3103
3104 assert!(matches!(
3105 result,
3106 Err(Error::Merkle(merkle::Error::RangeOutOfBounds(_)))
3107 ));
3108 }
3109
3110 #[test_traced("INFO")]
3111 fn test_historical_proof_start_too_large_returns_error_mmr() {
3112 let executor = deterministic::Runner::default();
3113 executor.start(|context| {
3114 test_historical_proof_start_too_large_returns_error_inner::<mmr::Family>(context)
3115 });
3116 }
3117
3118 #[test_traced("INFO")]
3119 fn test_historical_proof_start_too_large_returns_error_mmb() {
3120 let executor = deterministic::Runner::default();
3121 executor.start(|context| {
3122 test_historical_proof_start_too_large_returns_error_inner::<mmb::Family>(context)
3123 });
3124 }
3125
3126 async fn test_historical_proof_truly_historical_inner<F: Family + PartialEq>(context: Context) {
3128 let mut journal = create_journal_with_ops::<F>(context, "proof_historical", 50).await;
3130
3131 let hasher = StandardHasher::new(ForwardFold);
3133 let historical_root = journal_root(&journal);
3134 let historical_size = journal.size();
3135
3136 for i in 50..100 {
3138 (journal, _) = journal
3139 .append(&create_operation::<F>(i as u8))
3140 .await
3141 .unwrap();
3142 }
3143 let journal = journal.sync().await.unwrap();
3144
3145 let (proof, ops) = journal
3147 .historical_proof(historical_size, Location::<F>::new(0), NZU64!(50), 0)
3148 .await
3149 .unwrap();
3150
3151 assert_eq!(ops.len(), 50);
3153 for (i, op) in ops.iter().enumerate() {
3154 assert_eq!(*op, create_operation::<F>(i as u8));
3155 }
3156
3157 assert!(verify_proof(
3159 &proof,
3160 &ops,
3161 Location::<F>::new(0),
3162 &historical_root,
3163 &hasher
3164 ));
3165 }
3166
3167 #[test_traced("INFO")]
3168 fn test_historical_proof_truly_historical_mmr() {
3169 let executor = deterministic::Runner::default();
3170 executor.start(test_historical_proof_truly_historical_inner::<mmr::Family>);
3171 }
3172
3173 #[test_traced("INFO")]
3174 fn test_historical_proof_truly_historical_mmb() {
3175 let executor = deterministic::Runner::default();
3176 executor.start(test_historical_proof_truly_historical_inner::<mmb::Family>);
3177 }
3178
3179 async fn test_historical_proof_pruned_location_returns_error_inner<F: Family + PartialEq>(
3181 context: Context,
3182 ) {
3183 let mut journal = create_journal_with_ops::<F>(context, "proof_pruned", 50).await;
3184
3185 (journal, _) = journal
3186 .append(&TestOp::<F>::CommitFloor(None, Location::<F>::new(25)))
3187 .await
3188 .unwrap();
3189 journal = journal.sync().await.unwrap();
3190 let pruned_boundary;
3191 (journal, pruned_boundary) = journal.prune(Location::<F>::new(25)).await.unwrap();
3192
3193 let size = journal.size();
3195 let start_loc = Location::<F>::new(0);
3196 if start_loc < pruned_boundary {
3197 let result = journal
3198 .historical_proof(size, start_loc, NZU64!(1), 0)
3199 .await;
3200
3201 assert!(result.is_err());
3203 }
3204 }
3205
3206 #[test_traced("INFO")]
3207 fn test_historical_proof_pruned_location_returns_error_mmr() {
3208 let executor = deterministic::Runner::default();
3209 executor.start(|context| {
3210 test_historical_proof_pruned_location_returns_error_inner::<mmr::Family>(context)
3211 });
3212 }
3213
3214 #[test_traced("INFO")]
3215 fn test_historical_proof_pruned_location_returns_error_mmb() {
3216 let executor = deterministic::Runner::default();
3217 executor.start(|context| {
3218 test_historical_proof_pruned_location_returns_error_inner::<mmb::Family>(context)
3219 });
3220 }
3221
3222 async fn test_replay_operations_inner<F: Family + PartialEq>(context: Context) {
3224 {
3226 let journal = create_empty_journal::<F>(context.child("empty"), "replay").await;
3227 let stream = replay_items(
3228 &journal,
3229 0..journal.bounds().end,
3230 NZUsize!(10),
3231 ReadOptions::default(),
3232 )
3233 .await
3234 .unwrap();
3235 futures::pin_mut!(stream);
3236 assert!(stream.next().await.is_none());
3237 }
3238
3239 let journal = create_journal_with_ops::<F>(context.child("with_ops"), "replay", 50).await;
3241 let stream = replay_items(
3242 &journal,
3243 0..journal.bounds().end,
3244 NZUsize!(100),
3245 ReadOptions::default(),
3246 )
3247 .await
3248 .unwrap();
3249 futures::pin_mut!(stream);
3250
3251 for i in 0..50 {
3252 let (pos, op) = stream.next().await.unwrap().unwrap();
3253 assert_eq!(pos, i);
3254 assert_eq!(op, create_operation::<F>(i as u8));
3255 }
3256
3257 assert!(stream.next().await.is_none());
3258 }
3259
3260 #[test_traced("INFO")]
3261 fn test_replay_operations_mmr() {
3262 let executor = deterministic::Runner::default();
3263 executor.start(test_replay_operations_inner::<mmr::Family>);
3264 }
3265
3266 #[test_traced("INFO")]
3267 fn test_replay_operations_mmb() {
3268 let executor = deterministic::Runner::default();
3269 executor.start(test_replay_operations_inner::<mmb::Family>);
3270 }
3271
3272 #[test_traced("INFO")]
3273 fn test_replay_propagates_read_options_to_backing_journal() {
3274 let executor = deterministic::Runner::default();
3275 executor.start(|context| async move {
3276 let (context, recordings) = RecordingContext::new(context);
3277 let merkle_cfg = merkle_config("replay-options");
3278 let journal_cfg = journal_config("replay-options");
3279 let page_cache = journal_cfg.page_cache.clone();
3280 let mut journal = RecordingTestJournal::<mmr::Family>::new(
3281 context,
3282 merkle_cfg,
3283 journal_cfg,
3284 |op| op.is_commit(),
3285 ForwardFold,
3286 )
3287 .await
3288 .unwrap();
3289
3290 for i in 0..8 {
3291 let operation = create_operation::<mmr::Family>(i);
3292 (journal, _) = journal.append(&operation).await.unwrap();
3293 }
3294 journal = journal.sync().await.unwrap();
3295
3296 page_cache.clear();
3299 let stream = replay_items(
3300 &journal,
3301 0..journal.bounds().end,
3302 NZUsize!(100),
3303 ReadOptions::DONT_CACHE,
3304 )
3305 .await
3306 .unwrap();
3307 recordings.clear();
3308 futures::pin_mut!(stream);
3309 let (position, operation) = stream.next().await.unwrap().unwrap();
3310 assert_eq!(position, 0);
3311 assert_eq!(operation, create_operation::<mmr::Family>(0));
3312
3313 let reads = recordings.snapshot().reads;
3315 assert!(!reads.is_empty());
3316 assert!(
3317 reads
3318 .iter()
3319 .all(|options| *options == ReadOptions::DONT_CACHE)
3320 );
3321 });
3322 }
3323
3324 async fn test_replay_from_middle_inner<F: Family + PartialEq>(context: Context) {
3326 let journal = create_journal_with_ops::<F>(context, "replay_middle", 50).await;
3327 let stream = replay_items(
3328 &journal,
3329 25..journal.bounds().end,
3330 NZUsize!(100),
3331 ReadOptions::default(),
3332 )
3333 .await
3334 .unwrap();
3335 futures::pin_mut!(stream);
3336
3337 let mut count = 0;
3338 while let Some(result) = stream.next().await {
3339 let (pos, op) = result.unwrap();
3340 assert_eq!(pos, 25 + count);
3341 assert_eq!(op, create_operation::<F>((25 + count) as u8));
3342 count += 1;
3343 }
3344
3345 assert_eq!(count, 25);
3347 }
3348
3349 #[test_traced("INFO")]
3350 fn test_replay_from_middle_mmr() {
3351 let executor = deterministic::Runner::default();
3352 executor.start(test_replay_from_middle_inner::<mmr::Family>);
3353 }
3354
3355 #[test_traced("INFO")]
3356 fn test_replay_from_middle_mmb() {
3357 let executor = deterministic::Runner::default();
3358 executor.start(test_replay_from_middle_inner::<mmb::Family>);
3359 }
3360
3361 async fn test_speculative_batch_inner<F: Family + PartialEq>(context: Context) {
3363 let mut journal = create_journal_with_ops::<F>(context, "speculative_batch", 10).await;
3364 let original_root = journal_root(&journal);
3365
3366 let b1 = journal.new_batch();
3368 let b2 = journal.new_batch();
3369
3370 let op_a = create_operation::<F>(100);
3372 let op_b = create_operation::<F>(200);
3373 let b1 = b1.add(op_a.clone());
3374 let b2 = b2.add(op_b);
3375
3376 let m1 = b1.merkleize(journal.merkle.mem());
3378 let m2 = b2.merkleize(journal.merkle.mem());
3379 assert_ne!(batch_root(&journal, &m1), batch_root(&journal, &m2));
3380 assert_ne!(batch_root(&journal, &m1), original_root);
3381 assert_ne!(batch_root(&journal, &m2), original_root);
3382
3383 assert_eq!(journal_root(&journal), original_root);
3385
3386 let expected_root = batch_root(&journal, &m1);
3388 journal = journal.apply_batch(&m1).await.unwrap();
3389
3390 assert_eq!(journal_root(&journal), expected_root);
3392 assert_eq!(*journal.size(), 11);
3393 }
3394
3395 #[test_traced("INFO")]
3396 fn test_speculative_batch_mmr() {
3397 let executor = deterministic::Runner::default();
3398 executor.start(test_speculative_batch_inner::<mmr::Family>);
3399 }
3400
3401 #[test_traced("INFO")]
3402 fn test_speculative_batch_mmb() {
3403 let executor = deterministic::Runner::default();
3404 executor.start(test_speculative_batch_inner::<mmb::Family>);
3405 }
3406
3407 async fn test_speculative_batch_stacking_inner<F: Family + PartialEq>(context: Context) {
3410 let mut journal = create_journal_with_ops::<F>(context, "batch_stacking", 10).await;
3411
3412 let op_a = create_operation::<F>(100);
3413 let op_b = create_operation::<F>(200);
3414
3415 let (merkleized_a, merkleized_b) = {
3416 let batch_a = journal.new_batch().add(op_a.clone());
3417 let merkleized_a = batch_a.merkleize(journal.merkle.mem());
3418
3419 let batch_b = merkleized_a.new_batch::<Sha256>().add(op_b.clone());
3420 let merkleized_b = batch_b.merkleize(journal.merkle.mem());
3421 (merkleized_a, merkleized_b)
3422 };
3423
3424 let expected_root = batch_root(&journal, &merkleized_b);
3425 journal = journal.apply_batch(&merkleized_b).await.unwrap();
3426 drop(merkleized_a);
3427
3428 assert_eq!(journal_root(&journal), expected_root);
3429 assert_eq!(*journal.size(), 12);
3430
3431 let read_a = journal.read(*Location::<F>::new(10)).await.unwrap();
3433 assert_eq!(read_a, op_a);
3434 let read_b = journal.read(*Location::<F>::new(11)).await.unwrap();
3435 assert_eq!(read_b, op_b);
3436 }
3437
3438 #[test_traced("INFO")]
3439 fn test_speculative_batch_stacking_mmr() {
3440 let executor = deterministic::Runner::default();
3441 executor.start(test_speculative_batch_stacking_inner::<mmr::Family>);
3442 }
3443
3444 #[test_traced("INFO")]
3445 fn test_speculative_batch_stacking_mmb() {
3446 let executor = deterministic::Runner::default();
3447 executor.start(test_speculative_batch_stacking_inner::<mmb::Family>);
3448 }
3449
3450 async fn test_speculative_batch_sequential_inner<F: Family + PartialEq>(context: Context) {
3453 let mut journal = create_journal_with_ops::<F>(context, "batch_sequential", 10).await;
3454
3455 let op_a = create_operation::<F>(100);
3456 let op_b = create_operation::<F>(200);
3457
3458 let batch_a = journal.new_batch().add(op_a.clone());
3460 let merkleized_a = batch_a.merkleize(journal.merkle.mem());
3461 journal = journal.apply_batch(&merkleized_a).await.unwrap();
3462 assert_eq!(*journal.size(), 11);
3463
3464 let batch_b = journal.new_batch().add(op_b.clone());
3466 let merkleized_b = batch_b.merkleize(journal.merkle.mem());
3467 let expected_root = batch_root(&journal, &merkleized_b);
3468 journal = journal.apply_batch(&merkleized_b).await.unwrap();
3469
3470 assert_eq!(journal_root(&journal), expected_root);
3471 assert_eq!(*journal.size(), 12);
3472
3473 let read_a = journal.read(*Location::<F>::new(10)).await.unwrap();
3475 assert_eq!(read_a, op_a);
3476 let read_b = journal.read(*Location::<F>::new(11)).await.unwrap();
3477 assert_eq!(read_b, op_b);
3478 }
3479
3480 #[test_traced("INFO")]
3481 fn test_speculative_batch_sequential_mmr() {
3482 let executor = deterministic::Runner::default();
3483 executor.start(test_speculative_batch_sequential_inner::<mmr::Family>);
3484 }
3485
3486 #[test_traced("INFO")]
3487 fn test_speculative_batch_sequential_mmb() {
3488 let executor = deterministic::Runner::default();
3489 executor.start(test_speculative_batch_sequential_inner::<mmb::Family>);
3490 }
3491
3492 async fn test_stale_batch_sibling_inner<F: Family + PartialEq>(context: Context) {
3493 let mut journal = create_empty_journal::<F>(context.child("open"), "stale-sibling").await;
3494 let op_a = create_operation::<F>(1);
3495 let op_b = create_operation::<F>(2);
3496
3497 let batch_a = journal.new_batch().add(op_a.clone());
3499 let merkleized_a = batch_a.merkleize(journal.merkle.mem());
3500 let batch_b = journal.new_batch().add(op_b);
3501 let merkleized_b = batch_b.merkleize(journal.merkle.mem());
3502
3503 journal = journal.apply_batch(&merkleized_a).await.unwrap();
3506 let commit_op = TestOp::<F>::CommitFloor(None, Location::<F>::new(0));
3507 (journal, _) = journal.append(&commit_op).await.unwrap();
3508 journal = journal.sync().await.unwrap();
3509 let root_a = journal_root(&journal);
3510 let size_a = journal.size();
3511 let (_, ops) = journal
3512 .proof(Location::<F>::new(0), NZU64!(1), 0)
3513 .await
3514 .unwrap();
3515 assert_eq!(ops, vec![op_a.clone()]);
3516
3517 let result = journal.apply_batch(&merkleized_b).await;
3519 assert!(
3520 matches!(
3521 result,
3522 Err(super::Error::Merkle(merkle::Error::StaleBatch { .. }))
3523 ),
3524 "expected StaleBatch, got {result:?}"
3525 );
3526
3527 let journal = create_empty_journal::<F>(context.child("reopen"), "stale-sibling").await;
3529 assert_eq!(journal_root(&journal), root_a);
3530 assert_eq!(journal.size(), size_a);
3531 let (_, ops) = journal
3532 .proof(Location::<F>::new(0), NZU64!(1), 0)
3533 .await
3534 .unwrap();
3535 assert_eq!(ops, vec![op_a]);
3536 journal.destroy().await.unwrap();
3537 }
3538
3539 #[test_traced("INFO")]
3540 fn test_stale_batch_sibling_mmr() {
3541 let executor = deterministic::Runner::default();
3542 executor.start(test_stale_batch_sibling_inner::<mmr::Family>);
3543 }
3544
3545 #[test_traced("INFO")]
3546 fn test_stale_batch_sibling_mmb() {
3547 let executor = deterministic::Runner::default();
3548 executor.start(test_stale_batch_sibling_inner::<mmb::Family>);
3549 }
3550
3551 async fn test_stale_batch_chained_inner<F: Family + PartialEq>(context: Context) {
3552 let mut journal = create_journal_with_ops::<F>(context, "stale-chained", 5).await;
3553
3554 let parent_batch = journal.new_batch().add(create_operation::<F>(10));
3556 let parent = parent_batch.merkleize(journal.merkle.mem());
3557 let batch_a = parent.new_batch::<Sha256>().add(create_operation::<F>(20));
3558 let child_a = batch_a.merkleize(journal.merkle.mem());
3559 let batch_b = parent.new_batch::<Sha256>().add(create_operation::<F>(30));
3560 let child_b = batch_b.merkleize(journal.merkle.mem());
3561
3562 journal = journal.apply_batch(&child_a).await.unwrap();
3564 let result = journal.apply_batch(&child_b).await;
3565 drop(parent);
3566 assert!(
3567 matches!(
3568 result,
3569 Err(super::Error::Merkle(merkle::Error::StaleBatch { .. }))
3570 ),
3571 "expected StaleBatch for sibling, got {result:?}"
3572 );
3573 }
3574
3575 #[test_traced("INFO")]
3576 fn test_stale_batch_chained_mmr() {
3577 let executor = deterministic::Runner::default();
3578 executor.start(test_stale_batch_chained_inner::<mmr::Family>);
3579 }
3580
3581 #[test_traced("INFO")]
3582 fn test_stale_batch_chained_mmb() {
3583 let executor = deterministic::Runner::default();
3584 executor.start(test_stale_batch_chained_inner::<mmb::Family>);
3585 }
3586
3587 async fn test_stale_batch_parent_before_child_inner<F: Family + PartialEq>(context: Context) {
3588 let mut journal = create_empty_journal::<F>(context, "stale-parent-first").await;
3589
3590 let parent_batch = journal.new_batch().add(create_operation::<F>(1));
3592 let parent = parent_batch.merkleize(journal.merkle.mem());
3593 let child_batch = parent.new_batch::<Sha256>().add(create_operation::<F>(2));
3594 let child = child_batch.merkleize(journal.merkle.mem());
3595
3596 let expected_root = batch_root(&journal, &child);
3597
3598 journal = journal.apply_batch(&parent).await.unwrap();
3600 journal = journal.apply_batch(&child).await.unwrap();
3601
3602 assert_eq!(journal_root(&journal), expected_root);
3603 assert_eq!(*journal.size(), 2);
3604 }
3605
3606 async fn test_stale_batch_interior_fork_inner<F: Family + PartialEq>(context: Context) {
3609 let (storage, recordings) = RecordingContext::new(context.child("open"));
3611 let merkle_cfg = merkle_config("stale-interior");
3612 let journal_cfg = journal_config("stale-interior");
3613 let mut journal = RecordingTestJournal::<F>::new(
3614 storage,
3615 merkle_cfg,
3616 journal_cfg,
3617 |op| op.is_commit(),
3618 ForwardFold,
3619 )
3620 .await
3621 .unwrap();
3622 for i in 0..4 {
3623 (journal, _) = journal.append(&create_operation::<F>(i)).await.unwrap();
3624 }
3625 let commit_op = TestOp::<F>::CommitFloor(None, Location::<F>::new(0));
3626
3627 let batch_a = journal
3629 .new_batch()
3630 .add(create_operation::<F>(10))
3631 .add(commit_op.clone());
3632 let a = batch_a.merkleize(journal.merkle.mem());
3633 let batch_b = a.new_batch::<Sha256>().add(create_operation::<F>(20));
3634 let b = batch_b.merkleize(journal.merkle.mem());
3635 let op_sibling = create_operation::<F>(30);
3636 let batch_sibling = journal.new_batch().add(op_sibling.clone()).add(commit_op);
3637 let sibling = batch_sibling.merkleize(journal.merkle.mem());
3638 journal = journal.apply_batch(&sibling).await.unwrap();
3639 journal = journal.sync().await.unwrap();
3640 let root = journal.root(0).unwrap();
3641 let size = journal.size();
3642 recordings.clear();
3643
3644 let result = journal.apply_batch(&b).await;
3647 assert!(
3648 matches!(
3649 result,
3650 Err(super::Error::Merkle(merkle::Error::StaleBatch { .. }))
3651 ),
3652 "applying B must fail with StaleBatch, got {result:?}"
3653 );
3654 assert!(
3655 recordings.snapshot().writes.is_empty(),
3656 "a rejected batch must not write to storage"
3657 );
3658
3659 let journal = create_empty_journal::<F>(context.child("reopen"), "stale-interior").await;
3661 assert_eq!(journal_root(&journal), root);
3662 assert_eq!(journal.size(), size);
3663 let (_, ops) = journal
3664 .proof(Location::<F>::new(4), NZU64!(1), 0)
3665 .await
3666 .unwrap();
3667 assert_eq!(ops, vec![op_sibling]);
3668 journal.destroy().await.unwrap();
3669 }
3670
3671 async fn test_apply_batch_empty_child_after_parent_inner<F: Family + PartialEq>(
3673 context: Context,
3674 ) {
3675 let mut journal = create_journal_with_ops::<F>(context, "empty-child", 4).await;
3676 let parent = journal
3677 .new_batch()
3678 .add(create_operation::<F>(10))
3679 .merkleize(journal.merkle.mem());
3680 let child = parent.new_batch::<Sha256>().merkleize(journal.merkle.mem());
3681 journal = journal.apply_batch(&parent).await.unwrap();
3682 let root = journal_root(&journal);
3683 let size = journal.size();
3684
3685 journal = journal.apply_batch(&child).await.unwrap();
3687 assert_eq!(journal_root(&journal), root);
3688 assert_eq!(journal.size(), size);
3689 journal.destroy().await.unwrap();
3690 }
3691
3692 #[test_traced("INFO")]
3693 fn test_apply_batch_empty_child_after_parent_mmr() {
3694 let executor = deterministic::Runner::default();
3695 executor.start(test_apply_batch_empty_child_after_parent_inner::<mmr::Family>);
3696 }
3697
3698 #[test_traced("INFO")]
3699 fn test_apply_batch_empty_child_after_parent_mmb() {
3700 let executor = deterministic::Runner::default();
3701 executor.start(test_apply_batch_empty_child_after_parent_inner::<mmb::Family>);
3702 }
3703
3704 async fn test_apply_batch_rejects_dropped_journal_ancestor_inner<F: Family + PartialEq>(
3707 context: Context,
3708 ) {
3709 let mut journal =
3710 create_journal_with_ops::<F>(context.child("open"), "dropped-items", 4).await;
3711 let commit_op = TestOp::<F>::CommitFloor(None, Location::<F>::new(0));
3712 let seed = journal
3713 .new_batch()
3714 .add(create_operation::<F>(5))
3715 .add(commit_op.clone())
3716 .merkleize(journal.merkle.mem());
3717 journal = journal.apply_batch(&seed).await.unwrap();
3718 journal = journal.sync().await.unwrap();
3719 let root = journal_root(&journal);
3720 let size = journal.size();
3721
3722 let a = journal
3725 .new_batch()
3726 .add(create_operation::<F>(10))
3727 .merkleize(journal.merkle.mem());
3728 let b = a
3729 .new_batch::<Sha256>()
3730 .add(create_operation::<F>(20))
3731 .merkleize(journal.merkle.mem());
3732 let a_merkle = Arc::clone(&a.inner);
3733 drop(a);
3734 let c = b
3735 .new_batch::<Sha256>()
3736 .add(create_operation::<F>(30))
3737 .add(commit_op)
3738 .merkleize(journal.merkle.mem());
3739
3740 let result = journal.apply_batch(&c).await;
3743 assert!(
3744 matches!(
3745 result,
3746 Err(super::Error::Merkle(merkle::Error::AncestorDropped { .. }))
3747 ),
3748 "applying C must fail with AncestorDropped, got {result:?}"
3749 );
3750 drop(a_merkle);
3751
3752 let journal = create_empty_journal::<F>(context.child("reopen"), "dropped-items").await;
3754 assert_eq!(journal_root(&journal), root);
3755 assert_eq!(journal.size(), size);
3756 journal.destroy().await.unwrap();
3757 }
3758
3759 #[test_traced("INFO")]
3760 fn test_apply_batch_rejects_dropped_journal_ancestor_mmr() {
3761 let executor = deterministic::Runner::default();
3762 executor.start(test_apply_batch_rejects_dropped_journal_ancestor_inner::<mmr::Family>);
3763 }
3764
3765 #[test_traced("INFO")]
3766 fn test_apply_batch_rejects_dropped_journal_ancestor_mmb() {
3767 let executor = deterministic::Runner::default();
3768 executor.start(test_apply_batch_rejects_dropped_journal_ancestor_inner::<mmb::Family>);
3769 }
3770
3771 #[test_traced("INFO")]
3772 fn test_stale_batch_interior_fork_mmr() {
3773 let executor = deterministic::Runner::default();
3774 executor.start(test_stale_batch_interior_fork_inner::<mmr::Family>);
3775 }
3776
3777 #[test_traced("INFO")]
3778 fn test_stale_batch_interior_fork_mmb() {
3779 let executor = deterministic::Runner::default();
3780 executor.start(test_stale_batch_interior_fork_inner::<mmb::Family>);
3781 }
3782
3783 #[test_traced("INFO")]
3784 fn test_stale_batch_parent_before_child_mmr() {
3785 let executor = deterministic::Runner::default();
3786 executor.start(test_stale_batch_parent_before_child_inner::<mmr::Family>);
3787 }
3788
3789 #[test_traced("INFO")]
3790 fn test_stale_batch_parent_before_child_mmb() {
3791 let executor = deterministic::Runner::default();
3792 executor.start(test_stale_batch_parent_before_child_inner::<mmb::Family>);
3793 }
3794
3795 async fn test_stale_batch_child_before_parent_inner<F: Family + PartialEq>(context: Context) {
3796 let mut journal = create_empty_journal::<F>(context, "stale-child-first").await;
3797
3798 let parent_batch = journal.new_batch().add(create_operation::<F>(1));
3800 let parent = parent_batch.merkleize(journal.merkle.mem());
3801 let child_batch = parent.new_batch::<Sha256>().add(create_operation::<F>(2));
3802 let child = child_batch.merkleize(journal.merkle.mem());
3803
3804 journal = journal.apply_batch(&child).await.unwrap();
3806 let result = journal.apply_batch(&parent).await;
3807 assert!(
3808 matches!(
3809 result,
3810 Err(super::Error::Merkle(merkle::Error::StaleBatch { .. }))
3811 ),
3812 "expected StaleBatch for parent after child applied, got {result:?}"
3813 );
3814 }
3815
3816 #[test_traced("INFO")]
3817 fn test_stale_batch_child_before_parent_mmr() {
3818 let executor = deterministic::Runner::default();
3819 executor.start(test_stale_batch_child_before_parent_inner::<mmr::Family>);
3820 }
3821
3822 #[test_traced("INFO")]
3823 fn test_stale_batch_child_before_parent_mmb() {
3824 let executor = deterministic::Runner::default();
3825 executor.start(test_stale_batch_child_before_parent_inner::<mmb::Family>);
3826 }
3827
3828 async fn test_apply_batch_skip_ancestor_items_inner<F: Family + PartialEq>(context: Context) {
3830 let mut journal = create_journal_with_ops::<F>(context, "rp-skip", 3).await;
3831
3832 let parent_batch = journal
3834 .new_batch()
3835 .add(create_operation::<F>(10))
3836 .add(create_operation::<F>(11));
3837 let parent = parent_batch.merkleize(journal.merkle.mem());
3838
3839 let child_batch = parent
3841 .new_batch::<Sha256>()
3842 .add(create_operation::<F>(20))
3843 .add(create_operation::<F>(21))
3844 .add(create_operation::<F>(22));
3845 let child = child_batch.merkleize(journal.merkle.mem());
3846
3847 journal = journal.apply_batch(&parent).await.unwrap();
3849
3850 journal = journal.apply_batch(&child).await.unwrap();
3852
3853 let (_, ops) = journal
3855 .proof(Location::<F>::new(3), NZU64!(5), 0)
3856 .await
3857 .unwrap();
3858 assert_eq!(ops.len(), 5);
3859 }
3860
3861 #[test_traced("INFO")]
3862 fn test_apply_batch_skip_ancestor_items_mmr() {
3863 let executor = deterministic::Runner::default();
3864 executor.start(test_apply_batch_skip_ancestor_items_inner::<mmr::Family>);
3865 }
3866
3867 #[test_traced("INFO")]
3868 fn test_apply_batch_skip_ancestor_items_mmb() {
3869 let executor = deterministic::Runner::default();
3870 executor.start(test_apply_batch_skip_ancestor_items_inner::<mmb::Family>);
3871 }
3872
3873 async fn test_apply_batch_cross_batch_inner<F: Family + PartialEq>(context: Context) {
3875 let mut journal = create_journal_with_ops::<F>(context, "rp-cross", 2).await;
3876
3877 let grandparent_batch = journal
3879 .new_batch()
3880 .add(create_operation::<F>(3))
3881 .add(create_operation::<F>(4))
3882 .add(create_operation::<F>(5));
3883 let grandparent = grandparent_batch.merkleize(journal.merkle.mem());
3884
3885 let parent_batch = grandparent
3887 .new_batch::<Sha256>()
3888 .add(create_operation::<F>(6))
3889 .add(create_operation::<F>(7));
3890 let parent = parent_batch.merkleize(journal.merkle.mem());
3891
3892 let child_batch = parent.new_batch::<Sha256>().add(create_operation::<F>(8));
3894 let child = child_batch.merkleize(journal.merkle.mem());
3895
3896 journal = journal.apply_batch(&grandparent).await.unwrap();
3898
3899 journal = journal.apply_batch(&parent).await.unwrap();
3901
3902 journal = journal.apply_batch(&child).await.unwrap();
3904
3905 assert_eq!(*journal.size(), 8);
3907
3908 let (_, ops) = journal
3910 .proof(Location::<F>::new(2), NZU64!(6), 0)
3911 .await
3912 .unwrap();
3913 for (i, op) in ops.iter().enumerate() {
3914 assert_eq!(*op, create_operation::<F>((i + 3) as u8));
3915 }
3916 }
3917
3918 #[test_traced("INFO")]
3919 fn test_apply_batch_cross_batch_mmr() {
3920 let executor = deterministic::Runner::default();
3921 executor.start(test_apply_batch_cross_batch_inner::<mmr::Family>);
3922 }
3923
3924 #[test_traced("INFO")]
3925 fn test_apply_batch_cross_batch_mmb() {
3926 let executor = deterministic::Runner::default();
3927 executor.start(test_apply_batch_cross_batch_inner::<mmb::Family>);
3928 }
3929
3930 async fn test_merkleize_with_matches_add_inner<F: Family + PartialEq>(context: Context) {
3932 let journal = create_journal_with_ops::<F>(context, "mw-matches", 5).await;
3933
3934 let ops = vec![
3935 create_operation::<F>(10),
3936 create_operation::<F>(11),
3937 create_operation::<F>(12),
3938 ];
3939
3940 let mut batch = journal.new_batch();
3942 for op in &ops {
3943 batch = batch.add(op.clone());
3944 }
3945 let expected = batch.merkleize(journal.merkle.mem());
3946
3947 let batch = journal.new_batch();
3949 let actual = merkleize_with(batch, journal.merkle.mem(), ops);
3950
3951 assert_eq!(
3952 batch_root(&journal, &actual),
3953 batch_root(&journal, &expected)
3954 );
3955 }
3956
3957 #[test_traced("INFO")]
3958 fn test_merkleize_with_matches_add_mmr() {
3959 let executor = deterministic::Runner::default();
3960 executor.start(test_merkleize_with_matches_add_inner::<mmr::Family>);
3961 }
3962
3963 #[test_traced("INFO")]
3964 fn test_merkleize_with_matches_add_mmb() {
3965 let executor = deterministic::Runner::default();
3966 executor.start(test_merkleize_with_matches_add_inner::<mmb::Family>);
3967 }
3968
3969 async fn test_merkleize_with_apply_inner<F: Family + PartialEq>(context: Context) {
3971 let mut journal = create_journal_with_ops::<F>(context, "mw-apply", 5).await;
3972
3973 let ops = vec![create_operation::<F>(10), create_operation::<F>(11)];
3974 let batch = journal.new_batch();
3975 let merkleized = merkleize_with(batch, journal.merkle.mem(), ops.clone());
3976
3977 let expected_root = batch_root(&journal, &merkleized);
3978 journal = journal.apply_batch(&merkleized).await.unwrap();
3979
3980 assert_eq!(journal_root(&journal), expected_root);
3981 assert_eq!(*journal.size(), 7);
3982
3983 assert_eq!(journal.read(5).await.unwrap(), ops[0]);
3984 assert_eq!(journal.read(6).await.unwrap(), ops[1]);
3985 }
3986
3987 #[test_traced("INFO")]
3988 fn test_merkleize_with_apply_mmr() {
3989 let executor = deterministic::Runner::default();
3990 executor.start(test_merkleize_with_apply_inner::<mmr::Family>);
3991 }
3992
3993 #[test_traced("INFO")]
3994 fn test_merkleize_with_apply_mmb() {
3995 let executor = deterministic::Runner::default();
3996 executor.start(test_merkleize_with_apply_inner::<mmb::Family>);
3997 }
3998
3999 async fn test_apply_batch_skips_only_committed_ancestor_items_inner<F: Family + PartialEq>(
4002 context: Context,
4003 ) {
4004 let mut journal = create_empty_journal::<F>(context.child("storage"), "skip-partial").await;
4005
4006 let a_batch = journal.new_batch().add(create_operation::<F>(1));
4008 let a = a_batch.merkleize(journal.merkle.mem());
4009 let b_batch = a.new_batch::<Sha256>().add(create_operation::<F>(2));
4010 let b = b_batch.merkleize(journal.merkle.mem());
4011 let c_batch = b.new_batch::<Sha256>().add(create_operation::<F>(3));
4012 let c = c_batch.merkleize(journal.merkle.mem());
4013
4014 journal = journal.apply_batch(&a).await.unwrap();
4016 journal = journal.apply_batch(&c).await.unwrap();
4017
4018 assert_eq!(*journal.size(), 3);
4020
4021 let mut reference =
4023 create_empty_journal::<F>(context.child("ref"), "skip-partial-ref").await;
4024 for i in 1..=3u8 {
4025 (reference, _) = reference.append(&create_operation::<F>(i)).await.unwrap();
4026 }
4027 assert_eq!(journal_root(&journal), journal_root(&reference));
4028 }
4029
4030 #[test_traced("INFO")]
4031 fn test_apply_batch_skips_only_committed_ancestor_items_mmr() {
4032 let executor = deterministic::Runner::default();
4033 executor.start(test_apply_batch_skips_only_committed_ancestor_items_inner::<mmr::Family>);
4034 }
4035
4036 #[test_traced("INFO")]
4037 fn test_apply_batch_skips_only_committed_ancestor_items_mmb() {
4038 let executor = deterministic::Runner::default();
4039 executor.start(test_apply_batch_skips_only_committed_ancestor_items_inner::<mmb::Family>);
4040 }
4041
4042 async fn test_apply_batch_detects_dropped_uncommitted_ancestor_inner<F: Family + PartialEq>(
4045 context: Context,
4046 ) {
4047 let journal =
4048 create_empty_journal::<F>(context.child("storage"), "dropped-uncommitted").await;
4049
4050 let a_batch = journal.new_batch().add(create_operation::<F>(1));
4051 let a = a_batch.merkleize(journal.merkle.mem());
4052 let b_batch = a.new_batch::<Sha256>().add(create_operation::<F>(2));
4053 let b = b_batch.merkleize(journal.merkle.mem());
4054
4055 drop(a);
4056 let c_batch = b.new_batch::<Sha256>().add(create_operation::<F>(3));
4057 let c = c_batch.merkleize(journal.merkle.mem());
4058 drop(b);
4059
4060 assert_eq!(c.ancestor_items.len(), 1);
4061
4062 let result = journal.apply_batch(&c).await;
4063 assert!(
4064 matches!(
4065 result,
4066 Err(super::Error::Merkle(merkle::Error::AncestorDropped { expected, .. }))
4067 if expected == c.inner.size()
4068 ),
4069 "expected AncestorDropped, got {result:?}"
4070 );
4071 }
4072
4073 #[test_traced("INFO")]
4074 fn test_apply_batch_detects_dropped_uncommitted_ancestor_mmb() {
4075 let executor = deterministic::Runner::default();
4076 executor.start(test_apply_batch_detects_dropped_uncommitted_ancestor_inner::<mmb::Family>);
4077 }
4078
4079 async fn test_apply_batch_after_committed_ancestor_dropped_inner<F: Family + PartialEq>(
4082 context: Context,
4083 ) {
4084 let mut journal =
4085 create_empty_journal::<F>(context.child("storage"), "dropped-committed").await;
4086
4087 let mut a_batch = journal.new_batch();
4088 for i in 0..8u8 {
4089 a_batch = a_batch.add(create_operation::<F>(i));
4090 }
4091 let a = a_batch.merkleize(journal.merkle.mem());
4092 let b_batch = a.new_batch::<Sha256>().add(create_operation::<F>(8));
4093 let b = b_batch.merkleize(journal.merkle.mem());
4094
4095 journal = journal.apply_batch(&a).await.unwrap();
4096 drop(a);
4097
4098 let c_batch = b.new_batch::<Sha256>().add(create_operation::<F>(9));
4099 let c = c_batch.merkleize(journal.merkle.mem());
4100
4101 assert_eq!(c.ancestor_items.len(), 1);
4103 assert_eq!(journal.merkle.mem().validate_batch(&c.inner).unwrap(), 0);
4104
4105 drop(b);
4106 journal = journal.apply_batch(&c).await.unwrap();
4107 assert_eq!(*journal.size(), 10);
4108
4109 let mut reference =
4110 create_empty_journal::<F>(context.child("reference"), "dropped-committed-ref").await;
4111 for i in 0..10u8 {
4112 (reference, _) = reference.append(&create_operation::<F>(i)).await.unwrap();
4113 }
4114 assert_eq!(journal_root(&journal), journal_root(&reference));
4115 }
4116
4117 #[test_traced("INFO")]
4118 fn test_apply_batch_after_committed_ancestor_dropped_mmb() {
4119 let executor = deterministic::Runner::default();
4120 executor.start(test_apply_batch_after_committed_ancestor_dropped_inner::<mmb::Family>);
4121 }
4122
4123 async fn test_merkleize_after_committed_prefix_dropped_inner<F: Family + PartialEq>(
4125 context: Context,
4126 ) {
4127 let mut journal =
4128 create_empty_journal::<F>(context.child("storage"), "committed-prefix").await;
4129
4130 let prefix_items = (0..8u8).map(create_operation::<F>).collect();
4132 let (prefix, _) = journal
4133 .merkleize(journal.new_batch(), prefix_items, 0)
4134 .await
4135 .unwrap();
4136 let pending_items = (8..10u8).map(create_operation::<F>).collect();
4137 let (pending, _) = journal
4138 .merkleize(prefix.new_batch::<Sha256>(), pending_items, 0)
4139 .await
4140 .unwrap();
4141
4142 journal = journal.apply_batch(&prefix).await.unwrap();
4144 drop(prefix);
4145
4146 let child_batch = pending.new_batch::<Sha256>();
4149 drop(pending);
4150 let (child, expected_root) = journal
4151 .merkleize(child_batch, vec![create_operation::<F>(10)], 0)
4152 .await
4153 .unwrap();
4154 journal = journal.apply_batch(&child).await.unwrap();
4155
4156 assert_eq!(journal_root(&journal), expected_root);
4157 assert_eq!(*journal.size(), 11);
4158 }
4159
4160 #[test_traced("INFO")]
4161 fn test_merkleize_after_committed_prefix_dropped_mmr() {
4162 let executor = deterministic::Runner::default();
4163 executor.start(test_merkleize_after_committed_prefix_dropped_inner::<mmr::Family>);
4164 }
4165
4166 #[test_traced("INFO")]
4167 fn test_merkleize_after_committed_prefix_dropped_mmb() {
4168 let executor = deterministic::Runner::default();
4169 executor.start(test_merkleize_after_committed_prefix_dropped_inner::<mmb::Family>);
4170 }
4171
4172 #[test_traced("INFO")]
4174 fn test_merkleize_retains_ancestors_after_cancellation() {
4175 deterministic::Runner::default().start(|context| async move {
4176 let pool = Rayon::new(NZUsize!(2)).unwrap();
4177 let strategy = Explorer::parallel(&pool);
4178 let merkle_cfg = merkle_config_with("cancelled-merkleize", strategy);
4179 let journal_cfg = journal_config("cancelled-merkleize");
4180 type ParallelJournal = Journal<
4181 mmr::Family,
4182 Context,
4183 ContiguousJournal<Context, DropMonitor<TestOp<mmr::Family>>>,
4184 Sha256,
4185 Explorer,
4186 >;
4187 let journal = ParallelJournal::new(
4188 context,
4189 merkle_cfg,
4190 journal_cfg,
4191 |_: &DropMonitor<TestOp<mmr::Family>>| false,
4192 ForwardFold,
4193 )
4194 .await
4195 .unwrap();
4196
4197 let a_items = (0..8u8)
4198 .map(create_operation::<mmr::Family>)
4199 .map(DropMonitor::untracked)
4200 .collect();
4201 let a_batch = journal.new_batch().add_many(a_items);
4202 let a = a_batch.merkleize(journal.merkle.mem());
4203 let b_items = (8..10u8)
4204 .map(create_operation::<mmr::Family>)
4205 .map(DropMonitor::untracked)
4206 .collect();
4207 let b_batch = a.new_batch::<Sha256>().add_many(b_items);
4208 let b = b_batch.merkleize(journal.merkle.mem());
4209
4210 let ancestor = Arc::downgrade(&a.inner);
4211 let c_batch = b.new_batch::<Sha256>();
4212 drop(b);
4213
4214 let release = block_strategy(&pool, 2);
4215 let (item, clean_drop) = DropMonitor::tracked(create_operation::<mmr::Family>(10));
4216 let mut merkleize = Box::pin(journal.merkleize(c_batch, vec![item], 0));
4217 assert!(futures::poll!(merkleize.as_mut()).is_pending());
4218 drop(merkleize);
4219 drop(a);
4220
4221 assert!(ancestor.upgrade().is_some());
4222 drop(release);
4223 assert!(
4224 clean_drop
4225 .recv_timeout(Duration::from_secs(10))
4226 .expect("detached merkleization did not finish"),
4227 "detached merkleization panicked"
4228 );
4229 assert!(ancestor.upgrade().is_none());
4230 });
4231 }
4232
4233 async fn fully_pruned_authenticated_reopens<F: Family + PartialEq>(context: Context) {
4235 let merkle_cfg = merkle_config("fully-pruned-reopen");
4237 let journal_cfg = journal_config("fully-pruned-reopen");
4238 assert_eq!(journal_cfg.items_per_blob.get(), 7);
4239 let mut journal = TestJournal::<F>::new(
4240 context.child("seed"),
4241 merkle_cfg.clone(),
4242 journal_cfg.clone(),
4243 |_| true,
4244 ForwardFold,
4245 )
4246 .await
4247 .unwrap();
4248 for i in 0u8..7 {
4249 let (next, pos) = journal.append(&create_operation::<F>(i)).await.unwrap();
4250 journal = next;
4251 assert_eq!(*pos, u64::from(i));
4252 }
4253 let journal = journal.sync().await.unwrap();
4254 let root = journal.root(0).unwrap();
4255 let (journal, boundary) = journal.prune(Location::new(7)).await.unwrap();
4256 assert_eq!(*boundary, 7);
4257 assert_eq!(journal.bounds(), 7..7);
4258 assert_eq!(journal.root(0).unwrap(), root);
4259 drop(journal);
4260
4261 let journal = TestJournal::<F>::new(
4263 context.child("reopen"),
4264 merkle_cfg,
4265 journal_cfg,
4266 |_| true,
4267 ForwardFold,
4268 )
4269 .await
4270 .unwrap();
4271 assert_eq!(journal.bounds(), 7..7);
4272 assert_eq!(journal.root(0).unwrap(), root);
4273 let (journal, pos) = journal.append(&create_operation::<F>(7)).await.unwrap();
4274 assert_eq!(*pos, 7);
4275 assert_eq!(journal.bounds(), 7..8);
4276 journal.destroy().await.unwrap();
4277 }
4278
4279 #[test]
4280 fn test_fully_pruned_authenticated_reopens_mmr() {
4281 deterministic::Runner::default().start(fully_pruned_authenticated_reopens::<mmr::Family>);
4282 }
4283
4284 #[test]
4285 fn test_fully_pruned_authenticated_reopens_mmb() {
4286 deterministic::Runner::default().start(fully_pruned_authenticated_reopens::<mmb::Family>);
4287 }
4288}