1use crate::{
328 index::Factory as IndexFactory,
329 journal::{
330 authenticated::Inner,
331 contiguous::{fixed::Config as FConfig, variable::Config as VConfig},
332 },
333 merkle::{self, full::Config as MerkleConfig, Location},
334 qmdb::{
335 any::{
336 self,
337 operation::{Operation, Update},
338 Config as AnyConfig,
339 },
340 bitmap::Shared,
341 operation::Committable,
342 },
343 translator::Translator,
344 Context,
345};
346use commonware_codec::{CodecShared, FixedSize};
347use commonware_cryptography::Hasher;
348use commonware_macros::boxed;
349use commonware_parallel::Strategy;
350use commonware_utils::bitmap::Prunable as BitMap;
351use core::num::NonZeroUsize;
352use std::sync::Arc;
353
354pub mod batch;
355pub mod db;
356pub mod grafting;
357
358pub mod ordered;
359pub mod proof;
360pub(crate) mod sync;
361pub mod unordered;
362
363use self::db::Metrics;
364
365#[derive(Clone)]
367pub struct Config<T: Translator, J, S: Strategy> {
368 pub merkle_config: MerkleConfig<S>,
370
371 pub journal_config: J,
373
374 pub grafted_metadata_partition: String,
376
377 pub translator: T,
379
380 pub init_cache_size: Option<NonZeroUsize>,
383}
384
385impl<T: Translator, J, S: Strategy> From<Config<T, J, S>> for AnyConfig<T, J, S> {
386 fn from(cfg: Config<T, J, S>) -> Self {
387 Self {
388 merkle_config: cfg.merkle_config,
389 journal_config: cfg.journal_config,
390 translator: cfg.translator,
391 init_cache_size: cfg.init_cache_size,
392 }
393 }
394}
395
396pub type FixedConfig<T, S> = Config<T, FConfig, S>;
398
399pub type VariableConfig<T, C, S> = Config<T, VConfig<C>, S>;
401
402#[boxed]
404pub(super) async fn init<F, E, U, H, T, I, J, const N: usize, S>(
405 context: E,
406 config: Config<T, J::Config, S>,
407) -> Result<db::Db<F, E, J, I, H, U, N, S>, crate::qmdb::Error<F>>
408where
409 F: merkle::Graftable,
410 E: Context,
411 U: Update + Send + Sync,
412 H: Hasher,
413 T: Translator,
414 I: IndexFactory<T, Value = Location<F>>,
415 J: Inner<E, Item = Operation<F, U>>,
416 S: Strategy,
417 Operation<F, U>: Committable + CodecShared,
418{
419 const {
421 assert!(
425 N.is_multiple_of(H::Digest::SIZE),
426 "chunk size must be some multiple of the digest size",
427 );
428 assert!(N.is_power_of_two(), "chunk size must be a power of 2");
431 }
432
433 let strategy = config.merkle_config.strategy.clone();
434 let metadata_partition = config.grafted_metadata_partition.clone();
435
436 let (metadata, pruned_chunks, pinned_nodes) =
438 db::init_metadata(context.child("metadata"), &metadata_partition).await?;
439
440 let bitmap = BitMap::<N>::new_with_pruned_chunks(pruned_chunks)
444 .map_err(|_| crate::qmdb::Error::<F>::DataCorrupted("pruned chunks overflow"))?;
445 let bitmap = Arc::new(Shared::<N>::new(bitmap));
446
447 let any = any::init_with_bitmap(context.child("any"), config.into(), Some(bitmap)).await?;
448
449 let ops_size = any.log.merkle.size();
451 let ops_leaves = crate::merkle::Location::<F>::try_from(ops_size)?;
452 let grafted_tree = db::build_grafted_tree::<F, H, S, N>(
453 any.bitmap.as_ref(),
454 &pinned_nodes,
455 &any.log.merkle,
456 ops_leaves,
457 &strategy,
458 )
459 .await?;
460
461 let storage = grafting::Storage::<F, H, _, _>::new(
463 &grafted_tree,
464 grafting::height::<N>(),
465 &any.log.merkle,
466 );
467 let partial_chunk = db::partial_chunk(any.bitmap.as_ref());
468 let ops_root = any.root();
469 let root = db::compute_db_root::<F, H, _, _, N>(
470 any.bitmap.as_ref(),
471 &storage,
472 ops_leaves,
473 partial_chunk,
474 any.inactivity_floor_loc,
475 &ops_root,
476 )
477 .await?;
478
479 let metrics = Metrics::new(context);
480 let db = db::Db {
481 any,
482 grafted_tree: Arc::new(grafted_tree),
483 metadata,
484 strategy,
485 root,
486 metrics,
487 #[cfg(test)]
488 halt_before_prune_log: false,
489 };
490 db.update_metrics();
491 Ok(db)
492}
493
494#[cfg(any(test, feature = "test-traits"))]
496pub trait BitmapPrunedBits {
497 fn pruned_bits(&self) -> u64;
499
500 fn get_bit(&self, index: u64) -> bool;
502
503 fn oldest_retained(&self) -> u64;
505}
506
507#[cfg(test)]
508pub mod tests {
509 pub use super::BitmapPrunedBits;
512 use super::{ordered, unordered, FConfig, FixedConfig, MerkleConfig, VConfig, VariableConfig};
513 use crate::{
514 merkle::{self, mmb, mmr},
515 qmdb::{
516 any::{
517 test::colliding_digest,
518 traits::{DbAny, MerkleizedBatch as _, UnmerkleizedBatch as _},
519 },
520 store::tests::{TestKey, TestValue},
521 verify_proof,
522 },
523 translator::Translator,
524 };
525 use commonware_parallel::Sequential;
526 use commonware_runtime::{
527 buffer::paged::CacheRef,
528 deterministic::{self, Context},
529 BufferPooler, Runner as _, Supervisor as _,
530 };
531 use commonware_utils::{bitmap::Readable, NZUsize, TestRng, NZU16, NZU64};
532 use core::future::Future;
533 use ordered::tests::test_build_small_close_reopen as test_ordered_build_small_close_reopen;
534 use rand::Rng;
535 use std::{
536 num::{NonZeroU16, NonZeroUsize},
537 sync::Arc,
538 };
539 use tracing::warn;
540 use unordered::tests::test_build_small_close_reopen as test_unordered_build_small_close_reopen;
541
542 type Error<F> = crate::qmdb::Error<F>;
543 type Location<F> = merkle::Location<F>;
544 type WriteVec<F, C> = Vec<(<C as DbAny<F>>::Key, Option<<C as DbAny<F>>::Value>)>;
545
546 const PAGE_SIZE: NonZeroU16 = NZU16!(88);
548 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(8);
549
550 macro_rules! staged_merkleize_parity_test {
563 ($name:ident, $open_db:path) => {
564 #[test_traced("WARN")]
565 pub fn $name() {
566 fn key(i: u64) -> Digest {
567 Sha256::hash(&i.to_be_bytes())
568 }
569 fn val(i: u64) -> Digest {
570 Sha256::hash(&(i + 10000).to_be_bytes())
571 }
572
573 deterministic::Runner::default().start(|ctx| async move {
574 let mut db = $open_db(ctx.child("current"), "staged-parity".to_string()).await;
575
576 let mut seed = db.new_batch();
577 for i in 0..2000u64 {
578 seed = seed.write(key(i), Some(val(i)));
579 }
580 let seed = seed.merkleize(&db, None).await.unwrap();
581 db.apply_batch(seed).await.unwrap();
582 db.commit().await.unwrap();
583
584 for depth in [0u8, 1u8, 2u8] {
585 let mut stack = Vec::new();
589 match depth {
590 0 => {}
591 1 => {
592 let mut p = db.new_batch();
593 for i in 0..50u64 {
594 p = p.write(key(i), Some(val(i + 1_000)));
595 }
596 for i in 100..110u64 {
597 p = p.write(key(i), None);
598 }
599 stack.push(p.merkleize(&db, None).await.unwrap());
600 }
601 2 => {
602 let mut grandparent = db.new_batch();
603 for i in 0..10u64 {
604 grandparent = grandparent.write(key(i), Some(val(i + 1_000)));
605 }
606 for i in 100..110u64 {
607 grandparent = grandparent.write(key(i), None);
608 }
609 let grandparent = grandparent.merkleize(&db, None).await.unwrap();
610
611 let mut p = grandparent.new_batch::<Sha256>();
612 for i in 20..30u64 {
613 p = p.write(key(i), Some(val(i + 2_000)));
614 }
615 let p = p.merkleize(&db, None).await.unwrap();
616 stack.push(grandparent);
617 stack.push(p);
618 }
619 _ => unreachable!("covered depths"),
620 };
621 let new_batch = || {
622 stack
623 .last()
624 .map_or_else(|| db.new_batch(), |p| p.new_batch::<Sha256>())
625 };
626
627 let read_keys = [
631 key(5),
632 key(6),
633 key(9000),
634 key(5),
635 key(0),
636 key(20),
637 key(60),
638 key(105),
639 ];
640 let keys: Vec<&Digest> = read_keys.iter().collect();
641 let indexed_updates = vec![
642 (0, Some(val(5_000))),
643 (2, Some(val(5_001))),
644 (3, Some(val(5_002))),
645 (4, Some(val(5_003))),
646 (5, None),
647 (6, Some(val(5_004))),
648 (7, Some(val(5_005))),
649 ];
650 let upserts = vec![
651 (key(7000), Some(val(6_000))),
652 (key(30), Some(val(6_001))),
653 (key(5), Some(val(6_002))),
654 (key(31), None),
655 ];
656
657 let mut explicit = new_batch();
658 let explicit_values = explicit.get_many(&keys, &db).await.unwrap();
659 for (slot, value) in &indexed_updates {
660 explicit = explicit.write(read_keys[*slot], *value);
661 }
662 for (k, v) in &upserts {
663 explicit = explicit.write(*k, *v);
664 }
665 let explicit_root = explicit.merkleize(&db, None).await.unwrap().root();
666
667 let (staged_values, staged) = new_batch().stage(&keys, &db).await.unwrap();
668 let staged_root = staged
669 .merkleize(indexed_updates.clone(), upserts.clone(), None, &db)
670 .await
671 .unwrap()
672 .root();
673
674 assert_eq!(
675 explicit_values, staged_values,
676 "value mismatch at depth={depth}"
677 );
678 assert_eq!(explicit_root, staged_root, "root mismatch at depth={depth}");
679
680 let split = 3;
681 let (mut expanded_values, staged) =
682 new_batch().stage(&keys[..split], &db).await.unwrap();
683 let (range, suffix_values, staged) =
684 staged.expand(&keys[split..], &db).await.unwrap();
685 assert_eq!(range, split..keys.len());
686 expanded_values.extend(suffix_values);
687 let expanded_root = staged
688 .merkleize(indexed_updates.clone(), upserts.clone(), None, &db)
689 .await
690 .unwrap()
691 .root();
692
693 assert_eq!(
694 explicit_values, expanded_values,
695 "expanded value mismatch at depth={depth}"
696 );
697 assert_eq!(
698 explicit_root, expanded_root,
699 "expanded root mismatch at depth={depth}"
700 );
701
702 let planned = val(7_000);
703 let duplicate_update = val(7_001);
704 let (first_values, staged) =
705 new_batch().stage(&keys[..1], &db).await.unwrap();
706 let (duplicate_range, duplicate_values, staged) =
707 staged.expand(&keys[..1], &db).await.unwrap();
708 assert_eq!(duplicate_range, 1..2);
709 assert_eq!(
710 first_values[0], duplicate_values[0],
711 "duplicate expansion must assign a new slot without changing the base read"
712 );
713 assert_ne!(
714 duplicate_values[0],
715 Some(planned),
716 "expand must not observe values computed for earlier staged slots"
717 );
718
719 let duplicate_root = staged
720 .merkleize(
721 vec![
722 (0, Some(planned)),
723 (duplicate_range.start, Some(duplicate_update)),
724 ],
725 Vec::new(),
726 None,
727 &db,
728 )
729 .await
730 .unwrap()
731 .root();
732 let expected_duplicate_root = new_batch()
733 .write(read_keys[0], Some(planned))
734 .write(read_keys[0], Some(duplicate_update))
735 .merkleize(&db, None)
736 .await
737 .unwrap()
738 .root();
739 assert_eq!(
740 expected_duplicate_root, duplicate_root,
741 "duplicate expanded slots should use normal update-order semantics"
742 );
743 }
744 });
745 }
746 };
747 }
748 pub(crate) use staged_merkleize_parity_test;
749
750 pub(crate) fn fixed_config<T: Translator + Default>(
752 partition_prefix: &str,
753 pooler: &impl BufferPooler,
754 ) -> FixedConfig<T, Sequential> {
755 let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE);
756 FixedConfig {
757 merkle_config: MerkleConfig {
758 journal_partition: format!("{partition_prefix}-journal-partition"),
759 metadata_partition: format!("{partition_prefix}-metadata-partition"),
760 items_per_blob: NZU64!(11),
761 write_buffer: NZUsize!(1024),
762 strategy: Sequential,
763 page_cache: page_cache.clone(),
764 },
765 journal_config: FConfig {
766 partition: format!("{partition_prefix}-partition-prefix"),
767 items_per_blob: NZU64!(7),
768 page_cache,
769 write_buffer: NZUsize!(1024),
770 },
771 grafted_metadata_partition: format!("{partition_prefix}-grafted-metadata-partition"),
772 translator: T::default(),
773 init_cache_size: Some(NZUsize!(1024)),
774 }
775 }
776
777 pub(crate) fn variable_config<T: Translator + Default>(
779 partition_prefix: &str,
780 pooler: &impl BufferPooler,
781 ) -> VariableConfig<T, ((), ()), Sequential> {
782 let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE);
783 VariableConfig {
784 merkle_config: MerkleConfig {
785 journal_partition: format!("{partition_prefix}-journal-partition"),
786 metadata_partition: format!("{partition_prefix}-metadata-partition"),
787 items_per_blob: NZU64!(11),
788 write_buffer: NZUsize!(1024),
789 strategy: Sequential,
790 page_cache: page_cache.clone(),
791 },
792 journal_config: VConfig {
793 partition: format!("{partition_prefix}-partition-prefix"),
794 items_per_section: NZU64!(7),
795 compression: None,
796 codec_config: ((), ()),
797 page_cache,
798 write_buffer: NZUsize!(1024),
799 },
800 grafted_metadata_partition: format!("{partition_prefix}-grafted-metadata-partition"),
801 translator: T::default(),
802 init_cache_size: Some(NZUsize!(1024)),
803 }
804 }
805
806 fn commit_writes<'a, F: merkle::Graftable, C: DbAny<F>>(
812 db: &'a mut C,
813 writes: impl IntoIterator<Item = (C::Key, Option<<C as DbAny<F>>::Value>)> + 'a,
814 ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), Error<F>>> + 'a>> {
815 Box::pin(async move {
816 let mut batch = db.new_batch();
817 for (k, v) in writes {
818 batch = batch.write(k, v);
819 }
820 let merkleized = batch.merkleize(db, None).await?;
821 db.apply_batch(merkleized).await?;
822 db.commit().await?;
823 Ok(())
824 })
825 }
826
827 #[boxed]
830 pub async fn apply_random_ops<F, C>(
831 num_elements: u64,
832 commit_changes: bool,
833 rng_seed: u64,
834 mut db: C,
835 ) -> Result<C, Error<F>>
836 where
837 F: merkle::Graftable,
838 C: DbAny<F>,
839 C::Key: TestKey,
840 <C as DbAny<F>>::Value: TestValue,
841 {
842 warn!("rng_seed={}", rng_seed);
844 let mut rng = TestRng::new(rng_seed);
845
846 let writes: Vec<_> = (0u64..num_elements)
848 .map(|i| {
849 let k = TestKey::from_seed(i);
850 let v = TestValue::from_seed(rng.next_u64());
851 (k, Some(v))
852 })
853 .collect();
854 if commit_changes {
855 commit_writes(&mut db, writes).await?;
856 }
857
858 let mut pending: WriteVec<F, C> = Vec::new();
861 for _ in 0u64..num_elements * 10 {
862 let rand_key = TestKey::from_seed(rng.next_u64() % num_elements);
863 if rng.next_u32().is_multiple_of(7) {
864 pending.push((rand_key, None));
865 continue;
866 }
867 let v = TestValue::from_seed(rng.next_u64());
868 pending.push((rand_key, Some(v)));
869 if commit_changes && rng.next_u32().is_multiple_of(20) {
870 commit_writes(&mut db, pending.drain(..)).await?;
871 }
872 }
873 if commit_changes {
874 commit_writes(&mut db, pending).await?;
875 }
876 Ok(db)
877 }
878
879 #[boxed]
881 async fn build_random_close_reopen_round<M, C, F, Fut>(
882 mut context: Context,
883 mut open_db: F,
884 ) -> String
885 where
886 M: merkle::Graftable + 'static,
887 C: DbAny<M> + 'static,
888 C::Key: TestKey,
889 <C as DbAny<M>>::Value: TestValue,
890 F: FnMut(Context, String) -> Fut,
891 Fut: Future<Output = C>,
892 {
893 const ELEMENTS: u64 = 1000;
894
895 let partition = "build-random".to_string();
896 let rng_seed = context.next_u64();
897 let mut db: C = open_db(context.child("first"), partition.clone()).await;
898 db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
899 .await
900 .unwrap();
901 let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
902 db.apply_batch(merkleized).await.unwrap();
903 db.sync().await.unwrap();
904
905 let root = db.root();
907 drop(db);
908 let db: C = open_db(context.child("second"), partition).await;
909
910 assert_eq!(db.root(), root);
912
913 db.destroy().await.unwrap();
914 context.auditor().state()
915 }
916
917 pub async fn test_build_random_close_reopen<M, C, F, Fut>(context: Context, open_db: F)
922 where
923 M: merkle::Graftable + 'static,
924 C: DbAny<M> + 'static,
925 C::Key: TestKey,
926 <C as DbAny<M>>::Value: TestValue,
927 F: FnMut(Context, String) -> Fut + Clone,
928 Fut: Future<Output = C>,
929 {
930 let state1 =
932 build_random_close_reopen_round::<M, C, F, Fut>(context, open_db.clone()).await;
933
934 let executor = deterministic::Runner::default();
936 let state2 = executor
937 .start(|context| build_random_close_reopen_round::<M, C, F, Fut>(context, open_db));
938
939 assert_eq!(state1, state2);
940 }
941
942 pub async fn test_commit_after_sync_recovery<M, C, F, Fut>(context: Context, mut open_db: F)
944 where
945 M: merkle::Graftable + 'static,
946 C: DbAny<M> + 'static,
947 C::Key: TestKey,
948 <C as DbAny<M>>::Value: TestValue,
949 F: FnMut(Context, String) -> Fut + Clone,
950 Fut: Future<Output = C>,
951 {
952 let mut open_db_clone = open_db.clone();
953 let partition = "commit-after-sync".to_string();
954 let mut db: C = Box::pin(open_db_clone(context.child("first"), partition.clone())).await;
955 let key0 = <<C as DbAny<M>>::Key as TestKey>::from_seed(0);
956 let key1 = <<C as DbAny<M>>::Key as TestKey>::from_seed(1);
957 let value0 = <C as DbAny<M>>::Value::from_seed(100);
958 let value1 = <C as DbAny<M>>::Value::from_seed(200);
959
960 commit_writes::<M, C>(&mut db, [(key0, Some(value0.clone()))])
962 .await
963 .unwrap();
964 db.sync().await.unwrap();
965
966 commit_writes::<M, C>(&mut db, [(key1, Some(value1.clone()))])
968 .await
969 .unwrap();
970 let committed_root = db.root();
971 let committed_size = db.size();
972 drop(db);
973
974 let db: C = Box::pin(open_db(context.child("second"), partition)).await;
975 assert_eq!(db.root(), committed_root);
976 assert_eq!(db.size(), committed_size);
977 assert_eq!(db.get(&key0).await.unwrap(), Some(value0));
978 assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
979
980 db.destroy().await.unwrap();
981 }
982
983 pub async fn test_simulate_write_failures<M, C, F, Fut>(mut context: Context, mut open_db: F)
988 where
989 M: merkle::Graftable + 'static,
990 C: DbAny<M> + 'static,
991 C::Key: TestKey,
992 <C as DbAny<M>>::Value: TestValue,
993 F: FnMut(Context, String) -> Fut + Clone,
994 Fut: Future<Output = C>,
995 {
996 const ELEMENTS: u64 = 1000;
997
998 let partition = "build-random-fail-commit".to_string();
999 let rng_seed = context.next_u64();
1000 let mut db: C = Box::pin(open_db(context.child("first"), partition.clone())).await;
1001 db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1002 .await
1003 .unwrap();
1004 commit_writes(&mut db, []).await.unwrap();
1005 let committed_root = db.root();
1006 let committed_op_count = db.bounds().end;
1007 db.prune(db.sync_boundary()).await.unwrap();
1008
1009 let db = apply_random_ops::<M, C>(ELEMENTS, false, rng_seed + 1, db)
1011 .await
1012 .unwrap();
1013
1014 drop(db);
1017 let db: C = Box::pin(open_db(
1018 context.child("scenario").with_attribute("index", 1),
1019 partition.clone(),
1020 ))
1021 .await;
1022 assert_eq!(db.root(), committed_root);
1023 assert_eq!(db.bounds().end, committed_op_count);
1024
1025 let db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed + 1, db)
1027 .await
1028 .unwrap();
1029
1030 let committed_op_count = db.bounds().end;
1034 drop(db);
1035
1036 let db: C = Box::pin(open_db(
1039 context.child("scenario").with_attribute("index", 2),
1040 partition.clone(),
1041 ))
1042 .await;
1043 let scenario_2_root = db.root();
1044
1045 let fresh_partition = "build-random-fail-commit-fresh".to_string();
1048 let mut db: C = Box::pin(open_db(context.child("fresh"), fresh_partition.clone())).await;
1049 db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1050 .await
1051 .unwrap();
1052 commit_writes(&mut db, []).await.unwrap();
1053 db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed + 1, db)
1054 .await
1055 .unwrap();
1056 db.prune(db.sync_boundary()).await.unwrap();
1057 assert_eq!(db.bounds().end, committed_op_count);
1059 assert_eq!(db.root(), scenario_2_root);
1060
1061 db.destroy().await.unwrap();
1062 }
1063
1064 pub async fn test_different_pruning_delays_same_root<M, C, F, Fut>(
1069 context: Context,
1070 mut open_db: F,
1071 ) where
1072 M: merkle::Graftable,
1073 C: DbAny<M>,
1074 C::Key: TestKey,
1075 <C as DbAny<M>>::Value: TestValue,
1076 F: FnMut(Context, String) -> Fut + Clone,
1077 Fut: Future<Output = C>,
1078 {
1079 const NUM_OPERATIONS: u64 = 1000;
1080
1081 let mut open_db_clone = open_db.clone();
1082 let mut db_no_pruning: C = Box::pin(open_db_clone(
1084 context.child("no_pruning"),
1085 "no-pruning-test".into(),
1086 ))
1087 .await;
1088 let mut db_pruning: C =
1089 Box::pin(open_db(context.child("pruning"), "pruning-test".into())).await;
1090
1091 let mut pending_no_pruning: WriteVec<M, C> = Vec::new();
1094 let mut pending_pruning: WriteVec<M, C> = Vec::new();
1095 for i in 0..NUM_OPERATIONS {
1096 let key: C::Key = TestKey::from_seed(i);
1097 let value: <C as DbAny<M>>::Value = TestValue::from_seed(i * 1000);
1098
1099 pending_no_pruning.push((key, Some(value.clone())));
1100 pending_pruning.push((key, Some(value)));
1101
1102 if i % 50 == 49 {
1104 commit_writes(&mut db_no_pruning, pending_no_pruning.drain(..))
1105 .await
1106 .unwrap();
1107 commit_writes(&mut db_pruning, pending_pruning.drain(..))
1108 .await
1109 .unwrap();
1110 db_pruning
1111 .prune(db_no_pruning.sync_boundary())
1112 .await
1113 .unwrap();
1114 }
1115 }
1116
1117 commit_writes(&mut db_no_pruning, pending_no_pruning)
1119 .await
1120 .unwrap();
1121 commit_writes(&mut db_pruning, pending_pruning)
1122 .await
1123 .unwrap();
1124
1125 let root_no_pruning = db_no_pruning.root();
1127 let root_pruning = db_pruning.root();
1128 assert_eq!(root_no_pruning, root_pruning);
1129
1130 assert_eq!(
1132 db_no_pruning.inactivity_floor_loc(),
1133 db_pruning.inactivity_floor_loc()
1134 );
1135
1136 db_no_pruning.destroy().await.unwrap();
1137 db_pruning.destroy().await.unwrap();
1138 }
1139
1140 pub async fn test_sync_persists_bitmap_pruning_boundary<M, C, F, Fut>(
1146 mut context: Context,
1147 mut open_db: F,
1148 ) where
1149 M: merkle::Graftable + 'static,
1150 C: DbAny<M> + BitmapPrunedBits + 'static,
1151 C::Key: TestKey,
1152 <C as DbAny<M>>::Value: TestValue,
1153 F: FnMut(Context, String) -> Fut + Clone,
1154 Fut: Future<Output = C>,
1155 {
1156 const ELEMENTS: u64 = 500;
1157
1158 let mut open_db_clone = open_db.clone();
1159 let partition = "sync-bitmap-pruning".to_string();
1160 let rng_seed = context.next_u64();
1161 let mut db: C = Box::pin(open_db_clone(context.child("first"), partition.clone())).await;
1162
1163 db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1165 .await
1166 .unwrap();
1167 let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
1168 db.apply_batch(merkleized).await.unwrap();
1169
1170 db.prune(db.sync_boundary()).await.unwrap();
1172
1173 let pruned_bits_before = db.pruned_bits();
1174 warn!(
1175 "pruned_bits_before={}, inactivity_floor={}, op_count={}",
1176 pruned_bits_before,
1177 *db.inactivity_floor_loc(),
1178 *db.bounds().end
1179 );
1180
1181 assert!(
1183 pruned_bits_before > 0,
1184 "Expected bitmap to have pruned bits after prune()"
1185 );
1186
1187 db.sync().await.unwrap();
1189
1190 let root_before = db.root();
1192 drop(db);
1193
1194 let db: C = Box::pin(open_db(context.child("second"), partition)).await;
1196
1197 let pruned_bits_after = db.pruned_bits();
1200 warn!("pruned_bits_after={}", pruned_bits_after);
1201
1202 assert_eq!(
1203 pruned_bits_after, pruned_bits_before,
1204 "Bitmap pruned bits mismatch after reopen - sync() may not have called write_pruned()"
1205 );
1206
1207 assert_eq!(db.root(), root_before);
1209
1210 db.destroy().await.unwrap();
1211 }
1212
1213 pub async fn test_current_db_build_big<M, C, F, Fut>(context: Context, mut open_db: F)
1219 where
1220 M: merkle::Graftable,
1221 C: DbAny<M>,
1222 C::Key: TestKey,
1223 <C as DbAny<M>>::Value: TestValue,
1224 F: FnMut(Context, String) -> Fut + Clone,
1225 Fut: Future<Output = C>,
1226 {
1227 const ELEMENTS: u64 = 1000;
1228
1229 let mut open_db_clone = open_db.clone();
1230 let mut db: C = Box::pin(open_db_clone(context.child("first"), "build-big".into())).await;
1231
1232 let mut map = std::collections::HashMap::<C::Key, <C as DbAny<M>>::Value>::default();
1233
1234 let mut batch = db.new_batch();
1236
1237 for i in 0u64..ELEMENTS {
1239 let k: C::Key = TestKey::from_seed(i);
1240 let v: <C as DbAny<M>>::Value = TestValue::from_seed(i * 1000);
1241 batch = batch.write(k, Some(v.clone()));
1242 map.insert(k, v);
1243 }
1244
1245 for i in 0u64..ELEMENTS {
1247 if i % 3 != 0 {
1248 continue;
1249 }
1250 let k: C::Key = TestKey::from_seed(i);
1251 let v: <C as DbAny<M>>::Value = TestValue::from_seed((i + 1) * 10000);
1252 batch = batch.write(k, Some(v.clone()));
1253 map.insert(k, v);
1254 }
1255
1256 for i in 0u64..ELEMENTS {
1258 if i % 7 != 1 {
1259 continue;
1260 }
1261 let k: C::Key = TestKey::from_seed(i);
1262 batch = batch.write(k, None);
1263 map.remove(&k);
1264 }
1265
1266 let merkleized = batch.merkleize(&db, None).await.unwrap();
1267 db.apply_batch(merkleized).await.unwrap();
1268
1269 db.sync().await.unwrap();
1271 db.prune(db.sync_boundary()).await.unwrap();
1272
1273 let root = db.root();
1275 db.sync().await.unwrap();
1276 drop(db);
1277
1278 let db: C = Box::pin(open_db(context.child("second"), "build-big".into())).await;
1280 assert_eq!(root, db.root());
1281
1282 for i in 0u64..ELEMENTS {
1284 let k: C::Key = TestKey::from_seed(i);
1285 if let Some(map_value) = map.get(&k) {
1286 let Some(db_value) = db.get(&k).await.unwrap() else {
1287 panic!("key not found in db: {k}");
1288 };
1289 assert_eq!(*map_value, db_value);
1290 } else {
1291 assert!(db.get(&k).await.unwrap().is_none());
1292 }
1293 }
1294 }
1295
1296 pub async fn test_stale_batch_side_effect_free<M, C, F, Fut>(context: Context, mut open_db: F)
1300 where
1301 M: merkle::Graftable,
1302 C: DbAny<M>,
1303 C::Key: TestKey,
1304 <C as DbAny<M>>::Value: TestValue,
1305 F: FnMut(Context, String) -> Fut,
1306 Fut: Future<Output = C>,
1307 {
1308 let mut db: C = Box::pin(open_db(
1309 context.child("db"),
1310 "stale-side-effect-free".into(),
1311 ))
1312 .await;
1313
1314 let key1 = <C::Key as TestKey>::from_seed(1);
1315 let key2 = <C::Key as TestKey>::from_seed(2);
1316 let value1 = <<C as DbAny<M>>::Value as TestValue>::from_seed(10);
1317 let value2 = <<C as DbAny<M>>::Value as TestValue>::from_seed(20);
1318
1319 let mut batch = db.new_batch();
1320 batch = batch.write(key1, Some(value1.clone()));
1321 let batch_a = batch.merkleize(&db, None).await.unwrap();
1322 let mut batch = db.new_batch();
1323 batch = batch.write(key2, Some(value2));
1324 let batch_b = batch.merkleize(&db, None).await.unwrap();
1325
1326 db.apply_batch(batch_a).await.unwrap();
1327 let expected_root = db.root();
1328 let expected_bounds = db.bounds();
1329 let expected_metadata = db.get_metadata().await.unwrap();
1330 assert_eq!(db.get(&key1).await.unwrap(), Some(value1.clone()));
1331 assert_eq!(db.get(&key2).await.unwrap(), None);
1332
1333 let result = db.apply_batch(batch_b).await;
1334 assert!(
1335 matches!(result, Err(Error::StaleBatch { .. })),
1336 "expected StaleBatch error, got {result:?}"
1337 );
1338 assert_eq!(db.root(), expected_root);
1339 assert_eq!(db.bounds(), expected_bounds);
1340 assert_eq!(db.get_metadata().await.unwrap(), expected_metadata);
1341 assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
1342 assert_eq!(db.get(&key2).await.unwrap(), None);
1343
1344 db.destroy().await.unwrap();
1345 }
1346
1347 use crate::translator::OneCap;
1348 use commonware_cryptography::{sha256::Digest, Hasher as _, Sha256};
1349 use commonware_macros::{boxed, test_group, test_traced};
1350
1351 type OrderedFixedDb =
1352 ordered::fixed::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1353 type OrderedVariableDb =
1354 ordered::variable::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1355 type UnorderedFixedDb =
1356 unordered::fixed::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1357 type UnorderedVariableDb = unordered::variable::Db<
1358 mmr::Family,
1359 Context,
1360 Digest,
1361 Digest,
1362 Sha256,
1363 OneCap,
1364 32,
1365 Sequential,
1366 >;
1367 type OrderedFixedP1Db = ordered::fixed::partitioned::Db<
1368 mmr::Family,
1369 Context,
1370 Digest,
1371 Digest,
1372 Sha256,
1373 OneCap,
1374 1,
1375 32,
1376 Sequential,
1377 >;
1378 type OrderedVariableP1Db = ordered::variable::partitioned::Db<
1379 mmr::Family,
1380 Context,
1381 Digest,
1382 Digest,
1383 Sha256,
1384 OneCap,
1385 1,
1386 32,
1387 Sequential,
1388 >;
1389 type UnorderedFixedP1Db = unordered::fixed::partitioned::Db<
1390 mmr::Family,
1391 Context,
1392 Digest,
1393 Digest,
1394 Sha256,
1395 OneCap,
1396 1,
1397 32,
1398 Sequential,
1399 >;
1400 type UnorderedVariableP1Db = unordered::variable::partitioned::Db<
1401 mmr::Family,
1402 Context,
1403 Digest,
1404 Digest,
1405 Sha256,
1406 OneCap,
1407 1,
1408 32,
1409 Sequential,
1410 >;
1411 type OrderedFixedP2Db = ordered::fixed::partitioned::Db<
1412 mmr::Family,
1413 Context,
1414 Digest,
1415 Digest,
1416 Sha256,
1417 OneCap,
1418 2,
1419 32,
1420 Sequential,
1421 >;
1422 type OrderedVariableP2Db = ordered::variable::partitioned::Db<
1423 mmr::Family,
1424 Context,
1425 Digest,
1426 Digest,
1427 Sha256,
1428 OneCap,
1429 2,
1430 32,
1431 Sequential,
1432 >;
1433 type UnorderedFixedP2Db = unordered::fixed::partitioned::Db<
1434 mmr::Family,
1435 Context,
1436 Digest,
1437 Digest,
1438 Sha256,
1439 OneCap,
1440 2,
1441 32,
1442 Sequential,
1443 >;
1444 type UnorderedVariableP2Db = unordered::variable::partitioned::Db<
1445 mmr::Family,
1446 Context,
1447 Digest,
1448 Digest,
1449 Sha256,
1450 OneCap,
1451 2,
1452 32,
1453 Sequential,
1454 >;
1455
1456 type OrderedFixedMmbDb =
1457 ordered::fixed::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1458 type OrderedVariableMmbDb =
1459 ordered::variable::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1460 type UnorderedFixedMmbDb =
1461 unordered::fixed::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1462 type UnorderedVariableMmbDb = unordered::variable::Db<
1463 mmb::Family,
1464 Context,
1465 Digest,
1466 Digest,
1467 Sha256,
1468 OneCap,
1469 32,
1470 Sequential,
1471 >;
1472 type OrderedFixedMmbP1Db = ordered::fixed::partitioned::Db<
1473 mmb::Family,
1474 Context,
1475 Digest,
1476 Digest,
1477 Sha256,
1478 OneCap,
1479 1,
1480 32,
1481 Sequential,
1482 >;
1483 type OrderedVariableMmbP1Db = ordered::variable::partitioned::Db<
1484 mmb::Family,
1485 Context,
1486 Digest,
1487 Digest,
1488 Sha256,
1489 OneCap,
1490 1,
1491 32,
1492 Sequential,
1493 >;
1494 type UnorderedFixedMmbP1Db = unordered::fixed::partitioned::Db<
1495 mmb::Family,
1496 Context,
1497 Digest,
1498 Digest,
1499 Sha256,
1500 OneCap,
1501 1,
1502 32,
1503 Sequential,
1504 >;
1505 type UnorderedVariableMmbP1Db = unordered::variable::partitioned::Db<
1506 mmb::Family,
1507 Context,
1508 Digest,
1509 Digest,
1510 Sha256,
1511 OneCap,
1512 1,
1513 32,
1514 Sequential,
1515 >;
1516 type OrderedFixedMmbP2Db = ordered::fixed::partitioned::Db<
1517 mmb::Family,
1518 Context,
1519 Digest,
1520 Digest,
1521 Sha256,
1522 OneCap,
1523 2,
1524 32,
1525 Sequential,
1526 >;
1527 type OrderedVariableMmbP2Db = ordered::variable::partitioned::Db<
1528 mmb::Family,
1529 Context,
1530 Digest,
1531 Digest,
1532 Sha256,
1533 OneCap,
1534 2,
1535 32,
1536 Sequential,
1537 >;
1538 type UnorderedFixedMmbP2Db = unordered::fixed::partitioned::Db<
1539 mmb::Family,
1540 Context,
1541 Digest,
1542 Digest,
1543 Sha256,
1544 OneCap,
1545 2,
1546 32,
1547 Sequential,
1548 >;
1549 type UnorderedVariableMmbP2Db = unordered::variable::partitioned::Db<
1550 mmb::Family,
1551 Context,
1552 Digest,
1553 Digest,
1554 Sha256,
1555 OneCap,
1556 2,
1557 32,
1558 Sequential,
1559 >;
1560
1561 #[test_traced]
1568 fn test_partitioned_ordered_short_key_exclusion_not_forgeable() {
1569 type ForgedExclusionDb = ordered::variable::partitioned::Db<
1570 mmr::Family,
1571 Context,
1572 Vec<u8>,
1573 Vec<u8>,
1574 Sha256,
1575 OneCap,
1576 2,
1577 32,
1578 Sequential,
1579 >;
1580
1581 let executor = deterministic::Runner::default();
1582 executor.start(|context| async move {
1583 let page_cache = CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE);
1584 let cfg = VariableConfig {
1585 merkle_config: MerkleConfig {
1586 journal_partition: "forged-exclusion-journal".to_string(),
1587 metadata_partition: "forged-exclusion-metadata".to_string(),
1588 items_per_blob: NZU64!(11),
1589 write_buffer: NZUsize!(1024),
1590 strategy: Sequential,
1591 page_cache: page_cache.clone(),
1592 },
1593 journal_config: VConfig {
1594 partition: "forged-exclusion-log".to_string(),
1595 items_per_section: NZU64!(7),
1596 compression: None,
1597 codec_config: (((0..=8).into(), ()), ((0..=8).into(), ())),
1598 page_cache,
1599 write_buffer: NZUsize!(1024),
1600 },
1601 grafted_metadata_partition: "forged-exclusion-grafted".to_string(),
1602 translator: OneCap,
1603 init_cache_size: Some(NZUsize!(1024)),
1604 };
1605 let mut db = ForgedExclusionDb::init(context.child("db"), cfg)
1606 .await
1607 .unwrap();
1608
1609 let a = vec![0x00u8, 0x80];
1613 let b = vec![0x01u8];
1614 let c = vec![0x01u8, 0x00];
1615 let (va, vb, vc) = (vec![0x0au8], vec![0x0bu8], vec![0x0cu8]);
1616
1617 let merkleized = db
1619 .new_batch()
1620 .write(a.clone(), Some(va.clone()))
1621 .write(b.clone(), Some(vb.clone()))
1622 .merkleize(&db, None)
1623 .await
1624 .unwrap();
1625 db.apply_batch(merkleized).await.unwrap();
1626 let merkleized = db
1627 .new_batch()
1628 .write(c.clone(), Some(vc.clone()))
1629 .merkleize(&db, None)
1630 .await
1631 .unwrap();
1632 db.apply_batch(merkleized).await.unwrap();
1633 let root = db.root();
1634
1635 assert_eq!(db.get(&a).await.unwrap(), Some(va));
1637 assert_eq!(db.get(&b).await.unwrap(), Some(vb));
1638 assert_eq!(db.get(&c).await.unwrap(), Some(vc));
1639
1640 let (_, span_b) = db.get_span(&b).await.unwrap().unwrap();
1643 assert_eq!(
1644 span_b.next_key, c,
1645 "b.next_key must be c, not the wrapped-around a"
1646 );
1647
1648 assert!(matches!(
1650 db.exclusion_proof(&c).await,
1651 Err(Error::KeyExists)
1652 ));
1653
1654 let kvp = db.key_value_proof(b.clone()).await.unwrap();
1658 let forged = ordered::ExclusionProof::KeyValue(kvp.proof, span_b);
1659 assert!(!ForgedExclusionDb::verify_exclusion_proof(
1660 &c, &forged, &root
1661 ));
1662
1663 db.destroy().await.unwrap();
1664 });
1665 }
1666
1667 macro_rules! open_db_fn {
1669 ($db:ty, $cfg:ident) => {
1670 |ctx: Context, partition: String| async move {
1671 <$db>::init(ctx.child("storage"), $cfg::<OneCap>(&partition, &ctx))
1672 .await
1673 .unwrap()
1674 }
1675 };
1676 }
1677
1678 macro_rules! with_all_variants {
1680 ($cb:ident!($($args:tt)*)) => {
1681 $cb!($($args)*, of, OrderedFixedDb, fixed_config);
1682 $cb!($($args)*, ov, OrderedVariableDb, variable_config);
1683 $cb!($($args)*, uf, UnorderedFixedDb, fixed_config);
1684 $cb!($($args)*, uv, UnorderedVariableDb, variable_config);
1685 $cb!($($args)*, ofp1, OrderedFixedP1Db, fixed_config);
1686 $cb!($($args)*, ovp1, OrderedVariableP1Db, variable_config);
1687 $cb!($($args)*, ufp1, UnorderedFixedP1Db, fixed_config);
1688 $cb!($($args)*, uvp1, UnorderedVariableP1Db, variable_config);
1689 $cb!($($args)*, ofp2, OrderedFixedP2Db, fixed_config);
1690 $cb!($($args)*, ovp2, OrderedVariableP2Db, variable_config);
1691 $cb!($($args)*, ufp2, UnorderedFixedP2Db, fixed_config);
1692 $cb!($($args)*, uvp2, UnorderedVariableP2Db, variable_config);
1693 $cb!($($args)*, of_mmb, OrderedFixedMmbDb, fixed_config);
1694 $cb!($($args)*, ov_mmb, OrderedVariableMmbDb, variable_config);
1695 $cb!($($args)*, uf_mmb, UnorderedFixedMmbDb, fixed_config);
1696 $cb!($($args)*, uv_mmb, UnorderedVariableMmbDb, variable_config);
1697 $cb!($($args)*, ofp1_mmb, OrderedFixedMmbP1Db, fixed_config);
1698 $cb!($($args)*, ovp1_mmb, OrderedVariableMmbP1Db, variable_config);
1699 $cb!($($args)*, ufp1_mmb, UnorderedFixedMmbP1Db, fixed_config);
1700 $cb!($($args)*, uvp1_mmb, UnorderedVariableMmbP1Db, variable_config);
1701 $cb!($($args)*, ofp2_mmb, OrderedFixedMmbP2Db, fixed_config);
1702 $cb!($($args)*, ovp2_mmb, OrderedVariableMmbP2Db, variable_config);
1703 $cb!($($args)*, ufp2_mmb, UnorderedFixedMmbP2Db, fixed_config);
1704 $cb!($($args)*, uvp2_mmb, UnorderedVariableMmbP2Db, variable_config);
1705 };
1706 }
1707
1708 macro_rules! with_ordered_variants {
1710 ($cb:ident!($($args:tt)*)) => {
1711 $cb!($($args)*, of, OrderedFixedDb, fixed_config);
1712 $cb!($($args)*, ov, OrderedVariableDb, variable_config);
1713 $cb!($($args)*, ofp1, OrderedFixedP1Db, fixed_config);
1714 $cb!($($args)*, ovp1, OrderedVariableP1Db, variable_config);
1715 $cb!($($args)*, ofp2, OrderedFixedP2Db, fixed_config);
1716 $cb!($($args)*, ovp2, OrderedVariableP2Db, variable_config);
1717 $cb!($($args)*, of_mmb, OrderedFixedMmbDb, fixed_config);
1718 $cb!($($args)*, ov_mmb, OrderedVariableMmbDb, variable_config);
1719 $cb!($($args)*, ofp1_mmb, OrderedFixedMmbP1Db, fixed_config);
1720 $cb!($($args)*, ovp1_mmb, OrderedVariableMmbP1Db, variable_config);
1721 $cb!($($args)*, ofp2_mmb, OrderedFixedMmbP2Db, fixed_config);
1722 $cb!($($args)*, ovp2_mmb, OrderedVariableMmbP2Db, variable_config);
1723 };
1724 }
1725
1726 macro_rules! with_unordered_variants {
1728 ($cb:ident!($($args:tt)*)) => {
1729 $cb!($($args)*, uf, UnorderedFixedDb, fixed_config);
1730 $cb!($($args)*, uv, UnorderedVariableDb, variable_config);
1731 $cb!($($args)*, ufp1, UnorderedFixedP1Db, fixed_config);
1732 $cb!($($args)*, uvp1, UnorderedVariableP1Db, variable_config);
1733 $cb!($($args)*, ufp2, UnorderedFixedP2Db, fixed_config);
1734 $cb!($($args)*, uvp2, UnorderedVariableP2Db, variable_config);
1735 $cb!($($args)*, uf_mmb, UnorderedFixedMmbDb, fixed_config);
1736 $cb!($($args)*, uv_mmb, UnorderedVariableMmbDb, variable_config);
1737 $cb!($($args)*, ufp1_mmb, UnorderedFixedMmbP1Db, fixed_config);
1738 $cb!($($args)*, uvp1_mmb, UnorderedVariableMmbP1Db, variable_config);
1739 $cb!($($args)*, ufp2_mmb, UnorderedFixedMmbP2Db, fixed_config);
1740 $cb!($($args)*, uvp2_mmb, UnorderedVariableMmbP2Db, variable_config);
1741 };
1742 }
1743
1744 macro_rules! test_for_variant {
1749 ($f:ident, $traced:literal, $label:ident, $db:ty, $cfg:ident) => {
1750 paste::paste! {
1751 #[test_group("slow")]
1752 #[test_traced($traced)]
1753 fn [<$f _ $label>]() {
1754 let executor = deterministic::Runner::default();
1755 executor.start(|context| async move {
1756 Box::pin($f(context, open_db_fn!($db, $cfg))).await
1759 });
1760 }
1761 }
1762 };
1763 }
1764
1765 macro_rules! test_for_all_variants {
1767 ($f:ident, $traced:literal) => {
1768 with_all_variants!(test_for_variant!($f, $traced));
1769 };
1770 }
1771
1772 macro_rules! test_for_ordered_variants {
1774 ($f:ident, $traced:literal) => {
1775 with_ordered_variants!(test_for_variant!($f, $traced));
1776 };
1777 }
1778
1779 macro_rules! test_for_unordered_variants {
1781 ($f:ident, $traced:literal) => {
1782 with_unordered_variants!(test_for_variant!($f, $traced));
1783 };
1784 }
1785
1786 async fn test_ordered_build_big<M, C, F, Fut>(context: Context, open_db: F)
1788 where
1789 M: merkle::Graftable,
1790 C: DbAny<M>,
1791 C::Key: TestKey,
1792 <C as DbAny<M>>::Value: TestValue,
1793 F: FnMut(Context, String) -> Fut + Clone,
1794 Fut: Future<Output = C>,
1795 {
1796 test_current_db_build_big::<M, C, F, Fut>(context, open_db).await;
1797 }
1798
1799 async fn test_unordered_build_big<M, C, F, Fut>(context: Context, open_db: F)
1800 where
1801 M: merkle::Graftable,
1802 C: DbAny<M>,
1803 C::Key: TestKey,
1804 <C as DbAny<M>>::Value: TestValue,
1805 F: FnMut(Context, String) -> Fut + Clone,
1806 Fut: Future<Output = C>,
1807 {
1808 test_current_db_build_big::<M, C, F, Fut>(context, open_db).await;
1809 }
1810
1811 test_for_all_variants!(test_build_random_close_reopen, "WARN");
1812 test_for_all_variants!(test_simulate_write_failures, "WARN");
1813 test_for_all_variants!(test_different_pruning_delays_same_root, "WARN");
1814 test_for_all_variants!(test_sync_persists_bitmap_pruning_boundary, "WARN");
1815 test_for_all_variants!(test_commit_after_sync_recovery, "WARN");
1816 test_for_all_variants!(test_stale_batch_side_effect_free, "WARN");
1817
1818 test_for_ordered_variants!(test_ordered_build_big, "WARN");
1819 test_for_ordered_variants!(test_ordered_build_small_close_reopen, "DEBUG");
1820
1821 test_for_unordered_variants!(test_unordered_build_big, "WARN");
1822 test_for_unordered_variants!(test_unordered_build_small_close_reopen, "DEBUG");
1823
1824 fn key(i: u64) -> Digest {
1831 Sha256::hash(&i.to_be_bytes())
1832 }
1833
1834 fn val(i: u64) -> Digest {
1835 Sha256::hash(&(i + 10000).to_be_bytes())
1836 }
1837
1838 async fn mmb_commit(
1839 db: &mut UnorderedVariableMmbDb,
1840 writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
1841 ) {
1842 let mut batch = db.new_batch();
1843 for (k, v) in writes {
1844 batch = batch.write(k, v);
1845 }
1846 let merkleized = batch.merkleize(db, None).await.unwrap();
1847 db.apply_batch(merkleized).await.unwrap();
1848 db.commit().await.unwrap();
1849 }
1850
1851 async fn commit_writes_with_metadata(
1852 db: &mut UnorderedVariableDb,
1853 writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
1854 metadata: Option<Digest>,
1855 ) -> std::ops::Range<Location<mmr::Family>> {
1856 let mut batch = db.new_batch();
1857 for (k, v) in writes {
1858 batch = batch.write(k, v);
1859 }
1860 let merkleized = batch.merkleize(db, metadata).await.unwrap();
1861 let range = db.apply_batch(merkleized).await.unwrap();
1862 db.commit().await.unwrap();
1863 range
1864 }
1865
1866 #[test_traced("INFO")]
1867 fn test_current_rewind_recovery() {
1868 let executor = deterministic::Runner::default();
1869 executor.start(|context| async move {
1870 let partition = "current-rewind-recovery";
1871 let ctx = context.child("db");
1872 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
1873 ctx.child("storage"),
1874 variable_config::<OneCap>(partition, &ctx),
1875 )
1876 .await
1877 .unwrap();
1878 let initial_size = db.bounds().end;
1879 let initial_root = db.root();
1880 let initial_ops_root = db.ops_root();
1881 let initial_floor = db.inactivity_floor_loc();
1882
1883 let metadata_a = val(900);
1884 let first_range = commit_writes_with_metadata(
1885 &mut db,
1886 [(key(0), Some(val(0))), (key(1), Some(val(1)))],
1887 Some(metadata_a),
1888 )
1889 .await;
1890 assert_eq!(first_range.start, initial_size);
1891 let size_before = db.bounds().end;
1892 let root_before = db.root();
1893 let ops_root_before = db.ops_root();
1894 let floor_before = db.inactivity_floor_loc();
1895 assert_eq!(size_before, first_range.end);
1896
1897 let metadata_b = val(901);
1898 let second_range = commit_writes_with_metadata(
1899 &mut db,
1900 [
1901 (key(0), Some(val(100))),
1902 (key(1), None),
1903 (key(2), Some(val(2))),
1904 ],
1905 Some(metadata_b),
1906 )
1907 .await;
1908 assert_eq!(second_range.start, size_before);
1909 assert_ne!(db.root(), root_before);
1910 assert_eq!(db.get_metadata().await.unwrap(), Some(val(901)));
1911 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(100)));
1912 assert_eq!(db.get(&key(1)).await.unwrap(), None);
1913 assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
1914
1915 db.rewind(size_before).await.unwrap();
1916 assert_eq!(db.bounds().end, size_before);
1917 assert_eq!(db.root(), root_before);
1918 assert_eq!(db.ops_root(), ops_root_before);
1919 assert_eq!(db.inactivity_floor_loc(), floor_before);
1920 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a));
1921 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
1922 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
1923 assert_eq!(db.get(&key(2)).await.unwrap(), None);
1924
1925 db.commit().await.unwrap();
1926 drop(db);
1927
1928 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
1929 context.child("reopen"),
1930 variable_config::<OneCap>(partition, &context),
1931 )
1932 .await
1933 .unwrap();
1934 assert_eq!(reopened.bounds().end, size_before);
1935 assert_eq!(reopened.root(), root_before);
1936 assert_eq!(reopened.ops_root(), ops_root_before);
1937 assert_eq!(reopened.inactivity_floor_loc(), floor_before);
1938 assert_eq!(reopened.get_metadata().await.unwrap(), Some(val(900)));
1939 assert_eq!(reopened.get(&key(0)).await.unwrap(), Some(val(0)));
1940 assert_eq!(reopened.get(&key(1)).await.unwrap(), Some(val(1)));
1941 assert_eq!(reopened.get(&key(2)).await.unwrap(), None);
1942
1943 let mut reopened = reopened;
1944 reopened.rewind(initial_size).await.unwrap();
1945 assert_eq!(reopened.bounds().end, initial_size);
1946 assert_eq!(reopened.root(), initial_root);
1947 assert_eq!(reopened.ops_root(), initial_ops_root);
1948 assert_eq!(reopened.inactivity_floor_loc(), initial_floor);
1949 assert_eq!(reopened.get_metadata().await.unwrap(), None);
1950 assert_eq!(reopened.get(&key(0)).await.unwrap(), None);
1951 assert_eq!(reopened.get(&key(1)).await.unwrap(), None);
1952 assert_eq!(reopened.get(&key(2)).await.unwrap(), None);
1953
1954 reopened.commit().await.unwrap();
1955 drop(reopened);
1956
1957 let reopened_initial: UnorderedVariableDb = UnorderedVariableDb::init(
1958 context.child("reopen_initial"),
1959 variable_config::<OneCap>(partition, &context),
1960 )
1961 .await
1962 .unwrap();
1963 assert_eq!(reopened_initial.bounds().end, initial_size);
1964 assert_eq!(reopened_initial.root(), initial_root);
1965 assert_eq!(reopened_initial.ops_root(), initial_ops_root);
1966 assert_eq!(reopened_initial.inactivity_floor_loc(), initial_floor);
1967 assert_eq!(reopened_initial.get_metadata().await.unwrap(), None);
1968 assert_eq!(reopened_initial.get(&key(0)).await.unwrap(), None);
1969 assert_eq!(reopened_initial.get(&key(1)).await.unwrap(), None);
1970 assert_eq!(reopened_initial.get(&key(2)).await.unwrap(), None);
1971
1972 reopened_initial.destroy().await.unwrap();
1973 });
1974 }
1975
1976 #[test_traced("INFO")]
1977 fn test_current_rewind_recovery_pruned_repeated_updates() {
1978 let executor = deterministic::Runner::default();
1979 executor.start(|context| async move {
1980 const COMMITS: u64 = 96;
1981
1982 let partition = "current-rewind-pruned-recovery";
1983 let ctx = context.child("db");
1984 let mut db: UnorderedVariableDb =
1985 UnorderedVariableDb::init(ctx.child("storage"), variable_config::<OneCap>(partition, &ctx))
1986 .await
1987 .unwrap();
1988
1989 let key0 = key(0);
1990 let mut history = Vec::new();
1991 for round in 0..COMMITS {
1992 commit_writes_with_metadata(
1993 &mut db,
1994 [(key0, Some(val(20_000 + round)))],
1995 None,
1996 )
1997 .await;
1998 history.push((
1999 db.bounds().end,
2000 db.inactivity_floor_loc(),
2001 db.root(),
2002 db.ops_root(),
2003 val(20_000 + round),
2004 ));
2005 }
2006
2007 db.prune(Location::new(1)).await.unwrap();
2010 let pruned_bits = db.pruned_bits();
2011 assert!(pruned_bits > 0, "expected bitmap pruning for rewind test");
2012 let bounds = db.bounds();
2013
2014 let (target_size, target_root, target_ops_root, target_value) = history
2015 .iter()
2016 .enumerate()
2017 .find_map(|(idx, (size, floor, root, ops_root, value))| {
2018 let removed_commits = history.len() - idx - 1;
2019 if removed_commits >= 3 && *size > bounds.start && *floor >= pruned_bits {
2020 Some((*size, *root, *ops_root, *value))
2021 } else {
2022 None
2023 }
2024 })
2025 .unwrap_or_else(|| {
2026 panic!(
2027 "expected legal pruned rewind target with repeated updates; bounds={bounds:?}, pruned_bits={pruned_bits}, latest_floor={:?}, history={history:?}",
2028 db.inactivity_floor_loc()
2029 )
2030 });
2031
2032 db.rewind(target_size).await.unwrap();
2033 assert_eq!(db.root(), target_root);
2034 assert_eq!(db.ops_root(), target_ops_root);
2035 assert_eq!(db.bounds().end, target_size);
2036 assert_eq!(db.get(&key0).await.unwrap(), Some(target_value));
2037
2038 db.commit().await.unwrap();
2039 drop(db);
2040
2041 let mut reopened: UnorderedVariableDb = UnorderedVariableDb::init(
2042 context.child("reopen_pruned_recovery"),
2043 variable_config::<OneCap>(partition, &context),
2044 )
2045 .await
2046 .unwrap();
2047 assert_eq!(reopened.root(), target_root);
2048 assert_eq!(reopened.ops_root(), target_ops_root);
2049 assert_eq!(reopened.bounds().end, target_size);
2050 assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_value));
2051
2052 let metadata_after_rewind = val(30_000);
2053 let new_key = key(1);
2054 let new_value = val(30_001);
2055 let expected_end = commit_writes_with_metadata(
2056 &mut reopened,
2057 [(new_key, Some(new_value))],
2058 Some(metadata_after_rewind),
2059 )
2060 .await
2061 .end;
2062 let root_after_new_write = reopened.root();
2063 let ops_root_after_new_write = reopened.ops_root();
2064 assert_eq!(reopened.bounds().end, expected_end);
2065 assert_eq!(reopened.get_metadata().await.unwrap(), Some(metadata_after_rewind));
2066 assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_value));
2067 assert_eq!(reopened.get(&new_key).await.unwrap(), Some(new_value));
2068
2069 drop(reopened);
2070 let reopened_after_new_write: UnorderedVariableDb = UnorderedVariableDb::init(
2071 context.child("reopen_pruned_after_new_write"),
2072 variable_config::<OneCap>(partition, &context),
2073 )
2074 .await
2075 .unwrap();
2076 assert_eq!(reopened_after_new_write.root(), root_after_new_write);
2077 assert_eq!(reopened_after_new_write.ops_root(), ops_root_after_new_write);
2078 assert_eq!(reopened_after_new_write.bounds().end, expected_end);
2079 assert_eq!(
2080 reopened_after_new_write.get_metadata().await.unwrap(),
2081 Some(metadata_after_rewind)
2082 );
2083 assert_eq!(reopened_after_new_write.get(&key0).await.unwrap(), Some(target_value));
2084 assert_eq!(
2085 reopened_after_new_write.get(&new_key).await.unwrap(),
2086 Some(new_value)
2087 );
2088
2089 reopened_after_new_write.destroy().await.unwrap();
2090 });
2091 }
2092
2093 #[test_traced("INFO")]
2096 fn test_current_mmb_settlement_guard_defers_pruning() {
2097 let executor = deterministic::Runner::default();
2098 executor.start(|context| async move {
2099 const COMMITS: u64 = 100;
2100
2101 let partition = "current-mmb-reopen-prove-after-prune";
2102 let ctx = context.child("db");
2103 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2104 ctx.child("storage"),
2105 variable_config::<OneCap>(partition, &ctx),
2106 )
2107 .await
2108 .unwrap();
2109
2110 let k = key(0);
2111 let mut expected = None;
2112 for round in 0..COMMITS {
2113 expected = Some(val(50_000 + round));
2114 let mut batch = db.new_batch();
2115 batch = batch.write(k, expected);
2116 let merkleized = batch.merkleize(&db, None).await.unwrap();
2117 db.apply_batch(merkleized).await.unwrap();
2118 db.commit().await.unwrap();
2119 }
2120
2121 let root_before = db.root();
2122 assert!(
2123 *db.inactivity_floor_loc() >= 256,
2124 "expected inactivity floor past chunk 0"
2125 );
2126 assert_eq!(
2127 *db.sync_boundary(),
2128 0,
2129 "settlement guard should hold boundary at 0 during unsettled window"
2130 );
2131
2132 let result = db.prune(Location::<mmb::Family>::new(1)).await;
2134 assert!(
2135 matches!(result, Err(Error::PruneBeyondMinRequired(_, _))),
2136 "expected PruneBeyondMinRequired, got {result:?}"
2137 );
2138 assert_eq!(db.pruned_bits(), 0);
2139 db.sync().await.unwrap();
2140 drop(db);
2141
2142 let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2144 context.child("reopen"),
2145 variable_config::<OneCap>(partition, &context),
2146 )
2147 .await
2148 .unwrap();
2149
2150 assert_eq!(reopened.root(), root_before);
2151 assert_eq!(reopened.get(&k).await.unwrap(), expected);
2152
2153 let _proof = reopened.key_value_proof(k).await.unwrap();
2155
2156 reopened.destroy().await.unwrap();
2157 });
2158 }
2159
2160 #[test_traced("INFO")]
2161 fn test_current_mmb_rewind_rejects_unsettled_pruned_window() {
2162 let executor = deterministic::Runner::default();
2163 executor.start(|context| async move {
2164 const COMMITS: u64 = 320;
2165 const N: usize = 32;
2166
2167 let partition = "current-mmb-rewind-unsettled-window";
2168 let ctx = context.child("db");
2169 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2170 ctx.child("storage"),
2171 variable_config::<OneCap>(partition, &ctx),
2172 )
2173 .await
2174 .unwrap();
2175
2176 let key0 = key(0);
2177 let mut history = Vec::new();
2178 for round in 0..COMMITS {
2179 let mut batch = db.new_batch();
2180 batch = batch.write(key0, Some(val(60_000 + round)));
2181 let merkleized = batch.merkleize(&db, None).await.unwrap();
2182 db.apply_batch(merkleized).await.unwrap();
2183 db.commit().await.unwrap();
2184 history.push((db.bounds().end, db.inactivity_floor_loc()));
2185 }
2186
2187 db.prune(db.sync_boundary()).await.unwrap();
2188 let pruned_bits = db.pruned_bits();
2189 assert!(pruned_bits > 0, "expected MMB bitmap pruning to be active");
2190 db.sync().await.unwrap();
2191
2192 let chunk_bits = commonware_utils::bitmap::BitMap::<N>::CHUNK_SIZE_BITS;
2193 let pruned_chunks = (pruned_bits / chunk_bits) as u64;
2194 let gh = super::grafting::height::<N>();
2195 let youngest = pruned_chunks - 1;
2196 let pair_chunk = youngest & !1;
2197 let pair_start = pair_chunk << gh;
2198 let pair_pos = <mmb::Family as merkle::Graftable>::subtree_root_position(
2199 merkle::Location::<mmb::Family>::new(pair_start),
2200 gh + 1,
2201 );
2202 let absorbed_after =
2203 <mmb::Family as merkle::Graftable>::peak_birth_size(pair_pos, gh + 1);
2204
2205 let unsafe_target = history
2206 .iter()
2207 .filter_map(|(size, floor)| {
2208 let s = **size;
2209 if s >= pruned_bits && s < absorbed_after && **floor >= pruned_bits {
2210 Some(s)
2211 } else {
2212 None
2213 }
2214 })
2215 .max()
2216 .unwrap_or_else(|| {
2217 panic!(
2218 "expected rewind target in unsettled window: pruned_bits={pruned_bits}, absorbed_after={absorbed_after}, history={history:?}"
2219 )
2220 });
2221
2222 let err = db
2223 .rewind(merkle::Location::<mmb::Family>::new(unsafe_target))
2224 .await
2225 .unwrap_err();
2226 assert!(
2227 matches!(err, Error::Journal(crate::journal::Error::ItemPruned(_))),
2228 "unexpected rewind error for unsettled delayed-merge window: {err:?}"
2229 );
2230 drop(db);
2231
2232 let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2233 context.child("reopen"),
2234 variable_config::<OneCap>(partition, &context),
2235 )
2236 .await
2237 .unwrap();
2238 reopened.destroy().await.unwrap();
2239 });
2240 }
2241
2242 #[test_traced]
2247 fn test_current_mmb_prune_respects_sync_boundary() {
2248 let executor = deterministic::Runner::default();
2249 executor.start(|context| async move {
2250 const COMMITS: u64 = 320;
2251
2252 let ctx = context.child("db");
2253 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2254 ctx.child("storage"),
2255 variable_config::<OneCap>("prune-clip-mmb", &ctx),
2256 )
2257 .await
2258 .unwrap();
2259
2260 let k = key(0);
2261 for round in 0..COMMITS {
2262 mmb_commit(&mut db, [(k, Some(val(70_000 + round)))]).await;
2263 }
2264
2265 db.prune(db.sync_boundary()).await.unwrap();
2266
2267 let boundary = db.sync_boundary();
2268 let floor = db.inactivity_floor_loc();
2269 assert!(
2270 boundary < floor,
2271 "delayed-merge lag must be strictly active: boundary={boundary}, floor={floor}"
2272 );
2273 assert!(
2274 db.bounds().start <= boundary,
2275 "ops journal was pruned past the settled bitmap boundary: \
2276 bounds.start={}, boundary={boundary}",
2277 db.bounds().start
2278 );
2279
2280 db.destroy().await.unwrap();
2281 });
2282 }
2283
2284 #[test_traced]
2289 fn test_current_mmr_prune_boundary_lag_is_only_chunk_alignment() {
2290 let executor = deterministic::Runner::default();
2291 executor.start(|context| async move {
2292 const COMMITS: u64 = 320;
2293 const N: usize = 32;
2294
2295 let ctx = context.child("db");
2296 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2297 ctx.child("storage"),
2298 variable_config::<OneCap>("prune-clip-mmr", &ctx),
2299 )
2300 .await
2301 .unwrap();
2302
2303 for round in 0..COMMITS {
2304 commit_writes_with_metadata(
2305 &mut db,
2306 [(key(0), Some(val(80_000 + round)))],
2307 None,
2308 )
2309 .await;
2310 }
2311
2312 db.prune(db.sync_boundary()).await.unwrap();
2313
2314 let boundary = db.sync_boundary();
2315 let floor = db.inactivity_floor_loc();
2316 let chunk_bits = commonware_utils::bitmap::BitMap::<N>::CHUNK_SIZE_BITS;
2317 assert!(
2318 boundary <= floor && *floor - *boundary < chunk_bits,
2319 "MMR lag should be only chunk alignment: boundary={boundary}, floor={floor}, chunk_bits={chunk_bits}"
2320 );
2321 assert!(
2322 db.bounds().start <= boundary,
2323 "ops journal bounds must be <= sync_boundary: bounds.start={}, boundary={boundary}",
2324 db.bounds().start
2325 );
2326
2327 db.destroy().await.unwrap();
2328 });
2329 }
2330
2331 #[test_traced]
2334 fn test_current_prune_below_settled_boundary_is_honored() {
2335 let executor = deterministic::Runner::default();
2336 executor.start(|context| async move {
2337 const COMMITS: u64 = 100;
2338
2339 let ctx = context.child("db");
2340 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2341 ctx.child("storage"),
2342 variable_config::<OneCap>("prune-below-boundary", &ctx),
2343 )
2344 .await
2345 .unwrap();
2346
2347 for round in 0..COMMITS {
2348 commit_writes_with_metadata(&mut db, [(key(0), Some(val(90_000 + round)))], None)
2349 .await;
2350 }
2351
2352 assert!(*db.inactivity_floor_loc() > 1);
2353 let small = Location::new(1);
2354 db.prune(small).await.unwrap();
2355
2356 assert!(
2357 db.bounds().start <= small,
2358 "journal pruning exceeded the caller-supplied target: bounds.start={}, requested={small}",
2359 db.bounds().start
2360 );
2361
2362 db.destroy().await.unwrap();
2363 });
2364 }
2365
2366 #[test_traced]
2369 fn test_current_mmb_reopen_and_prove_after_prune_delayed_merge() {
2370 let executor = deterministic::Runner::default();
2371 executor.start(|context| async move {
2372 let db_ctx = context.child("db_init");
2373 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2374 db_ctx.child("db"),
2375 variable_config::<OneCap>("test_prune_delayed_merge", &db_ctx),
2376 )
2377 .await
2378 .unwrap();
2379
2380 let k = key(0);
2381
2382 for round in 0..200u64 {
2383 mmb_commit(&mut db, [(k, Some(val(60_000 + round)))]).await;
2384 }
2385
2386 db.prune(db.sync_boundary()).await.unwrap();
2387 db.sync().await.unwrap();
2388
2389 for round in 200..300u64 {
2391 mmb_commit(&mut db, [(key(1), Some(val(round)))]).await;
2392 }
2393
2394 let proof = db.key_value_proof(k).await.unwrap();
2395 assert!(UnorderedVariableMmbDb::verify_key_value_proof(
2396 k,
2397 val(60_000 + 199),
2398 &proof,
2399 &db.root()
2400 ));
2401
2402 let target_root = db.root();
2403 drop(db);
2404
2405 let reopen_ctx = context.child("db_reopen");
2406 let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2407 reopen_ctx.child("db"),
2408 variable_config::<OneCap>("test_prune_delayed_merge", &reopen_ctx),
2409 )
2410 .await
2411 .unwrap();
2412
2413 assert_eq!(reopened.root(), target_root);
2414
2415 let proof = reopened.key_value_proof(k).await.unwrap();
2416 assert!(UnorderedVariableMmbDb::verify_key_value_proof(
2417 k,
2418 val(60_000 + 199),
2419 &proof,
2420 &reopened.root()
2421 ));
2422
2423 reopened.destroy().await.unwrap();
2424 });
2425 }
2426
2427 #[test_traced]
2429 fn test_current_mmb_reopen_after_prune_two_chunks() {
2430 let executor = deterministic::Runner::default();
2431 executor.start(|context| async move {
2432 let db_ctx = context.child("db");
2433 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2434 db_ctx.child("db"),
2435 variable_config::<OneCap>("test_prune_two", &db_ctx),
2436 )
2437 .await
2438 .unwrap();
2439
2440 let k = key(0);
2441 let mut expected;
2443
2444 let mut round = 0u64;
2447 loop {
2448 expected = Some(val(60_000 + round));
2449 mmb_commit(&mut db, [(k, expected)]).await;
2450 round += 1;
2451 db.prune(db.sync_boundary()).await.unwrap();
2452 if db.pruned_bits() >= 512 {
2453 break;
2454 }
2455 assert!(
2456 round < 500,
2457 "failed to reach 2 pruned chunks after {round} commits"
2458 );
2459 }
2460 db.sync().await.unwrap();
2461
2462 let target_root = db.root();
2463 drop(db);
2464
2465 let reopen_ctx = context.child("db_reopen");
2466 let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2467 reopen_ctx.child("db"),
2468 variable_config::<OneCap>("test_prune_two", &reopen_ctx),
2469 )
2470 .await
2471 .unwrap();
2472
2473 assert_eq!(reopened.root(), target_root);
2474 assert_eq!(reopened.get(&k).await.unwrap(), expected);
2475 reopened.destroy().await.unwrap();
2476 });
2477 }
2478
2479 #[test_traced]
2481 fn test_current_mmb_repeated_prune() {
2482 let executor = deterministic::Runner::default();
2483 executor.start(|context| async move {
2484 let mut db_ctx = context.child("db_init");
2485 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2486 db_ctx.child("db"),
2487 variable_config::<OneCap>("test_repeated_prune", &db_ctx),
2488 )
2489 .await
2490 .unwrap();
2491
2492 for round in 0..3u64 {
2493 let k = key(round * 1000);
2494 let mut expected = None;
2495 for i in 0..90 {
2496 expected = Some(val(round * 1000 + i));
2497 mmb_commit(&mut db, [(k, expected)]).await;
2498 }
2499
2500 db.prune(db.sync_boundary()).await.unwrap();
2501 db.sync().await.unwrap();
2502
2503 let root_before = db.root();
2504 db_ctx = context.child("db").with_attribute("round", round);
2505
2506 let prev_db = db;
2507 db = UnorderedVariableMmbDb::init(
2508 db_ctx.child("db"),
2509 variable_config::<OneCap>("test_repeated_prune", &db_ctx),
2510 )
2511 .await
2512 .unwrap();
2513
2514 assert_eq!(db.root(), root_before);
2515 assert_eq!(db.get(&k).await.unwrap(), expected);
2516 drop(prev_db);
2517 }
2518
2519 db.destroy().await.unwrap();
2520 });
2521 }
2522
2523 #[test_traced]
2525 fn test_current_mmb_stepwise_growth_matches_unpruned_reference() {
2526 let executor = deterministic::Runner::default();
2527 executor.start(|context| async move {
2528 let db_ctx = context.child("db_stepwise");
2529 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2530 db_ctx.child("db"),
2531 variable_config::<OneCap>("test_stepwise", &db_ctx),
2532 )
2533 .await
2534 .unwrap();
2535
2536 let ref_ctx = context.child("ref_stepwise");
2537 let mut ref_db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2538 ref_ctx.child("db"),
2539 variable_config::<OneCap>("test_stepwise_ref", &ref_ctx),
2540 )
2541 .await
2542 .unwrap();
2543
2544 let k = key(0);
2545 let mut commit_idx = 0u64;
2546
2547 while *db.inactivity_floor_loc() < 1024 {
2549 let value = Some(val(80_000 + commit_idx));
2550 mmb_commit(&mut db, [(k, value)]).await;
2551 mmb_commit(&mut ref_db, [(k, value)]).await;
2552 commit_idx += 1;
2553 }
2554
2555 db.prune(db.sync_boundary()).await.unwrap();
2556 db.sync().await.unwrap();
2557 assert_eq!(
2558 db.root(),
2559 ref_db.root(),
2560 "root mismatch immediately after prune"
2561 );
2562
2563 loop {
2565 let db_leaves =
2566 *Location::<mmb::Family>::try_from(db.any.log.merkle.size()).unwrap();
2567 if db_leaves >= 1560 {
2568 break;
2569 }
2570
2571 let value = Some(val(80_000 + commit_idx));
2572 mmb_commit(&mut db, [(k, value)]).await;
2573 mmb_commit(&mut ref_db, [(k, value)]).await;
2574 commit_idx += 1;
2575
2576 let db_leaves =
2577 *Location::<mmb::Family>::try_from(db.any.log.merkle.size()).unwrap();
2578 assert_eq!(
2579 db.root(),
2580 ref_db.root(),
2581 "stepwise root mismatch: leaves={db_leaves}, commit_idx={commit_idx}"
2582 );
2583 }
2584
2585 db.destroy().await.unwrap();
2586 ref_db.destroy().await.unwrap();
2587 });
2588 }
2589
2590 #[test_traced]
2592 fn test_current_mmb_large_repeated_prune_matches_unpruned_reference() {
2593 let executor = deterministic::Runner::default();
2594 executor.start(|context| async move {
2595 const ROUNDS: u64 = 8;
2596 const COMMITS_PER_ROUND: u64 = 120;
2597
2598 let mut db_ctx = context.child("db_init");
2599 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2600 db_ctx.child("db"),
2601 variable_config::<OneCap>("test_large_prune", &db_ctx),
2602 )
2603 .await
2604 .unwrap();
2605
2606 let ref_ctx = context.child("ref");
2607 let mut ref_db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2608 ref_ctx.child("db"),
2609 variable_config::<OneCap>("test_large_prune_ref", &ref_ctx),
2610 )
2611 .await
2612 .unwrap();
2613
2614 let k = key(0);
2615 let mut expected = None;
2616
2617 for round in 0..ROUNDS {
2618 for i in 0..COMMITS_PER_ROUND {
2619 let value = Some(val(round * 10_000 + i));
2620 expected = value;
2621 mmb_commit(&mut db, [(k, value)]).await;
2622 mmb_commit(&mut ref_db, [(k, value)]).await;
2623 }
2624
2625 assert_eq!(
2626 db.root(),
2627 ref_db.root(),
2628 "root mismatch before prune at round {round}"
2629 );
2630
2631 db.prune(db.sync_boundary()).await.unwrap();
2632 db.sync().await.unwrap();
2633
2634 assert_eq!(
2635 db.root(),
2636 ref_db.root(),
2637 "root mismatch after prune at round {round}"
2638 );
2639
2640 let proof = db.key_value_proof(k).await.unwrap();
2641 assert!(
2642 UnorderedVariableMmbDb::verify_key_value_proof(
2643 k,
2644 expected.expect("value should exist"),
2645 &proof,
2646 &db.root()
2647 ),
2648 "proof verification failed at round {round}"
2649 );
2650
2651 db_ctx = context.child("db_reopen").with_attribute("round", round);
2652 let prev_db = db;
2653 db = UnorderedVariableMmbDb::init(
2654 db_ctx.child("db"),
2655 variable_config::<OneCap>("test_large_prune", &db_ctx),
2656 )
2657 .await
2658 .unwrap();
2659
2660 assert_eq!(
2661 db.root(),
2662 ref_db.root(),
2663 "root mismatch after reopen at round {round}"
2664 );
2665 assert_eq!(
2666 db.get(&k).await.unwrap(),
2667 expected,
2668 "value mismatch after reopen at round {round}"
2669 );
2670
2671 let proof = db.key_value_proof(k).await.unwrap();
2672 assert!(
2673 UnorderedVariableMmbDb::verify_key_value_proof(
2674 k,
2675 expected.expect("value should exist"),
2676 &proof,
2677 &db.root()
2678 ),
2679 "proof verification failed after reopen at round {round}"
2680 );
2681
2682 drop(prev_db);
2683 }
2684
2685 db.destroy().await.unwrap();
2686 ref_db.destroy().await.unwrap();
2687 });
2688 }
2689
2690 #[test_traced]
2692 fn test_current_prune_rejects_beyond_sync_boundary_without_mutation() {
2693 let executor = deterministic::Runner::default();
2694 executor.start(|context| async move {
2695 const COMMITS: u64 = 160;
2696
2697 let partition = "current-prune-beyond-boundary";
2698 let ctx = context.child("db");
2699 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2700 ctx.child("storage"),
2701 variable_config::<OneCap>(partition, &ctx),
2702 )
2703 .await
2704 .unwrap();
2705
2706 let key0 = key(0);
2707 for round in 0..COMMITS {
2708 commit_writes_with_metadata(&mut db, [(key0, Some(val(40_000 + round)))], None)
2709 .await;
2710 }
2711
2712 let expected_root = db.root();
2713 let expected_ops_root = db.ops_root();
2714 let expected_boundary = db.sync_boundary();
2715 let expected_pruned_bits = db.pruned_bits();
2716 let expected_value = db.get(&key0).await.unwrap();
2717
2718 let invalid_prune_loc = Location::new(*expected_boundary + 256);
2720 let result = db.prune(invalid_prune_loc).await;
2721 assert!(
2722 matches!(result, Err(Error::PruneBeyondMinRequired(loc, boundary))
2723 if loc == invalid_prune_loc && boundary == expected_boundary),
2724 "expected prune rejection above sync boundary, got {result:?}"
2725 );
2726
2727 assert_eq!(db.root(), expected_root);
2728 assert_eq!(db.ops_root(), expected_ops_root);
2729 assert_eq!(db.pruned_bits(), expected_pruned_bits);
2730 assert_eq!(db.get(&key0).await.unwrap(), expected_value);
2731
2732 drop(db);
2733
2734 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
2735 context.child("reopen"),
2736 variable_config::<OneCap>(partition, &context),
2737 )
2738 .await
2739 .unwrap();
2740 assert_eq!(reopened.root(), expected_root);
2741 assert_eq!(reopened.ops_root(), expected_ops_root);
2742 assert_eq!(reopened.pruned_bits(), expected_pruned_bits);
2743 assert_eq!(reopened.get(&key0).await.unwrap(), expected_value);
2744
2745 reopened.destroy().await.unwrap();
2746 });
2747 }
2748
2749 #[test_traced("INFO")]
2750 fn test_current_rewind_small_delta_large_history() {
2751 let executor = deterministic::Runner::default();
2752 executor.start(|context| async move {
2753 const COMMITS: u64 = 200;
2754
2755 let partition = "current-rewind-small-delta";
2756 let ctx = context.child("db");
2757 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2758 ctx.child("storage"),
2759 variable_config::<OneCap>(partition, &ctx),
2760 )
2761 .await
2762 .unwrap();
2763
2764 let key0 = key(0);
2765 let key1 = key(1);
2766 let mut history = Vec::new();
2767
2768 for round in 0..COMMITS {
2769 let key0_value = val(40_000 + round);
2770 let key1_value = if round % 3 == 1 {
2771 None
2772 } else {
2773 Some(val(50_000 + round))
2774 };
2775
2776 commit_writes_with_metadata(
2777 &mut db,
2778 [(key0, Some(key0_value)), (key1, key1_value)],
2779 None,
2780 )
2781 .await;
2782
2783 history.push((
2784 db.bounds().end,
2785 db.root(),
2786 db.ops_root(),
2787 key0_value,
2788 key1_value,
2789 ));
2790 }
2791
2792 let target = *history
2793 .get(history.len() - 3)
2794 .expect("history should contain at least three commits");
2795 let (target_size, target_root, target_ops_root, target_key0, target_key1) = target;
2796
2797 db.rewind(target_size).await.unwrap();
2798 assert_eq!(db.bounds().end, target_size);
2799 assert_eq!(db.root(), target_root);
2800 assert_eq!(db.ops_root(), target_ops_root);
2801 assert_eq!(db.get(&key0).await.unwrap(), Some(target_key0));
2802 assert_eq!(db.get(&key1).await.unwrap(), target_key1);
2803
2804 db.commit().await.unwrap();
2805 drop(db);
2806
2807 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
2808 context.child("reopen_small_delta"),
2809 variable_config::<OneCap>(partition, &context),
2810 )
2811 .await
2812 .unwrap();
2813 assert_eq!(reopened.bounds().end, target_size);
2814 assert_eq!(reopened.root(), target_root);
2815 assert_eq!(reopened.ops_root(), target_ops_root);
2816 assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_key0));
2817 assert_eq!(reopened.get(&key1).await.unwrap(), target_key1);
2818
2819 reopened.destroy().await.unwrap();
2820 });
2821 }
2822
2823 #[test_traced("INFO")]
2824 fn test_current_rewind_pruned_target_errors() {
2825 let executor = deterministic::Runner::default();
2826 executor.start(|context| async move {
2827 const KEYS: u64 = 384;
2828
2829 let partition = "current-rewind-pruned";
2830 let ctx = context.child("db");
2831 let mut db: UnorderedVariableDb =
2832 UnorderedVariableDb::init(ctx.child("storage"), variable_config::<OneCap>(partition, &ctx))
2833 .await
2834 .unwrap();
2835
2836 let first_range = commit_writes_with_metadata(
2837 &mut db,
2838 (0..KEYS).map(|i| (key(i), Some(val(i)))),
2839 None,
2840 )
2841 .await;
2842 commit_writes_with_metadata(
2843 &mut db,
2844 (0..KEYS).map(|i| (key(i), Some(val(1000 + i)))),
2845 None,
2846 )
2847 .await;
2848
2849 db.prune(db.sync_boundary()).await.unwrap();
2850 let pruned_bits = db.pruned_bits();
2851 assert!(
2852 pruned_bits > *first_range.start,
2853 "expected bitmap pruning boundary above rewind target: pruned_bits={pruned_bits}, target={:?}",
2854 first_range.start
2855 );
2856
2857 let oldest_retained = db.bounds().start;
2858 let boundary_err = db.rewind(oldest_retained).await.unwrap_err();
2859 assert!(
2860 matches!(
2861 boundary_err,
2862 Error::Journal(crate::journal::Error::ItemPruned(_))
2863 ),
2864 "unexpected rewind error at retained boundary: {boundary_err:?}"
2865 );
2866
2867 let expected_pruned_loc = *first_range.start - 1;
2868 let err = db.rewind(first_range.start).await.unwrap_err();
2869 assert!(
2870 matches!(
2871 err,
2872 Error::Journal(crate::journal::Error::ItemPruned(loc))
2873 if loc == expected_pruned_loc
2874 ),
2875 "unexpected rewind error: {err:?}"
2876 );
2877
2878 db.destroy().await.unwrap();
2879 });
2880 }
2881
2882 #[test_traced("INFO")]
2883 fn test_current_rewind_rejects_target_below_bitmap_floor() {
2884 let executor = deterministic::Runner::default();
2885 executor.start(|context| async move {
2886 const COMMITS: u64 = 96;
2887
2888 let partition = "current-rewind-bitmap-floor";
2889 let ctx = context.child("db");
2890 let mut db: UnorderedVariableDb =
2891 UnorderedVariableDb::init(ctx.child("storage"), variable_config::<OneCap>(partition, &ctx))
2892 .await
2893 .unwrap();
2894
2895 let mut history = Vec::new();
2896 for round in 0..COMMITS {
2897 commit_writes_with_metadata(
2898 &mut db,
2899 [(key(0), Some(val(10_000 + round)))],
2900 None,
2901 )
2902 .await;
2903 history.push((db.bounds().end, db.inactivity_floor_loc()));
2904 }
2905 assert!(db.inactivity_floor_loc() > Location::new(64));
2906
2907 let prune_loc = Location::new(1);
2910 db.prune(prune_loc).await.unwrap();
2911 let pruned_bits = db.pruned_bits();
2912 assert!(pruned_bits > 0);
2913 let retained_start = db.bounds().start;
2914
2915 let rewind_target = history
2918 .iter()
2919 .find_map(|(size, floor)| {
2920 if *size > *retained_start
2921 && *size >= pruned_bits
2922 && *floor >= *retained_start
2923 && *floor < pruned_bits
2924 {
2925 Some(*size)
2926 } else {
2927 None
2928 }
2929 })
2930 .unwrap_or_else(|| {
2931 panic!(
2932 "expected rewind target below bitmap boundary. retained_start={retained_start:?}, pruned_bits={pruned_bits}, latest_floor={:?}, history={history:?}",
2933 db.inactivity_floor_loc()
2934 )
2935 });
2936
2937 let err = db.rewind(rewind_target).await.unwrap_err();
2938 assert!(
2939 matches!(err, Error::Journal(crate::journal::Error::ItemPruned(_))),
2940 "unexpected rewind error: {err:?}"
2941 );
2942
2943 db.destroy().await.unwrap();
2944 });
2945 }
2946
2947 pub async fn test_speculative_root_matches_committed<M, C, F, Fut>(
2953 context: Context,
2954 mut open_db: F,
2955 ) where
2956 M: merkle::Graftable + 'static,
2957 C: DbAny<M> + 'static,
2958 C::Key: TestKey,
2959 <C as DbAny<M>>::Value: TestValue,
2960 F: FnMut(Context, String) -> Fut + Clone,
2961 Fut: Future<Output = C>,
2962 {
2963 let mut open_db_clone = open_db.clone();
2964 let partition = "speculative-root".to_string();
2965
2966 let mut db: C = Box::pin(open_db_clone(context.child("init"), partition.clone())).await;
2971 let mut batch = db.new_batch();
2972 for i in 0..260 {
2973 batch = batch.write(TestKey::from_seed(i), Some(TestValue::from_seed(i + 1000)));
2974 }
2975 let merkleized = batch.merkleize(&db, None).await.unwrap();
2976 db.apply_batch(merkleized).await.unwrap();
2977 let speculative_root = db.root();
2978
2979 db.sync().await.unwrap();
2981 drop(db);
2982
2983 let db: C = Box::pin(open_db(context.child("reopen"), partition)).await;
2984 assert_eq!(db.root(), speculative_root);
2985
2986 db.destroy().await.unwrap();
2987 }
2988
2989 test_for_all_variants!(test_speculative_root_matches_committed, "INFO");
2990
2991 #[test_traced("INFO")]
2993 fn test_current_batch_merkleized_get() {
2994 let executor = deterministic::Runner::default();
2995 executor.start(|context| async move {
2996 let ctx = context.child("db");
2997 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2998 ctx.child("storage"),
2999 variable_config::<OneCap>("mg", &ctx),
3000 )
3001 .await
3002 .unwrap();
3003
3004 let ka = key(0);
3005 let kb = key(1);
3006 let kc = key(2);
3007
3008 {
3010 let mut batch = db.new_batch();
3011 batch = batch.write(ka, Some(val(0)));
3012 let merkleized = batch.merkleize(&db, None).await.unwrap();
3013 db.apply_batch(merkleized).await.unwrap();
3014 }
3015
3016 let va2 = val(100);
3018 let vb = val(1);
3019 let mut batch = db.new_batch();
3020 batch = batch.write(ka, Some(va2));
3021 batch = batch.write(kb, Some(vb));
3022 let merkleized = batch.merkleize(&db, None).await.unwrap();
3023
3024 assert_eq!(merkleized.get(&ka, &db).await.unwrap(), Some(va2));
3025 assert_eq!(merkleized.get(&kb, &db).await.unwrap(), Some(vb));
3026 assert_eq!(merkleized.get(&kc, &db).await.unwrap(), None);
3027
3028 db.destroy().await.unwrap();
3029 });
3030 }
3031
3032 #[test_traced("INFO")]
3035 fn test_current_batch_chaining() {
3036 let executor = deterministic::Runner::default();
3037 executor.start(|context| async move {
3038 let ctx = context.child("db");
3039 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3040 ctx.child("storage"),
3041 variable_config::<OneCap>("ch", &ctx),
3042 )
3043 .await
3044 .unwrap();
3045
3046 let mut parent = db.new_batch();
3048 for i in 0..5 {
3049 parent = parent.write(key(i), Some(val(i)));
3050 }
3051 let parent_m = parent.merkleize(&db, None).await.unwrap();
3052
3053 let mut child = parent_m.new_batch::<Sha256>();
3055 for i in 5..10 {
3056 child = child.write(key(i), Some(val(i)));
3057 }
3058 child = child.write(key(0), Some(val(999)));
3059 let child_m = child.merkleize(&db, None).await.unwrap();
3060
3061 let child_root = child_m.root();
3062
3063 assert_eq!(child_m.get(&key(0), &db).await.unwrap(), Some(val(999)));
3065 assert_eq!(child_m.get(&key(3), &db).await.unwrap(), Some(val(3)));
3066 assert_eq!(child_m.get(&key(7), &db).await.unwrap(), Some(val(7)));
3067
3068 db.apply_batch(child_m).await.unwrap();
3069 assert_eq!(db.root(), child_root);
3070
3071 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(999)));
3073 for i in 1..10 {
3074 assert_eq!(db.get(&key(i)).await.unwrap(), Some(val(i)));
3075 }
3076
3077 db.destroy().await.unwrap();
3078 });
3079 }
3080
3081 #[test_traced("INFO")]
3082 fn test_current_unordered_root_matches_between_pending_and_committed_paths() {
3083 let executor = deterministic::Runner::default();
3084 executor.start(|context| async move {
3085 let ctx = context.child("db");
3086 let mut db: UnorderedFixedDb =
3087 UnorderedFixedDb::init(ctx.child("storage"), fixed_config::<OneCap>("ucr", &ctx))
3088 .await
3089 .unwrap();
3090 let key_a = colliding_digest(0xAA, 1);
3091 let key_b = colliding_digest(0xAA, 0);
3092
3093 let mut initial = db.new_batch();
3098 for i in 0..4 {
3099 initial = initial.write(colliding_digest(0xAA, i), Some(colliding_digest(0xBB, i)));
3100 }
3101 let merkleized = initial.merkleize(&db, None).await.unwrap();
3102 db.apply_batch(merkleized).await.unwrap();
3103 db.commit().await.unwrap();
3104
3105 let parent = db
3109 .new_batch()
3110 .write(key_a, Some(colliding_digest(0xCC, 1)))
3111 .merkleize(&db, None)
3112 .await
3113 .unwrap();
3114
3115 let pending_child = parent
3119 .new_batch::<Sha256>()
3120 .write(key_a, Some(colliding_digest(0xDD, 1)))
3121 .write(key_b, Some(colliding_digest(0xDD, 0)))
3122 .merkleize(&db, None)
3123 .await
3124 .unwrap();
3125
3126 let pending_root = pending_child.root();
3127 let pending_ops_root = pending_child.ops_root();
3128
3129 db.apply_batch(parent).await.unwrap();
3130 db.commit().await.unwrap();
3131
3132 let committed_child = db
3133 .new_batch()
3134 .write(key_a, Some(colliding_digest(0xDD, 1)))
3135 .write(key_b, Some(colliding_digest(0xDD, 0)))
3136 .merkleize(&db, None)
3137 .await
3138 .unwrap();
3139
3140 assert_eq!(pending_root, committed_child.root());
3141 assert_eq!(pending_ops_root, committed_child.ops_root());
3142
3143 db.apply_batch(pending_child).await.unwrap();
3146 assert_eq!(db.root(), committed_child.root());
3147 assert_eq!(db.ops_root(), committed_child.ops_root());
3148
3149 db.destroy().await.unwrap();
3150 });
3151 }
3152
3153 #[test_traced("INFO")]
3154 fn test_current_ordered_root_matches_between_pending_and_committed_paths() {
3155 let executor = deterministic::Runner::default();
3156 executor.start(|context| async move {
3157 let ctx = context.child("db");
3158 let mut db: OrderedFixedDb =
3159 OrderedFixedDb::init(ctx.child("storage"), fixed_config::<OneCap>("ocr", &ctx))
3160 .await
3161 .unwrap();
3162 let key_a = colliding_digest(0xAA, 1);
3163 let key_b = colliding_digest(0xAA, 0);
3164
3165 let mut initial = db.new_batch();
3168 for i in 0..4 {
3169 initial = initial.write(colliding_digest(0xAA, i), Some(colliding_digest(0xBB, i)));
3170 }
3171 let merkleized = initial.merkleize(&db, None).await.unwrap();
3172 db.apply_batch(merkleized).await.unwrap();
3173 db.commit().await.unwrap();
3174
3175 let parent = db
3179 .new_batch()
3180 .write(key_a, Some(colliding_digest(0xCC, 1)))
3181 .merkleize(&db, None)
3182 .await
3183 .unwrap();
3184
3185 let pending_child = parent
3188 .new_batch::<Sha256>()
3189 .write(key_a, Some(colliding_digest(0xDD, 1)))
3190 .write(key_b, Some(colliding_digest(0xDD, 0)))
3191 .merkleize(&db, None)
3192 .await
3193 .unwrap();
3194
3195 let pending_root = pending_child.root();
3196 let pending_ops_root = pending_child.ops_root();
3197
3198 db.apply_batch(parent).await.unwrap();
3199 db.commit().await.unwrap();
3200
3201 let committed_child = db
3202 .new_batch()
3203 .write(key_a, Some(colliding_digest(0xDD, 1)))
3204 .write(key_b, Some(colliding_digest(0xDD, 0)))
3205 .merkleize(&db, None)
3206 .await
3207 .unwrap();
3208
3209 assert_eq!(pending_root, committed_child.root());
3210 assert_eq!(pending_ops_root, committed_child.ops_root());
3211
3212 db.apply_batch(pending_child).await.unwrap();
3215 assert_eq!(db.root(), committed_child.root());
3216 assert_eq!(db.ops_root(), committed_child.ops_root());
3217
3218 db.destroy().await.unwrap();
3219 });
3220 }
3221
3222 #[test_traced("INFO")]
3224 fn test_current_batch_apply_requires_commit_for_recovery() {
3225 let executor = deterministic::Runner::default();
3226 executor.start(|context| async move {
3227 let partition = "apply_requires_commit";
3228 let ctx = context.child("db");
3229 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3230 ctx.child("storage"),
3231 variable_config::<OneCap>(partition, &ctx),
3232 )
3233 .await
3234 .unwrap();
3235
3236 let committed_root = db.root();
3237
3238 let merkleized = db
3239 .new_batch()
3240 .write(key(0), Some(val(0)))
3241 .merkleize(&db, None)
3242 .await
3243 .unwrap();
3244 db.apply_batch(merkleized).await.unwrap();
3245
3246 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3247
3248 drop(db);
3249
3250 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
3251 context.child("reopen"),
3252 variable_config::<OneCap>(partition, &context),
3253 )
3254 .await
3255 .unwrap();
3256 assert_eq!(reopened.root(), committed_root);
3257 assert_eq!(reopened.get(&key(0)).await.unwrap(), None);
3258
3259 reopened.destroy().await.unwrap();
3260 });
3261 }
3262
3263 #[test_traced("INFO")]
3265 fn test_current_batch_single_stage_pipeline() {
3266 let executor = deterministic::Runner::default();
3267 executor.start(|context| async move {
3268 let ctx = context.child("db");
3269 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3270 ctx.child("storage"),
3271 variable_config::<OneCap>("pipe", &ctx),
3272 )
3273 .await
3274 .unwrap();
3275
3276 let mut batch = db.new_batch();
3277 batch = batch.write(key(0), Some(val(0)));
3278 let parent_merkleized = batch.merkleize(&db, None).await.unwrap();
3279 db.apply_batch(parent_merkleized).await.unwrap();
3280
3281 let child_merkleized = {
3282 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3283 let mut child = db.new_batch();
3284 child = child.write(key(1), Some(val(1)));
3285 child.merkleize(&db, None).await.unwrap()
3286 };
3287 db.commit().await.unwrap();
3288
3289 db.apply_batch(child_merkleized).await.unwrap();
3290 db.commit().await.unwrap();
3291
3292 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3293 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3294
3295 db.destroy().await.unwrap();
3296 });
3297 }
3298
3299 #[test_traced("INFO")]
3302 fn test_current_sequential_commit() {
3303 let executor = deterministic::Runner::default();
3304 executor.start(|context| async move {
3305 let ctx = context.child("db");
3306 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3307 ctx.child("storage"),
3308 variable_config::<OneCap>("ff", &ctx),
3309 )
3310 .await
3311 .unwrap();
3312
3313 let parent_m = db
3315 .new_batch()
3316 .write(key(0), Some(val(0)))
3317 .merkleize(&db, None)
3318 .await
3319 .unwrap();
3320
3321 let child_m = parent_m
3323 .new_batch::<Sha256>()
3324 .write(key(1), Some(val(1)))
3325 .merkleize(&db, None)
3326 .await
3327 .unwrap();
3328
3329 db.apply_batch(parent_m).await.unwrap();
3330 db.apply_batch(child_m).await.unwrap();
3331
3332 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3334 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3335
3336 let ctx2 = context.child("db").with_attribute("index", 2);
3339 let mut db2: UnorderedVariableDb = UnorderedVariableDb::init(
3340 ctx2.child("db"),
3341 variable_config::<OneCap>("ff2", &ctx2),
3342 )
3343 .await
3344 .unwrap();
3345 let m1 = db2
3346 .new_batch()
3347 .write(key(0), Some(val(0)))
3348 .merkleize(&db2, None)
3349 .await
3350 .unwrap();
3351 db2.apply_batch(m1).await.unwrap();
3352 let m2 = db2
3353 .new_batch()
3354 .write(key(1), Some(val(1)))
3355 .merkleize(&db2, None)
3356 .await
3357 .unwrap();
3358 db2.apply_batch(m2).await.unwrap();
3359
3360 assert_eq!(db.root(), db2.root());
3361
3362 db.destroy().await.unwrap();
3363 db2.destroy().await.unwrap();
3364 });
3365 }
3366
3367 #[test_traced("INFO")]
3370 fn test_current_to_batch_then_chain() {
3371 let executor = deterministic::Runner::default();
3372 executor.start(|context| async move {
3373 let ctx = context.child("db");
3374 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3375 ctx.child("storage"),
3376 variable_config::<OneCap>("tb", &ctx),
3377 )
3378 .await
3379 .unwrap();
3380
3381 let m = db
3383 .new_batch()
3384 .write(key(0), Some(val(0)))
3385 .merkleize(&db, None)
3386 .await
3387 .unwrap();
3388 db.apply_batch(m).await.unwrap();
3389
3390 let snapshot = db.to_batch();
3392 assert_eq!(snapshot.root(), db.root());
3393
3394 let child = snapshot
3396 .new_batch::<Sha256>()
3397 .write(key(1), Some(val(1)))
3398 .merkleize(&db, None)
3399 .await
3400 .unwrap();
3401
3402 assert_ne!(child.root(), snapshot.root());
3404
3405 db.apply_batch(child).await.unwrap();
3407 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3408 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3409
3410 db.destroy().await.unwrap();
3411 });
3412 }
3413
3414 #[test_traced("INFO")]
3419 fn test_current_live_batch_safe_across_prune() {
3420 let executor = deterministic::Runner::default();
3421 executor.start(|context| async move {
3422 let ctx = context.child("db");
3423 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3424 ctx.child("storage"),
3425 variable_config::<OneCap>("prune-live", &ctx),
3426 )
3427 .await
3428 .unwrap();
3429
3430 let mut seed = db.new_batch();
3432 for i in 0u64..300 {
3433 seed = seed.write(key(i), Some(val(i)));
3434 }
3435 let seed_m = seed.merkleize(&db, None).await.unwrap();
3436 db.apply_batch(seed_m).await.unwrap();
3437 db.commit().await.unwrap();
3438
3439 let mut p = db.new_batch();
3441 for i in 0u64..250 {
3442 p = p.write(key(i), Some(val(i + 10_000)));
3443 }
3444 let p_m = p.merkleize(&db, None).await.unwrap();
3445 db.apply_batch(Arc::clone(&p_m)).await.unwrap();
3446 db.commit().await.unwrap();
3447
3448 let c = p_m
3450 .new_batch::<Sha256>()
3451 .write(key(250), Some(val(99_999)))
3452 .merkleize(&db, None)
3453 .await
3454 .unwrap();
3455
3456 db.prune(db.sync_boundary()).await.unwrap();
3458
3459 assert_eq!(c.get(&key(250), &db).await.unwrap(), Some(val(99_999)));
3461
3462 db.apply_batch(c).await.unwrap();
3465 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(10_000)));
3466 assert_eq!(db.get(&key(250)).await.unwrap(), Some(val(99_999)));
3467
3468 db.destroy().await.unwrap();
3469 });
3470 }
3471
3472 #[test_traced("INFO")]
3481 fn test_current_extend_applied_batch() {
3482 let executor = deterministic::Runner::default();
3483 executor.start(|context| async move {
3484 let ctx = context.child("db");
3485 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3486 ctx.child("storage"),
3487 variable_config::<OneCap>("xtend", &ctx),
3488 )
3489 .await
3490 .unwrap();
3491
3492 let a = db
3494 .new_batch()
3495 .write(key(0), Some(val(0)))
3496 .merkleize(&db, None)
3497 .await
3498 .unwrap();
3499 db.apply_batch(Arc::clone(&a)).await.unwrap();
3500
3501 let b = a
3505 .new_batch::<Sha256>()
3506 .write(key(1), Some(val(1)))
3507 .merkleize(&db, None)
3508 .await
3509 .unwrap();
3510 db.apply_batch(b).await.unwrap();
3511
3512 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3513 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3514
3515 let c = db
3517 .new_batch()
3518 .write(key(2), Some(val(2)))
3519 .merkleize(&db, None)
3520 .await
3521 .unwrap();
3522 db.apply_batch(c).await.unwrap();
3523
3524 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3525 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3526 assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
3527
3528 db.destroy().await.unwrap();
3529 });
3530 }
3531
3532 #[test_traced("INFO")]
3539 fn test_current_live_batch_child_after_prune() {
3540 let executor = deterministic::Runner::default();
3541 executor.start(|context| async move {
3542 let ctx = context.child("db");
3543 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3544 ctx.child("storage"),
3545 variable_config::<OneCap>("child-after-prune", &ctx),
3546 )
3547 .await
3548 .unwrap();
3549
3550 let mut seed = db.new_batch();
3552 for i in 0u64..300 {
3553 seed = seed.write(key(i), Some(val(i)));
3554 }
3555 let seed_m = seed.merkleize(&db, None).await.unwrap();
3556 db.apply_batch(seed_m).await.unwrap();
3557 db.commit().await.unwrap();
3558
3559 let mut a_batch = db.new_batch();
3561 for i in 0u64..250 {
3562 a_batch = a_batch.write(key(i), Some(val(i + 10_000)));
3563 }
3564 let a = a_batch.merkleize(&db, None).await.unwrap();
3565 db.apply_batch(Arc::clone(&a)).await.unwrap();
3566 db.commit().await.unwrap();
3567
3568 db.prune(db.sync_boundary()).await.unwrap();
3570
3571 let b = a
3575 .new_batch::<Sha256>()
3576 .write(key(300), Some(val(300)))
3577 .merkleize(&db, None)
3578 .await
3579 .unwrap();
3580
3581 db.apply_batch(b).await.unwrap();
3582 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(10_000)));
3583 assert_eq!(db.get(&key(249)).await.unwrap(), Some(val(10_249)));
3584 assert_eq!(db.get(&key(300)).await.unwrap(), Some(val(300)));
3585
3586 db.destroy().await.unwrap();
3587 });
3588 }
3589
3590 #[test_traced("WARN")]
3594 fn test_current_apply_after_ancestor_dropped() {
3595 let executor = deterministic::Runner::default();
3596 executor.start(|context| async move {
3597 let ctx = context.child("db");
3598 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3599 ctx.child("storage"),
3600 variable_config::<OneCap>("adrop", &ctx),
3601 )
3602 .await
3603 .unwrap();
3604
3605 let mut a = db.new_batch();
3607 for i in 0..3 {
3608 a = a.write(key(i), Some(val(i)));
3609 }
3610 let a_m = a.merkleize(&db, None).await.unwrap();
3611
3612 let mut b = a_m.new_batch::<Sha256>();
3613 for i in 3..6 {
3614 b = b.write(key(i), Some(val(i)));
3615 }
3616 let b_m = b.merkleize(&db, None).await.unwrap();
3617
3618 let mut c = b_m.new_batch::<Sha256>();
3619 for i in 6..9 {
3620 c = c.write(key(i), Some(val(i)));
3621 }
3622 let c_m = c.merkleize(&db, None).await.unwrap();
3623
3624 drop(a_m);
3626 drop(b_m);
3627
3628 db.apply_batch(c_m).await.unwrap();
3630 db.commit().await.unwrap();
3631
3632 for i in 0..9 {
3634 assert_eq!(
3635 db.get(&key(i)).await.unwrap(),
3636 Some(val(i)),
3637 "key({i}) missing after apply_batch with dropped ancestors"
3638 );
3639 }
3640
3641 db.destroy().await.unwrap();
3642 });
3643 }
3644
3645 #[test_traced("WARN")]
3654 fn test_current_chain_bitmap_order_matches_sequential() {
3655 let executor = deterministic::Runner::default();
3656 executor.start(|context| async move {
3657 let ctx1 = context.child("db").with_attribute("index", 1);
3659 let mut db1: UnorderedVariableDb = UnorderedVariableDb::init(
3660 ctx1.child("db"),
3661 variable_config::<OneCap>("ord1", &ctx1),
3662 )
3663 .await
3664 .unwrap();
3665
3666 commit_writes_with_metadata(
3668 &mut db1,
3669 [(key(10), Some(val(10))), (key(11), Some(val(11)))],
3670 None,
3671 )
3672 .await;
3673
3674 let a = db1
3681 .new_batch()
3682 .write(key(10), Some(val(100)))
3683 .write(key(11), None) .merkleize(&db1, None)
3685 .await
3686 .unwrap();
3687
3688 let b = a
3689 .new_batch::<Sha256>()
3690 .write(key(12), Some(val(120)))
3691 .write(key(13), Some(val(130)))
3692 .merkleize(&db1, None)
3693 .await
3694 .unwrap();
3695
3696 let c = b
3697 .new_batch::<Sha256>()
3698 .write(key(14), Some(val(140)))
3699 .merkleize(&db1, None)
3700 .await
3701 .unwrap();
3702
3703 db1.apply_batch(c).await.unwrap();
3704 db1.commit().await.unwrap();
3705
3706 let d1 = db1
3708 .new_batch()
3709 .write(key(20), Some(val(200)))
3710 .merkleize(&db1, None)
3711 .await
3712 .unwrap();
3713 let chain_then_d_root = d1.root();
3714
3715 let ctx2 = context.child("db").with_attribute("index", 2);
3717 let mut db2: UnorderedVariableDb = UnorderedVariableDb::init(
3718 ctx2.child("db"),
3719 variable_config::<OneCap>("ord2", &ctx2),
3720 )
3721 .await
3722 .unwrap();
3723
3724 commit_writes_with_metadata(
3725 &mut db2,
3726 [(key(10), Some(val(10))), (key(11), Some(val(11)))],
3727 None,
3728 )
3729 .await;
3730
3731 let a2 = db2
3732 .new_batch()
3733 .write(key(10), Some(val(100)))
3734 .write(key(11), None)
3735 .merkleize(&db2, None)
3736 .await
3737 .unwrap();
3738 db2.apply_batch(a2).await.unwrap();
3739 db2.commit().await.unwrap();
3740
3741 let b2 = db2
3742 .new_batch()
3743 .write(key(12), Some(val(120)))
3744 .write(key(13), Some(val(130)))
3745 .merkleize(&db2, None)
3746 .await
3747 .unwrap();
3748 db2.apply_batch(b2).await.unwrap();
3749 db2.commit().await.unwrap();
3750
3751 let c2 = db2
3752 .new_batch()
3753 .write(key(14), Some(val(140)))
3754 .merkleize(&db2, None)
3755 .await
3756 .unwrap();
3757 db2.apply_batch(c2).await.unwrap();
3758 db2.commit().await.unwrap();
3759
3760 let d2 = db2
3761 .new_batch()
3762 .write(key(20), Some(val(200)))
3763 .merkleize(&db2, None)
3764 .await
3765 .unwrap();
3766 let sequential_then_d_root = d2.root();
3767
3768 assert_eq!(
3769 chain_then_d_root, sequential_then_d_root,
3770 "batch D's root on top of chain-applied state must match sequential state"
3771 );
3772
3773 db1.destroy().await.unwrap();
3774 db2.destroy().await.unwrap();
3775 });
3776 }
3777
3778 #[test_traced("WARN")]
3790 fn test_current_stale_bitmap_clears_after_prune() {
3791 let executor = deterministic::Runner::default();
3792 executor.start(|context| async move {
3793 let ctx = context.child("db");
3794 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3795 ctx.child("storage"),
3796 variable_config::<OneCap>("stale-clears", &ctx),
3797 )
3798 .await
3799 .unwrap();
3800
3801 let mut seed = db.new_batch();
3803 for i in 0u64..255 {
3804 seed = seed.write(key(i), Some(val(i)));
3805 }
3806 let seed_m = seed.merkleize(&db, None).await.unwrap();
3807 db.apply_batch(seed_m).await.unwrap();
3808 db.commit().await.unwrap();
3809
3810 let mut p = db.new_batch();
3813 for i in 1u64..255 {
3814 p = p.write(key(i), Some(val(i + 10000)));
3815 }
3816 let p_m = p.merkleize(&db, None).await.unwrap();
3817
3818 let c_m = p_m
3820 .new_batch::<Sha256>()
3821 .write(key(0), Some(val(9999)))
3822 .merkleize(&db, None)
3823 .await
3824 .unwrap();
3825
3826 db.apply_batch(p_m).await.unwrap();
3828 db.commit().await.unwrap();
3829
3830 let floor = *db.inactivity_floor_loc();
3831 assert!(floor >= 256, "floor must be past chunk 0: floor={floor}",);
3832
3833 db.prune(db.sync_boundary()).await.unwrap();
3834 db.apply_batch(c_m).await.unwrap();
3835
3836 db.destroy().await.unwrap();
3837 });
3838 }
3839
3840 #[test_traced("INFO")]
3843 fn test_current_partial_ancestor_commit() {
3844 let executor = deterministic::Runner::default();
3845 executor.start(|context| async move {
3846 let ctx = context.child("db");
3847 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3848 ctx.child("storage"),
3849 variable_config::<OneCap>("pac", &ctx),
3850 )
3851 .await
3852 .unwrap();
3853
3854 let a = db
3855 .new_batch()
3856 .write(key(0), Some(val(0)))
3857 .merkleize(&db, None)
3858 .await
3859 .unwrap();
3860 let b = a
3861 .new_batch::<Sha256>()
3862 .write(key(1), Some(val(1)))
3863 .merkleize(&db, None)
3864 .await
3865 .unwrap();
3866 let c = b
3867 .new_batch::<Sha256>()
3868 .write(key(2), Some(val(2)))
3869 .merkleize(&db, None)
3870 .await
3871 .unwrap();
3872
3873 let expected_root = c.root();
3874
3875 db.apply_batch(a).await.unwrap();
3876 db.apply_batch(c).await.unwrap();
3877
3878 assert_eq!(db.root(), expected_root);
3879 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3880 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3881 assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
3882
3883 db.destroy().await.unwrap();
3884 });
3885 }
3886
3887 #[test_traced("INFO")]
3891 fn test_current_partial_ancestor_bitmap_ordering() {
3892 let executor = deterministic::Runner::default();
3893 executor.start(|context| async move {
3894 let ctx = context.child("db");
3895 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3896 ctx.child("storage"),
3897 variable_config::<OneCap>("bmo", &ctx),
3898 )
3899 .await
3900 .unwrap();
3901
3902 let a = db
3904 .new_batch()
3905 .write(key(0), Some(val(0)))
3906 .merkleize(&db, None)
3907 .await
3908 .unwrap();
3909 let b = a
3910 .new_batch::<Sha256>()
3911 .write(key(1), Some(val(1)))
3912 .merkleize(&db, None)
3913 .await
3914 .unwrap();
3915 let c = b
3916 .new_batch::<Sha256>()
3917 .write(key(2), Some(val(2)))
3918 .merkleize(&db, None)
3919 .await
3920 .unwrap();
3921 let d = c
3922 .new_batch::<Sha256>()
3923 .write(key(3), Some(val(3)))
3924 .merkleize(&db, None)
3925 .await
3926 .unwrap();
3927
3928 db.apply_batch(a).await.unwrap();
3932 db.apply_batch(d.clone()).await.unwrap();
3933
3934 let e = db
3939 .new_batch()
3940 .write(key(4), Some(val(4)))
3941 .merkleize(&db, None)
3942 .await
3943 .unwrap();
3944 db.apply_batch(e).await.unwrap();
3945
3946 let ref_ctx = context.child("ref");
3948 let mut ref_db: UnorderedVariableDb = UnorderedVariableDb::init(
3949 ref_ctx.child("db"),
3950 variable_config::<OneCap>("bmo_ref", &ref_ctx),
3951 )
3952 .await
3953 .unwrap();
3954 for i in 0..5 {
3955 let batch = ref_db
3956 .new_batch()
3957 .write(key(i), Some(val(i)))
3958 .merkleize(&ref_db, None)
3959 .await
3960 .unwrap();
3961 ref_db.apply_batch(batch).await.unwrap();
3962 }
3963
3964 assert_eq!(
3965 db.root(),
3966 ref_db.root(),
3967 "root mismatch: bitmap ordering bug"
3968 );
3969
3970 db.destroy().await.unwrap();
3971 ref_db.destroy().await.unwrap();
3972 });
3973 }
3974
3975 #[test_traced("INFO")]
3985 fn test_current_apply_chunks_match_speculative_chunks() {
3986 const N: usize = 32;
3987 const CHUNK_SIZE_BITS: u64 = commonware_utils::bitmap::Prunable::<N>::CHUNK_SIZE_BITS;
3988 const SEED_KEYS: u64 = CHUNK_SIZE_BITS + 50;
3991
3992 let executor = deterministic::Runner::default();
3993 executor.start(|context| async move {
3994 let ctx = context.child("db");
3995 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3996 ctx.child("storage"),
3997 variable_config::<OneCap>("spec_eq", &ctx),
3998 )
3999 .await
4000 .unwrap();
4001
4002 let seed = (0..SEED_KEYS).fold(db.new_batch(), |b, i| b.write(key(i), Some(val(i))));
4004 let seed = seed.merkleize(&db, None).await.unwrap();
4005 db.apply_batch(seed).await.unwrap();
4006 db.commit().await.unwrap();
4007
4008 assert!(
4010 Readable::<N>::len(db.any.bitmap.as_ref()) > CHUNK_SIZE_BITS,
4011 "setup must cross a chunk boundary",
4012 );
4013
4014 let parent = db
4016 .new_batch()
4017 .write(key(10), Some(val(110))) .write(key(50), None) .write(key(CHUNK_SIZE_BITS + 5), Some(val(120))) .write(key(SEED_KEYS), Some(val(130))) .write(key(SEED_KEYS + 1), Some(val(131))) .merkleize(&db, None)
4023 .await
4024 .unwrap();
4025
4026 let child = parent
4031 .new_batch::<Sha256>()
4032 .write(key(10), Some(val(210)))
4033 .write(key(SEED_KEYS), None)
4034 .write(key(75), None)
4035 .write(key(CHUNK_SIZE_BITS + 30), Some(val(220)))
4036 .merkleize(&db, None)
4037 .await
4038 .unwrap();
4039
4040 let speculative_chunks: Vec<[u8; N]> = {
4042 let len = Readable::<N>::len(&child.bitmap);
4043 let chunk_count = len.div_ceil(CHUNK_SIZE_BITS) as usize;
4044 (0..chunk_count)
4045 .map(|idx| Readable::<N>::get_chunk(&child.bitmap, idx))
4046 .collect()
4047 };
4048 assert!(speculative_chunks.len() >= 2);
4050
4051 db.apply_batch(child).await.unwrap();
4055 let committed_chunks: Vec<[u8; N]> = {
4056 let len = Readable::<N>::len(db.any.bitmap.as_ref());
4057 let chunk_count = len.div_ceil(CHUNK_SIZE_BITS) as usize;
4058 (0..chunk_count)
4059 .map(|idx| Readable::<N>::get_chunk(db.any.bitmap.as_ref(), idx))
4060 .collect()
4061 };
4062
4063 assert_eq!(
4064 speculative_chunks, committed_chunks,
4065 "speculative chunks must equal post-apply committed chunks across all chunks",
4066 );
4067
4068 db.destroy().await.unwrap();
4069 });
4070 }
4071
4072 #[test_traced("INFO")]
4074 fn test_current_mmb_ops_historical_proof_verifies_with_backward_bagging() {
4075 let executor = deterministic::Runner::default();
4076 executor.start(|context| async move {
4077 let ctx = context.child("db");
4078 let mut db: UnorderedFixedMmbDb = UnorderedFixedMmbDb::init(
4079 ctx.child("storage"),
4080 fixed_config::<OneCap>("mmb-ops-proof", &ctx),
4081 )
4082 .await
4083 .unwrap();
4084
4085 let writes: Vec<(Digest, Option<Digest>)> =
4087 (0u64..16).map(|i| (key(i), Some(val(i)))).collect();
4088 commit_writes(&mut db, writes).await.unwrap();
4089
4090 let ops_root = db.ops_root();
4091 let historical_size = db.bounds().end;
4092 let (proof, ops) = db
4093 .ops_historical_proof(historical_size, Location::new(0), NZU64!(32))
4094 .await
4095 .unwrap();
4096
4097 assert!(verify_proof::<Sha256, _, _>(
4099 &proof,
4100 Location::new(0),
4101 &ops,
4102 &ops_root
4103 ));
4104
4105 db.destroy().await.unwrap();
4106 });
4107 }
4108}