1use crate::{
329 Context,
330 index::Factory as IndexFactory,
331 journal::{
332 authenticated,
333 contiguous::{fixed::Config as FConfig, variable::Config as VConfig},
334 },
335 merkle::{self, Location, full::Config as MerkleConfig},
336 qmdb::{
337 any::{
338 self, Config as AnyConfig,
339 operation::{Operation, Update},
340 },
341 bitmap::Shared,
342 },
343 translator::Translator,
344};
345use commonware_codec::{Codec, FixedSize};
346use commonware_cryptography::Hasher;
347use commonware_macros::boxed;
348use commonware_parallel::Strategy;
349use commonware_runtime::Spawner;
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, B = ()> {
368 pub merkle_config: MerkleConfig<S>,
370
371 pub journal_config: J,
373
374 pub grafted_metadata_partition: String,
376
377 pub translator: T,
380
381 pub init_cache: Option<NonZeroUsize>,
384
385 pub init_buffer: NonZeroUsize,
387
388 pub init_concurrency: B,
394}
395
396impl<T: Translator, J, S: Strategy, B> From<Config<T, J, S, B>> for AnyConfig<T, J, S, B> {
397 fn from(cfg: Config<T, J, S, B>) -> Self {
398 Self {
399 merkle_config: cfg.merkle_config,
400 journal_config: cfg.journal_config,
401 translator: cfg.translator,
402 init_cache: cfg.init_cache,
403 init_buffer: cfg.init_buffer,
404 init_concurrency: cfg.init_concurrency,
405 }
406 }
407}
408
409pub type FixedConfig<T, S, B = ()> = Config<T, FConfig, S, B>;
411
412pub type VariableConfig<T, C, S, B = ()> = Config<T, VConfig<C>, S, B>;
414
415#[boxed]
417pub(super) async fn init<F, E, U, H, I, J, const N: usize, S>(
418 context: E,
419 config: Config<I::Translator, J::Config, S, <I as crate::qmdb::SnapshotBuild<F>>::Concurrency>,
420 max_size: Option<Location<F>>,
421) -> Result<db::Db<F, E, J, I, H, U, N, S>, crate::qmdb::Error<F>>
422where
423 F: merkle::Graftable,
424 E: Context + Spawner,
425 U: Update,
426 H: Hasher,
427 I: IndexFactory<Value = Location<F>> + crate::qmdb::SnapshotBuild<F>,
428 J: authenticated::Backing<E, Item = Operation<F, U>> + 'static,
429 S: Strategy,
430 Operation<F, U>: Codec,
431{
432 const {
434 assert!(
438 N.is_multiple_of(H::Digest::SIZE),
439 "chunk size must be some multiple of the digest size",
440 );
441 assert!(N.is_power_of_two(), "chunk size must be a power of 2");
444 }
445
446 let strategy = config.merkle_config.strategy.clone();
447 let metadata_partition = config.grafted_metadata_partition.clone();
448
449 let (metadata, pruned_chunks, pinned_nodes) =
451 db::init_metadata(context.child("metadata"), &metadata_partition).await?;
452
453 let bitmap = BitMap::<N>::new_with_pruned_chunks(pruned_chunks)
455 .map_err(|_| crate::qmdb::Error::<F>::DataCorrupted("pruned chunks overflow"))?;
456 let bitmap = Arc::new(Shared::<N>::new(bitmap));
457
458 let any = any::init_with_bitmap(
463 context.child("any"),
464 config.into(),
465 Some(bitmap),
466 max_size,
467 db::pair_absorption_threshold::<F, N>(pruned_chunks as u64),
468 )
469 .await?;
470
471 let (grafted_tree, graft_roots, root) = db::rebuild_grafted_tree::<F, H, S, N>(
474 any.bitmap.as_ref(),
475 &pinned_nodes,
476 &any.log.merkle,
477 any.inactivity_floor_loc,
478 any.root(),
479 &strategy,
480 )
481 .await?;
482
483 let metrics = Metrics::new(context);
484 let db = db::Db {
485 any,
486 grafted_tree: Arc::new(grafted_tree),
487 graft_roots,
488 metadata,
489 strategy,
490 root,
491 metrics,
492 #[cfg(test)]
493 halt_before_prune_log: false,
494 };
495 db.update_metrics();
496 Ok(db)
497}
498
499#[cfg(any(test, feature = "test-traits"))]
501pub trait BitmapPrunedBits {
502 fn pruned_bits(&self) -> u64;
504
505 fn get_bit(&self, index: u64) -> bool;
507
508 fn oldest_retained(&self) -> u64;
510}
511
512#[cfg(test)]
513pub mod tests {
514 pub use super::BitmapPrunedBits;
517 use super::{
518 Codec, FConfig, FixedConfig, MerkleConfig, Operation, Strategy, Update, VConfig,
519 VariableConfig, batch, db, grafting, ordered, unordered,
520 };
521 use crate::{
522 index::{Ordered as OrderedIndex, Unordered as UnorderedIndex},
523 journal::contiguous::Mutable,
524 merkle::{self, mmb, mmr, storage::Storage as _},
525 qmdb::{
526 any::{
527 ValueEncoding,
528 batch::Slot,
529 operation::update,
530 test::{
531 Changes, Choice, Inspect, Links, Neighbors, Script, assert_bits, assert_exact,
532 build, colliding_digest, counter, hold, hold_batch, live, replay,
533 test_any_activity_depths, test_any_ordered_policy_eviction_matrix,
534 test_any_ordered_policy_repair_across_ancestors,
535 test_any_policy_ancestor_twins, test_any_policy_decisions_match_writes,
536 test_any_policy_evicts_parent_created_key, test_any_policy_hold,
537 test_any_policy_keep_evict_and_recover,
538 test_any_policy_limits_after_colliding_writes,
539 test_any_policy_limits_with_writes, test_any_policy_matches_proportional,
540 test_any_policy_own_writes, test_any_proportional_bound,
541 test_any_proportional_one_batch,
542 },
543 traits::{DbAny, MerkleizedBatch as _, UnmerkleizedBatch as _},
544 },
545 chain::Bounds,
546 floor::{Bounded, Hold, Proportional},
547 store::tests::{TestKey, TestValue},
548 verify_proof,
549 },
550 translator::Translator,
551 };
552 use commonware_parallel::Sequential;
553 use commonware_runtime::{
554 Runner as _, Supervisor as _,
555 buffer::paged::CacheRef,
556 deterministic::{self, Context},
557 };
558 use commonware_utils::{NZU16, NZU64, NZUsize, TestRng, bitmap::Readable};
559 use core::future::Future;
560 use ordered::tests::test_build_small_close_reopen as test_ordered_build_small_close_reopen;
561 use rand::Rng;
562 use std::{
563 collections::BTreeMap,
564 num::{NonZeroU16, NonZeroUsize},
565 ops::Range,
566 pin::Pin,
567 sync::Arc,
568 };
569 use tracing::warn;
570 use unordered::tests::test_build_small_close_reopen as test_unordered_build_small_close_reopen;
571
572 type Error<F> = crate::qmdb::Error<F>;
573 type Location<F> = merkle::Location<F>;
574 type WriteVec<F, C> = Vec<(<C as DbAny<F>>::Key, Option<<C as DbAny<F>>::Value>)>;
575
576 const PAGE_SIZE: NonZeroU16 = NZU16!(88);
578 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(8);
579
580 macro_rules! staged_merkleize_parity_test {
592 ($name:ident, $open_db:path) => {
593 #[test_traced("WARN")]
594 pub fn $name() {
595 fn key(i: u64) -> Digest {
596 Sha256::hash(&[&i.to_be_bytes()])
597 }
598 fn val(i: u64) -> Digest {
599 Sha256::hash(&[&(i + 10000).to_be_bytes()])
600 }
601
602 deterministic::Runner::default().start(|ctx| async move {
603 let db = $open_db(ctx.child("current"), "staged-parity".to_string()).await;
604
605 let mut seed = db.new_batch();
606 for i in 0..2000u64 {
607 seed = seed.write(key(i), Some(val(i)));
608 }
609 let seed = seed.merkleize(&db, None, &mut Proportional).await.unwrap();
610 let db = db.apply_batch(seed).await.unwrap().commit().await.unwrap();
611
612 for depth in [0u8, 1u8, 2u8] {
613 let mut stack = Vec::new();
617 match depth {
618 0 => {}
619 1 => {
620 let mut p = db.new_batch();
621 for i in 0..50u64 {
622 p = p.write(key(i), Some(val(i + 1_000)));
623 }
624 for i in 100..110u64 {
625 p = p.write(key(i), None);
626 }
627 stack.push(
628 p.merkleize(&db, None, &mut Proportional)
629 .await
630 .unwrap(),
631 );
632 }
633 2 => {
634 let mut grandparent = db.new_batch();
635 for i in 0..10u64 {
636 grandparent = grandparent.write(key(i), Some(val(i + 1_000)));
637 }
638 for i in 100..110u64 {
639 grandparent = grandparent.write(key(i), None);
640 }
641 let grandparent = grandparent
642 .merkleize(&db, None, &mut Proportional)
643 .await
644 .unwrap();
645
646 let mut p = grandparent.new_batch::<Sha256>();
647 for i in 20..30u64 {
648 p = p.write(key(i), Some(val(i + 2_000)));
649 }
650 let p = p.merkleize(&db, None, &mut Proportional).await.unwrap();
651 stack.push(grandparent);
652 stack.push(p);
653 }
654 _ => unreachable!("covered depths"),
655 };
656 let new_batch = || {
657 stack
658 .last()
659 .map_or_else(|| db.new_batch(), |p| p.new_batch::<Sha256>())
660 };
661
662 let read_keys = [
666 key(5),
667 key(6),
668 key(9000),
669 key(5),
670 key(0),
671 key(20),
672 key(60),
673 key(105),
674 ];
675 let keys: Vec<&Digest> = read_keys.iter().collect();
676 let slots = vec![
677 Slot::Put(val(5_000)),
678 Slot::Keep,
679 Slot::Put(val(5_001)),
680 Slot::Put(val(5_002)),
681 Slot::Put(val(5_003)),
682 Slot::Delete,
683 Slot::Put(val(5_004)),
684 Slot::Put(val(5_005)),
685 ];
686 let unstaged = vec![
687 (key(7000), Some(val(6_000))),
688 (key(30), Some(val(6_001))),
689 (key(5), Some(val(6_002))),
690 (key(31), None),
691 ];
692
693 let mut explicit = new_batch();
694 let explicit_values = explicit.get_many(&keys, &db).await.unwrap();
695 for (key, slot) in read_keys.iter().zip(&slots) {
696 match slot {
697 Slot::Keep => {}
698 Slot::Put(value) => explicit = explicit.write(*key, Some(*value)),
699 Slot::Delete => explicit = explicit.write(*key, None),
700 }
701 }
702 for (k, v) in &unstaged {
703 explicit = explicit.write(*k, *v);
704 }
705 let explicit_root = explicit
706 .merkleize(&db, None, &mut Proportional)
707 .await
708 .unwrap()
709 .root();
710
711 let (staged_values, staged) = new_batch().stage(&keys, &db).await.unwrap();
712 let staged_root = staged
713 .merkleize(
714 slots.clone(),
715 unstaged.clone(),
716 None,
717 &db,
718 &mut Proportional,
719 )
720 .await
721 .unwrap()
722 .root();
723
724 assert_eq!(
725 explicit_values, staged_values,
726 "value mismatch at depth={depth}"
727 );
728 assert_eq!(explicit_root, staged_root, "root mismatch at depth={depth}");
729
730 let split = 3;
731 let (mut expanded_values, staged) =
732 new_batch().stage(&keys[..split], &db).await.unwrap();
733 let (range, suffix_values, staged) =
734 staged.expand(&keys[split..], &db).await.unwrap();
735 assert_eq!(range, split..keys.len());
736 assert_eq!(staged.len(), keys.len());
737 assert!(!staged.is_empty());
738 expanded_values.extend(suffix_values);
739 let expanded_root = staged
740 .merkleize(
741 slots.clone(),
742 unstaged.clone(),
743 None,
744 &db,
745 &mut Proportional,
746 )
747 .await
748 .unwrap()
749 .root();
750
751 assert_eq!(
752 explicit_values, expanded_values,
753 "expanded value mismatch at depth={depth}"
754 );
755 assert_eq!(
756 explicit_root, expanded_root,
757 "expanded root mismatch at depth={depth}"
758 );
759
760 let planned = val(7_000);
761 let duplicate_update = val(7_001);
762 let (first_values, staged) =
763 new_batch().stage(&keys[..1], &db).await.unwrap();
764 let (duplicate_range, duplicate_values, staged) =
765 staged.expand(&keys[..1], &db).await.unwrap();
766 assert_eq!(duplicate_range, 1..2);
767 assert_eq!(
768 first_values[0], duplicate_values[0],
769 "duplicate expansion must assign a new slot without changing the base read"
770 );
771 assert_ne!(
772 duplicate_values[0],
773 Some(planned),
774 "expand must not observe values computed for earlier staged slots"
775 );
776
777 let duplicate_root = staged
778 .merkleize(
779 vec![Slot::Put(planned), Slot::Put(duplicate_update)],
780 Vec::new(),
781 None,
782 &db,
783 &mut Proportional,
784 )
785 .await
786 .unwrap()
787 .root();
788 let expected_duplicate_root = new_batch()
789 .write(read_keys[0], Some(planned))
790 .write(read_keys[0], Some(duplicate_update))
791 .merkleize(&db, None, &mut Proportional)
792 .await
793 .unwrap()
794 .root();
795 assert_eq!(
796 expected_duplicate_root, duplicate_root,
797 "duplicate expanded slots should use normal update-order semantics"
798 );
799 }
800 });
801 }
802 };
803 }
804 pub(crate) use staged_merkleize_parity_test;
805
806 pub(crate) fn fixed_config<T: Translator + Default>(
808 partition_prefix: &str,
809 ) -> FixedConfig<T, Sequential> {
810 fixed_config_full(partition_prefix, ())
811 }
812
813 pub(crate) fn fixed_config_full<T: Translator + Default, B>(
816 partition_prefix: &str,
817 init_concurrency: B,
818 ) -> FixedConfig<T, Sequential, B> {
819 let page_cache = CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE);
820 FixedConfig {
821 merkle_config: MerkleConfig {
822 journal_partition: format!("{partition_prefix}-journal-partition"),
823 metadata_partition: format!("{partition_prefix}-metadata-partition"),
824 items_per_blob: NZU64!(11),
825 write_buffer: NZUsize!(1024),
826 replay_buffer: NZUsize!(1024),
827 strategy: Sequential,
828 page_cache: page_cache.clone(),
829 },
830 journal_config: FConfig {
831 partition: format!("{partition_prefix}-partition-prefix"),
832 items_per_blob: NZU64!(7),
833 page_cache,
834 write_buffer: NZUsize!(1024),
835 replay_buffer: NZUsize!(1024),
836 },
837 grafted_metadata_partition: format!("{partition_prefix}-grafted-metadata-partition"),
838 translator: T::default(),
839 init_cache: Some(NZUsize!(1024)),
840 init_buffer: NZUsize!(1 << 21),
841 init_concurrency,
842 }
843 }
844
845 pub(crate) fn variable_config<T: Translator + Default>(
847 partition_prefix: &str,
848 ) -> VariableConfig<T, ((), ()), Sequential> {
849 variable_config_full(partition_prefix, ())
850 }
851
852 pub(crate) fn variable_config_full<T: Translator + Default, B>(
855 partition_prefix: &str,
856 init_concurrency: B,
857 ) -> VariableConfig<T, ((), ()), Sequential, B> {
858 let page_cache = CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE);
859 VariableConfig {
860 merkle_config: MerkleConfig {
861 journal_partition: format!("{partition_prefix}-journal-partition"),
862 metadata_partition: format!("{partition_prefix}-metadata-partition"),
863 items_per_blob: NZU64!(11),
864 write_buffer: NZUsize!(1024),
865 replay_buffer: NZUsize!(1024),
866 strategy: Sequential,
867 page_cache: page_cache.clone(),
868 },
869 journal_config: VConfig {
870 partition: format!("{partition_prefix}-partition-prefix"),
871 items_per_section: NZU64!(7),
872 compression: None,
873 codec_config: ((), ()),
874 page_cache,
875 write_buffer: NZUsize!(1024),
876 replay_buffer: NZUsize!(1024),
877 },
878 grafted_metadata_partition: format!("{partition_prefix}-grafted-metadata-partition"),
879 translator: T::default(),
880 init_cache: Some(NZUsize!(1024)),
881 init_buffer: NZUsize!(1 << 21),
882 init_concurrency,
883 }
884 }
885
886 pub(crate) fn fixed_config_partitioned<T: Translator + Default>(
888 partition_prefix: &str,
889 ) -> FixedConfig<T, Sequential, NonZeroUsize> {
890 fixed_config_full(partition_prefix, NZUsize!(1))
891 }
892
893 pub(crate) fn variable_config_partitioned<T: Translator + Default>(
895 partition_prefix: &str,
896 ) -> VariableConfig<T, ((), ()), Sequential, NonZeroUsize> {
897 variable_config_full(partition_prefix, NZUsize!(1))
898 }
899
900 fn commit_writes<'a, F: merkle::Graftable, C: DbAny<F> + 'a>(
906 db: C,
907 writes: impl IntoIterator<Item = (C::Key, Option<<C as DbAny<F>>::Value>)> + 'a,
908 ) -> std::pin::Pin<Box<dyn Future<Output = Result<C, Error<F>>> + 'a>> {
909 Box::pin(async move {
910 let mut batch = db.new_batch();
911 for (k, v) in writes {
912 batch = batch.write(k, v);
913 }
914 let merkleized = batch.merkleize(&db, None, &mut Proportional).await?;
915 let db = db.apply_batch(merkleized).await?;
916 db.commit().await
917 })
918 }
919
920 #[boxed]
923 pub async fn apply_random_ops<F, C>(
924 num_elements: u64,
925 commit_changes: bool,
926 rng_seed: u64,
927 mut db: C,
928 ) -> Result<C, Error<F>>
929 where
930 F: merkle::Graftable,
931 C: DbAny<F>,
932 C::Key: TestKey,
933 <C as DbAny<F>>::Value: TestValue,
934 {
935 warn!("rng_seed={}", rng_seed);
937 let mut rng = TestRng::new(rng_seed);
938
939 let writes: Vec<_> = (0u64..num_elements)
941 .map(|i| {
942 let k = TestKey::from_seed(i);
943 let v = TestValue::from_seed(rng.next_u64());
944 (k, Some(v))
945 })
946 .collect();
947 if commit_changes {
948 db = commit_writes(db, writes).await?;
949 }
950
951 let mut pending: WriteVec<F, C> = Vec::new();
954 for _ in 0u64..num_elements * 10 {
955 let rand_key = TestKey::from_seed(rng.next_u64() % num_elements);
956 if rng.next_u32().is_multiple_of(7) {
957 pending.push((rand_key, None));
958 continue;
959 }
960 let v = TestValue::from_seed(rng.next_u64());
961 pending.push((rand_key, Some(v)));
962 if commit_changes && rng.next_u32().is_multiple_of(20) {
963 db = commit_writes(db, pending.drain(..)).await?;
964 }
965 }
966 if commit_changes {
967 db = commit_writes(db, pending).await?;
968 }
969 Ok(db)
970 }
971
972 #[boxed]
974 async fn build_random_close_reopen_round<M, C, F, Fut>(
975 mut context: Context,
976 mut open_db: F,
977 ) -> String
978 where
979 M: merkle::Graftable + 'static,
980 C: DbAny<M> + 'static,
981 C::Key: TestKey,
982 <C as DbAny<M>>::Value: TestValue,
983 F: FnMut(Context, String) -> Fut,
984 Fut: Future<Output = C>,
985 {
986 const ELEMENTS: u64 = 1000;
987
988 let partition = "build-random".to_string();
989 let rng_seed = context.next_u64();
990 let db: C = open_db(context.child("first"), partition.clone()).await;
991 let db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
992 .await
993 .unwrap();
994 let merkleized = db
995 .new_batch()
996 .merkleize(&db, None, &mut Proportional)
997 .await
998 .unwrap();
999 let db = db.apply_batch(merkleized).await.unwrap();
1000 let db = db.sync().await.unwrap();
1001
1002 let root = db.root();
1004 drop(db);
1005 let db: C = open_db(context.child("second"), partition).await;
1006
1007 assert_eq!(db.root(), root);
1009
1010 db.destroy().await.unwrap();
1011 context.auditor().state()
1012 }
1013
1014 pub async fn test_build_random_close_reopen<M, C, F, Fut>(context: Context, open_db: F)
1019 where
1020 M: merkle::Graftable,
1021 C: DbAny<M> + 'static,
1022 C::Key: TestKey,
1023 <C as DbAny<M>>::Value: TestValue,
1024 F: FnMut(Context, String) -> Fut + Clone,
1025 Fut: Future<Output = C>,
1026 {
1027 let state1 =
1029 build_random_close_reopen_round::<M, C, F, Fut>(context, open_db.clone()).await;
1030
1031 let executor = deterministic::Runner::default();
1033 let state2 = executor
1034 .start(|context| build_random_close_reopen_round::<M, C, F, Fut>(context, open_db));
1035
1036 assert_eq!(state1, state2);
1037 }
1038
1039 pub async fn test_commit_after_sync_recovery<M, C, F, Fut>(context: Context, mut open_db: F)
1041 where
1042 M: merkle::Graftable,
1043 C: DbAny<M> + 'static,
1044 C::Key: TestKey,
1045 <C as DbAny<M>>::Value: TestValue,
1046 F: FnMut(Context, String) -> Fut + Clone,
1047 Fut: Future<Output = C>,
1048 {
1049 let mut open_db_clone = open_db.clone();
1050 let partition = "commit-after-sync".to_string();
1051 let db: C = Box::pin(open_db_clone(context.child("first"), partition.clone())).await;
1052 let key0 = <<C as DbAny<M>>::Key as TestKey>::from_seed(0);
1053 let key1 = <<C as DbAny<M>>::Key as TestKey>::from_seed(1);
1054 let value0 = <C as DbAny<M>>::Value::from_seed(100);
1055 let value1 = <C as DbAny<M>>::Value::from_seed(200);
1056
1057 let db = commit_writes::<M, C>(db, [(key0, Some(value0.clone()))])
1059 .await
1060 .unwrap();
1061 let db = db.sync().await.unwrap();
1062
1063 let db = commit_writes::<M, C>(db, [(key1, Some(value1.clone()))])
1065 .await
1066 .unwrap();
1067 let committed_root = db.root();
1068 let committed_size = db.size();
1069 drop(db);
1070
1071 let db: C = Box::pin(open_db(context.child("second"), partition)).await;
1072 assert_eq!(db.root(), committed_root);
1073 assert_eq!(db.size(), committed_size);
1074 assert_eq!(db.get(&key0).await.unwrap(), Some(value0));
1075 assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
1076
1077 db.destroy().await.unwrap();
1078 }
1079
1080 pub async fn test_simulate_write_failures<M, C, F, Fut>(mut context: Context, mut open_db: F)
1085 where
1086 M: merkle::Graftable,
1087 C: DbAny<M> + 'static,
1088 C::Key: TestKey,
1089 <C as DbAny<M>>::Value: TestValue,
1090 F: FnMut(Context, String) -> Fut + Clone,
1091 Fut: Future<Output = C>,
1092 {
1093 const ELEMENTS: u64 = 1000;
1094
1095 let partition = "build-random-fail-commit".to_string();
1096 let rng_seed = context.next_u64();
1097 let db: C = Box::pin(open_db(context.child("first"), partition.clone())).await;
1098 let db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1099 .await
1100 .unwrap();
1101 let db = commit_writes(db, []).await.unwrap();
1102 let committed_root = db.root();
1103 let committed_op_count = db.bounds().end;
1104 let boundary = db.sync_boundary();
1105 let db = db.prune(boundary).await.unwrap();
1106
1107 let db = apply_random_ops::<M, C>(ELEMENTS, false, rng_seed + 1, db)
1109 .await
1110 .unwrap();
1111
1112 drop(db);
1115 let db: C = Box::pin(open_db(
1116 context.child("scenario").with_attribute("index", 1),
1117 partition.clone(),
1118 ))
1119 .await;
1120 assert_eq!(db.root(), committed_root);
1121 assert_eq!(db.bounds().end, committed_op_count);
1122
1123 let db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed + 1, db)
1125 .await
1126 .unwrap();
1127
1128 let committed_op_count = db.bounds().end;
1132 drop(db);
1133
1134 let db: C = Box::pin(open_db(
1137 context.child("scenario").with_attribute("index", 2),
1138 partition.clone(),
1139 ))
1140 .await;
1141 let scenario_2_root = db.root();
1142
1143 let fresh_partition = "build-random-fail-commit-fresh".to_string();
1146 let db: C = Box::pin(open_db(context.child("fresh"), fresh_partition.clone())).await;
1147 let db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1148 .await
1149 .unwrap();
1150 let db = commit_writes(db, []).await.unwrap();
1151 let db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed + 1, db)
1152 .await
1153 .unwrap();
1154 let boundary = db.sync_boundary();
1155 let db = db.prune(boundary).await.unwrap();
1156 assert_eq!(db.bounds().end, committed_op_count);
1158 assert_eq!(db.root(), scenario_2_root);
1159
1160 db.destroy().await.unwrap();
1161 }
1162
1163 pub async fn test_different_pruning_delays_same_root<M, C, F, Fut>(
1168 context: Context,
1169 mut open_db: F,
1170 ) where
1171 M: merkle::Graftable,
1172 C: DbAny<M>,
1173 C::Key: TestKey,
1174 <C as DbAny<M>>::Value: TestValue,
1175 F: FnMut(Context, String) -> Fut + Clone,
1176 Fut: Future<Output = C>,
1177 {
1178 const NUM_OPERATIONS: u64 = 1000;
1179
1180 let mut open_db_clone = open_db.clone();
1181 let mut db_no_pruning: C = Box::pin(open_db_clone(
1183 context.child("no_pruning"),
1184 "no-pruning-test".into(),
1185 ))
1186 .await;
1187 let mut db_pruning: C =
1188 Box::pin(open_db(context.child("pruning"), "pruning-test".into())).await;
1189
1190 let mut pending_no_pruning: WriteVec<M, C> = Vec::new();
1193 let mut pending_pruning: WriteVec<M, C> = Vec::new();
1194 for i in 0..NUM_OPERATIONS {
1195 let key: C::Key = TestKey::from_seed(i);
1196 let value: <C as DbAny<M>>::Value = TestValue::from_seed(i * 1000);
1197
1198 pending_no_pruning.push((key, Some(value.clone())));
1199 pending_pruning.push((key, Some(value)));
1200
1201 if i % 50 == 49 {
1203 db_no_pruning = commit_writes(db_no_pruning, pending_no_pruning.drain(..))
1204 .await
1205 .unwrap();
1206 db_pruning = commit_writes(db_pruning, pending_pruning.drain(..))
1207 .await
1208 .unwrap();
1209 db_pruning = db_pruning
1210 .prune(db_no_pruning.sync_boundary())
1211 .await
1212 .unwrap();
1213 }
1214 }
1215
1216 let db_no_pruning = commit_writes(db_no_pruning, pending_no_pruning)
1218 .await
1219 .unwrap();
1220 let db_pruning = commit_writes(db_pruning, pending_pruning).await.unwrap();
1221
1222 let root_no_pruning = db_no_pruning.root();
1224 let root_pruning = db_pruning.root();
1225 assert_eq!(root_no_pruning, root_pruning);
1226
1227 assert_eq!(
1229 db_no_pruning.inactivity_floor_loc(),
1230 db_pruning.inactivity_floor_loc()
1231 );
1232
1233 db_no_pruning.destroy().await.unwrap();
1234 db_pruning.destroy().await.unwrap();
1235 }
1236
1237 pub async fn test_sync_persists_bitmap_pruning_boundary<M, C, F, Fut>(
1243 mut context: Context,
1244 mut open_db: F,
1245 ) where
1246 M: merkle::Graftable,
1247 C: DbAny<M> + BitmapPrunedBits + 'static,
1248 C::Key: TestKey,
1249 <C as DbAny<M>>::Value: TestValue,
1250 F: FnMut(Context, String) -> Fut + Clone,
1251 Fut: Future<Output = C>,
1252 {
1253 const ELEMENTS: u64 = 500;
1254
1255 let mut open_db_clone = open_db.clone();
1256 let partition = "sync-bitmap-pruning".to_string();
1257 let rng_seed = context.next_u64();
1258 let db: C = Box::pin(open_db_clone(context.child("first"), partition.clone())).await;
1259
1260 let db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1262 .await
1263 .unwrap();
1264 let merkleized = db
1265 .new_batch()
1266 .merkleize(&db, None, &mut Proportional)
1267 .await
1268 .unwrap();
1269 let db = db.apply_batch(merkleized).await.unwrap();
1270
1271 let boundary = db.sync_boundary();
1273 let db = db.prune(boundary).await.unwrap();
1274
1275 let pruned_bits_before = db.pruned_bits();
1276 warn!(
1277 "pruned_bits_before={}, inactivity_floor={}, op_count={}",
1278 pruned_bits_before,
1279 *db.inactivity_floor_loc(),
1280 *db.bounds().end
1281 );
1282
1283 assert!(
1285 pruned_bits_before > 0,
1286 "Expected bitmap to have pruned bits after prune()"
1287 );
1288
1289 let db = db.sync().await.unwrap();
1291
1292 let root_before = db.root();
1294 drop(db);
1295
1296 let db: C = Box::pin(open_db(context.child("second"), partition)).await;
1298
1299 let pruned_bits_after = db.pruned_bits();
1302 warn!("pruned_bits_after={}", pruned_bits_after);
1303
1304 assert_eq!(
1305 pruned_bits_after, pruned_bits_before,
1306 "Bitmap pruned bits mismatch after reopen - sync() may not have called write_pruned()"
1307 );
1308
1309 assert_eq!(db.root(), root_before);
1311
1312 db.destroy().await.unwrap();
1313 }
1314
1315 pub async fn test_current_db_build_big<M, C, F, Fut>(context: Context, mut open_db: F)
1321 where
1322 M: merkle::Graftable,
1323 C: DbAny<M>,
1324 C::Key: TestKey,
1325 <C as DbAny<M>>::Value: TestValue,
1326 F: FnMut(Context, String) -> Fut + Clone,
1327 Fut: Future<Output = C>,
1328 {
1329 const ELEMENTS: u64 = 1000;
1330
1331 let mut open_db_clone = open_db.clone();
1332 let db: C = Box::pin(open_db_clone(context.child("first"), "build-big".into())).await;
1333
1334 let mut map = std::collections::HashMap::<C::Key, <C as DbAny<M>>::Value>::default();
1335
1336 let mut batch = db.new_batch();
1338
1339 for i in 0u64..ELEMENTS {
1341 let k: C::Key = TestKey::from_seed(i);
1342 let v: <C as DbAny<M>>::Value = TestValue::from_seed(i * 1000);
1343 batch = batch.write(k, Some(v.clone()));
1344 map.insert(k, v);
1345 }
1346
1347 for i in 0u64..ELEMENTS {
1349 if i % 3 != 0 {
1350 continue;
1351 }
1352 let k: C::Key = TestKey::from_seed(i);
1353 let v: <C as DbAny<M>>::Value = TestValue::from_seed((i + 1) * 10000);
1354 batch = batch.write(k, Some(v.clone()));
1355 map.insert(k, v);
1356 }
1357
1358 for i in 0u64..ELEMENTS {
1360 if i % 7 != 1 {
1361 continue;
1362 }
1363 let k: C::Key = TestKey::from_seed(i);
1364 batch = batch.write(k, None);
1365 map.remove(&k);
1366 }
1367
1368 let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
1369 let db = db.apply_batch(merkleized).await.unwrap();
1370
1371 let db = db.sync().await.unwrap();
1373 let boundary = db.sync_boundary();
1374 let db = db.prune(boundary).await.unwrap();
1375
1376 let root = db.root();
1378 db.sync().await.unwrap();
1379
1380 let db: C = Box::pin(open_db(context.child("second"), "build-big".into())).await;
1382 assert_eq!(root, db.root());
1383
1384 for i in 0u64..ELEMENTS {
1386 let k: C::Key = TestKey::from_seed(i);
1387 if let Some(map_value) = map.get(&k) {
1388 let Some(db_value) = db.get(&k).await.unwrap() else {
1389 panic!("key not found in db: {k}");
1390 };
1391 assert_eq!(*map_value, db_value);
1392 } else {
1393 assert!(db.get(&k).await.unwrap().is_none());
1394 }
1395 }
1396 }
1397
1398 pub async fn test_stale_batch_side_effect_free<M, C, F, Fut>(context: Context, mut open_db: F)
1402 where
1403 M: merkle::Graftable,
1404 C: DbAny<M>,
1405 C::Key: TestKey,
1406 <C as DbAny<M>>::Value: TestValue,
1407 F: FnMut(Context, String) -> Fut,
1408 Fut: Future<Output = C>,
1409 {
1410 let db: C = Box::pin(open_db(
1411 context.child("db"),
1412 "stale-side-effect-free".into(),
1413 ))
1414 .await;
1415
1416 let key1 = <C::Key as TestKey>::from_seed(1);
1417 let key2 = <C::Key as TestKey>::from_seed(2);
1418 let value1 = <<C as DbAny<M>>::Value as TestValue>::from_seed(10);
1419 let value2 = <<C as DbAny<M>>::Value as TestValue>::from_seed(20);
1420
1421 let mut batch = db.new_batch();
1422 batch = batch.write(key1, Some(value1.clone()));
1423 let batch_a = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
1424 let mut batch = db.new_batch();
1425 batch = batch.write(key2, Some(value2));
1426 let batch_b = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
1427
1428 let db = db.apply_batch(batch_a).await.unwrap();
1429 let db = db.commit().await.unwrap();
1430 let expected_root = db.root();
1431 let expected_bounds = db.bounds();
1432 let expected_metadata = db.get_metadata().await.unwrap();
1433 assert_eq!(db.get(&key1).await.unwrap(), Some(value1.clone()));
1434 assert_eq!(db.get(&key2).await.unwrap(), None);
1435
1436 let Err(err) = db.apply_batch(batch_b).await else {
1437 panic!("expected StaleBatch error");
1438 };
1439 assert!(
1440 matches!(err, Error::StaleBatch),
1441 "expected StaleBatch error, got {err:?}"
1442 );
1443
1444 let db: C = Box::pin(open_db(
1446 context.child("reopen"),
1447 "stale-side-effect-free".into(),
1448 ))
1449 .await;
1450 assert_eq!(db.root(), expected_root);
1451 assert_eq!(db.bounds(), expected_bounds);
1452 assert_eq!(db.get_metadata().await.unwrap(), expected_metadata);
1453 assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
1454 assert_eq!(db.get(&key2).await.unwrap(), None);
1455
1456 db.destroy().await.unwrap();
1457 }
1458
1459 struct Observed<M: merkle::Family> {
1461 root: Digest,
1463 bounds: Range<Location<M>>,
1465 floor: Location<M>,
1467 pruned: u64,
1469 bits: Vec<bool>,
1471 }
1472
1473 impl<M: merkle::Family> Observed<M> {
1474 fn capture<C>(db: &C) -> Self
1476 where
1477 C: DbAny<M, Digest = Digest> + BitmapPrunedBits,
1478 {
1479 let bounds = db.bounds();
1480 let pruned = db.pruned_bits();
1481 Self {
1482 root: db.root(),
1483 floor: db.inactivity_floor_loc(),
1484 bits: (pruned..*bounds.end).map(|i| db.get_bit(i)).collect(),
1485 pruned,
1486 bounds,
1487 }
1488 }
1489
1490 fn assert_bits(&self, expected: &Self, label: &str) {
1492 assert_eq!(
1493 self.bounds.end, expected.bounds.end,
1494 "{label}: size diverged",
1495 );
1496 let from = self.pruned.max(expected.pruned);
1497 assert_eq!(
1498 self.bits[(from - self.pruned) as usize..],
1499 expected.bits[(from - expected.pruned) as usize..],
1500 "{label}: activity bits diverged",
1501 );
1502 }
1503 }
1504
1505 fn indexed(i: u64) -> Digest {
1509 colliding_digest((i / 4) as u8, i)
1510 }
1511
1512 pub async fn test_chained_schedules_match_sequential<M, U, const N: usize, S, C, F, Fut>(
1517 context: Context,
1518 mut open_db: F,
1519 ) where
1520 M: merkle::Graftable,
1521 U: Update,
1522 S: Strategy,
1523 Operation<M, U>: Codec,
1524 C: DbAny<
1525 M,
1526 Key = Digest,
1527 Value = Digest,
1528 Digest = Digest,
1529 Merkleized = Arc<batch::MerkleizedBatch<M, Digest, U, N, S>>,
1530 Batch = batch::UnmerkleizedBatch<M, Sha256, U, N, S>,
1531 > + BitmapPrunedBits,
1532 F: FnMut(Context, String) -> Fut,
1533 Fut: Future<Output = C>,
1534 {
1535 const SEED: u64 = 2 * CHUNK_BITS + 50;
1536 let k0 = indexed(0);
1537 let k1 = indexed(2 * CHUNK_BITS + 10);
1538 let kx = indexed(SEED);
1539 let c1 = indexed(CHUNK_BITS + 50);
1540 let fresh = SEED + 1;
1541 let keys: Vec<_> = (0..=fresh + CHUNK_BITS).map(indexed).collect();
1542
1543 let seed: WriteVec<M, C> = (0..SEED).map(|i| (indexed(i), Some(val(i)))).collect();
1547
1548 let mut a: WriteVec<M, C> = (0..CHUNK_BITS)
1550 .map(|i| (indexed(i), Some(val(SEED + i))))
1551 .collect();
1552 a.extend([(k1, None), (kx, Some(val(5000)))]);
1553
1554 let mut b: WriteVec<M, C> = vec![(k0, Some(val(5001))), (k1, Some(val(5002))), (kx, None)];
1556 b.extend((fresh..fresh + CHUNK_BITS).map(|i| (indexed(i), Some(val(i)))));
1557
1558 let e: WriteVec<M, C> = Vec::new();
1560 let c: WriteVec<M, C> = vec![(k0, Some(val(5003))), (k1, None), (c1, Some(val(5004)))];
1561 let d: WriteVec<M, C> = vec![(k0, Some(val(5005))), (kx, Some(val(5006)))];
1562
1563 let f: WriteVec<M, C> = vec![
1565 (k0, Some(val(5007))),
1566 (c1, Some(val(5008))),
1567 (indexed(fresh + CHUNK_BITS), Some(val(5009))),
1568 ];
1569
1570 let reference: C =
1572 Box::pin(open_db(context.child("reference"), "chained-ref".into())).await;
1573 let reference = commit_writes(reference, seed.clone()).await.unwrap();
1574 let reference = commit_writes(reference, a.clone()).await.unwrap();
1575 let size_a = *reference.size();
1576 let reference = commit_writes(reference, b.clone()).await.unwrap();
1577 let size_b = *reference.size();
1578 let reference = commit_writes(reference, e.clone()).await.unwrap();
1579 let reference = commit_writes(reference, c.clone()).await.unwrap();
1580 let size_c = *reference.size();
1581 let reference = commit_writes(reference, d.clone()).await.unwrap();
1582
1583 assert!(
1585 size_b / CHUNK_BITS > size_a / CHUNK_BITS,
1586 "B must complete a chunk",
1587 );
1588 assert_eq!(
1589 size_c / CHUNK_BITS,
1590 size_b / CHUNK_BITS,
1591 "E and C must not complete a chunk",
1592 );
1593 let expected = Observed::capture(&reference);
1594
1595 let f_batch = build(&reference, reference.new_batch(), &f).await;
1597 let f_root = f_batch.root();
1598 let reference = reference.apply_batch(f_batch).await.unwrap();
1599 let expected_f = Observed::capture(&reference);
1600 let mut values = Vec::with_capacity(keys.len());
1601 for key in &keys {
1602 values.push(reference.get(key).await.unwrap());
1603 }
1604 reference.destroy().await.unwrap();
1605
1606 for schedule in 1..=5u64 {
1607 let partition = format!("chained-{schedule}");
1608 let db: C = Box::pin(open_db(
1609 context.child("schedule").with_attribute("index", schedule),
1610 partition.clone(),
1611 ))
1612 .await;
1613 let db = commit_writes(db, seed.clone()).await.unwrap();
1614
1615 let a_batch = build(&db, db.new_batch(), &a).await;
1617 let b_batch = build(&db, a_batch.new_batch::<Sha256>(), &b).await;
1618 let e_batch = build(&db, b_batch.new_batch::<Sha256>(), &e).await;
1619 let c_batch = build(&db, e_batch.new_batch::<Sha256>(), &c).await;
1620
1621 let (db, pruned) = match schedule {
1623 1 => {
1625 let d_batch = build(&db, c_batch.new_batch::<Sha256>(), &d).await;
1626 (db.apply_batch(d_batch).await.unwrap(), false)
1627 }
1628
1629 2 => {
1631 let d_batch = build(&db, c_batch.new_batch::<Sha256>(), &d).await;
1632 let db = db.apply_batch(a_batch).await.unwrap();
1633 (db.apply_batch(d_batch).await.unwrap(), false)
1634 }
1635
1636 3 => {
1638 let db = db.apply_batch(a_batch).await.unwrap();
1639 let d_batch = build(&db, c_batch.new_batch::<Sha256>(), &d).await;
1640 (db.apply_batch(d_batch).await.unwrap(), false)
1641 }
1642
1643 4 => {
1645 let db = db.apply_batch(a_batch).await.unwrap();
1646 let d_batch = build(&db, c_batch.new_batch::<Sha256>(), &d).await;
1647 let db = db.apply_batch(b_batch).await.unwrap();
1648 (db.apply_batch(d_batch).await.unwrap(), false)
1649 }
1650
1651 5 => {
1654 let d_batch = build(&db, c_batch.new_batch::<Sha256>(), &d).await;
1655 let db = db.apply_batch(a_batch).await.unwrap();
1656 let db = db.commit().await.unwrap();
1657 let boundary = db.sync_boundary();
1658 let db = db.prune(boundary).await.unwrap();
1659 assert!(db.pruned_bits() > 0, "S5 must prune bitmap bits");
1660 (db.apply_batch(d_batch).await.unwrap(), true)
1661 }
1662 _ => unreachable!("five schedules"),
1663 };
1664
1665 let label = format!("S{schedule}");
1668 let observed = Observed::capture(&db);
1669 assert_eq!(observed.root, expected.root, "{label}: root diverged");
1670 if !pruned {
1671 assert_eq!(observed.bounds, expected.bounds, "{label}: bounds diverged");
1672 }
1673 assert_eq!(observed.floor, expected.floor, "{label}: floor diverged");
1674 observed.assert_bits(&expected, &label);
1675
1676 let f_batch = build(&db, db.new_batch(), &f).await;
1678 assert_eq!(f_batch.root(), f_root, "{label}: follow-on root diverged");
1679 let db = db.apply_batch(f_batch).await.unwrap();
1680 let observed_f = Observed::capture(&db);
1681 observed_f.assert_bits(&expected_f, &format!("{label} after F"));
1682
1683 db.sync().await.unwrap();
1686 let db: C = Box::pin(open_db(
1687 context.child("reopen").with_attribute("index", schedule),
1688 partition,
1689 ))
1690 .await;
1691 let reopened = Observed::capture(&db);
1692 assert_eq!(
1693 reopened.root, observed_f.root,
1694 "{label}: root diverged on reopen",
1695 );
1696 reopened.assert_bits(&observed_f, &format!("{label} on reopen"));
1697 for (key, value) in keys.iter().zip(&values) {
1698 assert_eq!(
1699 &db.get(key).await.unwrap(),
1700 value,
1701 "{label}: value of {key} diverged on reopen",
1702 );
1703 }
1704 db.destroy().await.unwrap();
1705 }
1706 }
1707
1708 use crate::translator::OneCap;
1709 use commonware_cryptography::{Hasher as _, Sha256, sha256::Digest};
1710 use commonware_macros::{boxed, test_group, test_traced};
1711
1712 type OrderedFixedDb =
1713 ordered::fixed::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1714 type OrderedVariableDb =
1715 ordered::variable::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1716 type UnorderedFixedDb =
1717 unordered::fixed::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1718 type UnorderedVariableDb = unordered::variable::Db<
1719 mmr::Family,
1720 Context,
1721 Digest,
1722 Digest,
1723 Sha256,
1724 OneCap,
1725 32,
1726 Sequential,
1727 >;
1728 type OrderedFixedP1Db = ordered::fixed::partitioned::Db<
1729 mmr::Family,
1730 Context,
1731 Digest,
1732 Digest,
1733 Sha256,
1734 OneCap,
1735 1,
1736 32,
1737 Sequential,
1738 >;
1739 type OrderedVariableP1Db = ordered::variable::partitioned::Db<
1740 mmr::Family,
1741 Context,
1742 Digest,
1743 Digest,
1744 Sha256,
1745 OneCap,
1746 1,
1747 32,
1748 Sequential,
1749 >;
1750 type UnorderedFixedP1Db = unordered::fixed::partitioned::Db<
1751 mmr::Family,
1752 Context,
1753 Digest,
1754 Digest,
1755 Sha256,
1756 OneCap,
1757 1,
1758 32,
1759 Sequential,
1760 >;
1761 type UnorderedVariableP1Db = unordered::variable::partitioned::Db<
1762 mmr::Family,
1763 Context,
1764 Digest,
1765 Digest,
1766 Sha256,
1767 OneCap,
1768 1,
1769 32,
1770 Sequential,
1771 >;
1772 type OrderedFixedP2Db = ordered::fixed::partitioned::Db<
1773 mmr::Family,
1774 Context,
1775 Digest,
1776 Digest,
1777 Sha256,
1778 OneCap,
1779 2,
1780 32,
1781 Sequential,
1782 >;
1783 type OrderedVariableP2Db = ordered::variable::partitioned::Db<
1784 mmr::Family,
1785 Context,
1786 Digest,
1787 Digest,
1788 Sha256,
1789 OneCap,
1790 2,
1791 32,
1792 Sequential,
1793 >;
1794 type UnorderedFixedP2Db = unordered::fixed::partitioned::Db<
1795 mmr::Family,
1796 Context,
1797 Digest,
1798 Digest,
1799 Sha256,
1800 OneCap,
1801 2,
1802 32,
1803 Sequential,
1804 >;
1805 type UnorderedVariableP2Db = unordered::variable::partitioned::Db<
1806 mmr::Family,
1807 Context,
1808 Digest,
1809 Digest,
1810 Sha256,
1811 OneCap,
1812 2,
1813 32,
1814 Sequential,
1815 >;
1816
1817 type OrderedFixedMmbDb =
1818 ordered::fixed::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1819 type OrderedVariableMmbDb =
1820 ordered::variable::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1821 type UnorderedFixedMmbDb =
1822 unordered::fixed::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1823 type UnorderedVariableMmbDb = unordered::variable::Db<
1824 mmb::Family,
1825 Context,
1826 Digest,
1827 Digest,
1828 Sha256,
1829 OneCap,
1830 32,
1831 Sequential,
1832 >;
1833 type OrderedFixedMmbP1Db = ordered::fixed::partitioned::Db<
1834 mmb::Family,
1835 Context,
1836 Digest,
1837 Digest,
1838 Sha256,
1839 OneCap,
1840 1,
1841 32,
1842 Sequential,
1843 >;
1844 type OrderedVariableMmbP1Db = ordered::variable::partitioned::Db<
1845 mmb::Family,
1846 Context,
1847 Digest,
1848 Digest,
1849 Sha256,
1850 OneCap,
1851 1,
1852 32,
1853 Sequential,
1854 >;
1855 type UnorderedFixedMmbP1Db = unordered::fixed::partitioned::Db<
1856 mmb::Family,
1857 Context,
1858 Digest,
1859 Digest,
1860 Sha256,
1861 OneCap,
1862 1,
1863 32,
1864 Sequential,
1865 >;
1866 type UnorderedVariableMmbP1Db = unordered::variable::partitioned::Db<
1867 mmb::Family,
1868 Context,
1869 Digest,
1870 Digest,
1871 Sha256,
1872 OneCap,
1873 1,
1874 32,
1875 Sequential,
1876 >;
1877 type OrderedFixedMmbP2Db = ordered::fixed::partitioned::Db<
1878 mmb::Family,
1879 Context,
1880 Digest,
1881 Digest,
1882 Sha256,
1883 OneCap,
1884 2,
1885 32,
1886 Sequential,
1887 >;
1888 type OrderedVariableMmbP2Db = ordered::variable::partitioned::Db<
1889 mmb::Family,
1890 Context,
1891 Digest,
1892 Digest,
1893 Sha256,
1894 OneCap,
1895 2,
1896 32,
1897 Sequential,
1898 >;
1899 type UnorderedFixedMmbP2Db = unordered::fixed::partitioned::Db<
1900 mmb::Family,
1901 Context,
1902 Digest,
1903 Digest,
1904 Sha256,
1905 OneCap,
1906 2,
1907 32,
1908 Sequential,
1909 >;
1910 type UnorderedVariableMmbP2Db = unordered::variable::partitioned::Db<
1911 mmb::Family,
1912 Context,
1913 Digest,
1914 Digest,
1915 Sha256,
1916 OneCap,
1917 2,
1918 32,
1919 Sequential,
1920 >;
1921
1922 #[test_traced]
1923 fn test_reconstruction_views_expose_all_chunk_states() {
1924 let executor = deterministic::Runner::default();
1925 executor.start(|context| async move {
1926 let db = UnorderedFixedMmbDb::init(
1927 context.child("db"),
1928 fixed_config::<OneCap>("reconstruction-views"),
1929 None,
1930 )
1931 .await
1932 .unwrap();
1933
1934 let mut batch = db.new_batch();
1937 for i in 0u64..512 {
1938 let key = Sha256::hash(&[&i.to_be_bytes()]);
1939 let value = Sha256::hash(&[&(i + 1_000).to_be_bytes()]);
1940 batch = batch.write(key, Some(value));
1941 }
1942 let batch = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
1943 let db = db.apply_batch(batch).await.unwrap();
1944
1945 let end = db.bounds().end;
1948 let bitmap = db.bitmap();
1949 assert_eq!(bitmap.len(), *end);
1950 assert_eq!(bitmap.pruned_chunks(), 0);
1951
1952 let first = bitmap.get_chunk(0);
1953 let chunk_bits = first.len() as u64 * 8;
1954 let grafting_height = chunk_bits.trailing_zeros();
1955 let complete = bitmap.complete_chunks() as u64;
1956 let graftable =
1957 grafting::graftable_chunks::<mmb::Family>(*end, grafting_height).min(complete);
1958 assert_eq!(complete - graftable, 1, "expected one pending chunk");
1959 assert!(first.iter().any(|byte| *byte != 0));
1960 let pending = bitmap.get_chunk(graftable as usize);
1961 assert!(pending.iter().any(|byte| *byte != 0));
1962
1963 let (partial, partial_bits) = bitmap.last_chunk();
1964 assert!(partial_bits > 0 && partial_bits < chunk_bits);
1965 assert!(partial.iter().any(|byte| *byte != 0));
1966
1967 let witness = db.ops_root_witness().await.unwrap();
1969 assert_eq!(
1970 witness.pending_chunk_digest,
1971 Some(Sha256::hash(&[pending.as_slice()]))
1972 );
1973 assert_eq!(
1974 witness.partial_chunk,
1975 Some((partial_bits, Sha256::hash(&[partial.as_slice()])))
1976 );
1977
1978 let storage = db.grafted_storage();
1981 assert_eq!(
1982 storage.size(),
1983 <mmb::Family as merkle::Family>::location_to_position(end)
1984 );
1985
1986 let ops_pos = <mmb::Family as merkle::Graftable>::subtree_root_position(
1987 Location::new(0),
1988 grafting_height,
1989 );
1990 assert!(storage.get_node(ops_pos).await.unwrap().is_some());
1991
1992 let pinned_positions =
1995 <mmb::Family as merkle::Family>::nodes_to_pin(end).collect::<Vec<_>>();
1996 let raw_pinned = db.pinned_nodes_at(end).await.unwrap();
1997 assert_eq!(pinned_positions.len(), raw_pinned.len());
1998 let mut checked_grafted_node = false;
1999 for (pos, raw_digest) in pinned_positions.into_iter().zip(raw_pinned) {
2000 let digest = storage.get_node(pos).await.unwrap().unwrap();
2001 if pos == ops_pos {
2002 assert_ne!(digest, raw_digest);
2003 checked_grafted_node = true;
2004 }
2005 }
2006 assert!(checked_grafted_node);
2007
2008 drop(storage);
2009 db.destroy().await.unwrap();
2010 });
2011 }
2012
2013 #[test_traced]
2018 fn test_partitioned_ordered_short_key_exclusion_not_forgeable() {
2019 type ForgedExclusionDb = ordered::variable::partitioned::Db<
2020 mmr::Family,
2021 Context,
2022 Vec<u8>,
2023 Vec<u8>,
2024 Sha256,
2025 OneCap,
2026 2,
2027 32,
2028 Sequential,
2029 >;
2030
2031 let executor = deterministic::Runner::default();
2032 executor.start(|context| async move {
2033 let page_cache = CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE);
2034 let cfg = VariableConfig {
2035 merkle_config: MerkleConfig {
2036 journal_partition: "forged-exclusion-journal".to_string(),
2037 metadata_partition: "forged-exclusion-metadata".to_string(),
2038 items_per_blob: NZU64!(11),
2039 write_buffer: NZUsize!(1024),
2040 replay_buffer: NZUsize!(1024),
2041 strategy: Sequential,
2042 page_cache: page_cache.clone(),
2043 },
2044 journal_config: VConfig {
2045 partition: "forged-exclusion-log".to_string(),
2046 items_per_section: NZU64!(7),
2047 compression: None,
2048 codec_config: (((0..=8).into(), ()), ((0..=8).into(), ())),
2049 page_cache,
2050 write_buffer: NZUsize!(1024),
2051 replay_buffer: NZUsize!(1024),
2052 },
2053 grafted_metadata_partition: "forged-exclusion-grafted".to_string(),
2054 translator: OneCap,
2055 init_cache: Some(NZUsize!(1024)),
2056 init_buffer: NZUsize!(1 << 21),
2057 init_concurrency: NZUsize!(1),
2058 };
2059 let db = ForgedExclusionDb::init(context.child("db"), cfg, None)
2060 .await
2061 .unwrap();
2062
2063 let a = vec![0x00u8, 0x80];
2067 let b = vec![0x01u8];
2068 let c = vec![0x01u8, 0x00];
2069 let (va, vb, vc) = (vec![0x0au8], vec![0x0bu8], vec![0x0cu8]);
2070
2071 let merkleized = db
2073 .new_batch()
2074 .write(a.clone(), Some(va.clone()))
2075 .write(b.clone(), Some(vb.clone()))
2076 .merkleize(&db, None, &mut Proportional)
2077 .await
2078 .unwrap();
2079 let db = db.apply_batch(merkleized).await.unwrap();
2080 let merkleized = db
2081 .new_batch()
2082 .write(c.clone(), Some(vc.clone()))
2083 .merkleize(&db, None, &mut Proportional)
2084 .await
2085 .unwrap();
2086 let db = db.apply_batch(merkleized).await.unwrap();
2087 let root = db.root();
2088
2089 assert_eq!(db.get(&a).await.unwrap(), Some(va));
2091 assert_eq!(db.get(&b).await.unwrap(), Some(vb));
2092 assert_eq!(db.get(&c).await.unwrap(), Some(vc));
2093
2094 let (_, span_b) = db.get_span(&b).await.unwrap().unwrap();
2097 assert_eq!(
2098 span_b.next_key, c,
2099 "b.next_key must be c, not the wrapped-around a"
2100 );
2101
2102 assert!(matches!(
2104 db.exclusion_proof(&c).await,
2105 Err(Error::KeyExists)
2106 ));
2107
2108 let kvp = db.key_value_proof(b.clone()).await.unwrap();
2112 let forged = ordered::proof::constant::ExclusionProof::KeyValue(kvp.proof, span_b);
2113 assert!(!forged.verify::<Sha256>(&c, &root));
2114
2115 db.destroy().await.unwrap();
2116 });
2117 }
2118
2119 macro_rules! open_db_fn {
2121 ($db:ty, $cfg:ident) => {
2122 |ctx: Context, partition: String| async move {
2123 <$db>::init(ctx.child("storage"), $cfg::<OneCap>(&partition), None)
2124 .await
2125 .unwrap()
2126 }
2127 };
2128 }
2129
2130 macro_rules! with_all_variants {
2132 ($cb:ident!($($args:tt)*)) => {
2133 $cb!($($args)*, of, OrderedFixedDb, fixed_config);
2134 $cb!($($args)*, ov, OrderedVariableDb, variable_config);
2135 $cb!($($args)*, uf, UnorderedFixedDb, fixed_config);
2136 $cb!($($args)*, uv, UnorderedVariableDb, variable_config);
2137 $cb!($($args)*, ofp1, OrderedFixedP1Db, fixed_config_partitioned);
2138 $cb!($($args)*, ovp1, OrderedVariableP1Db, variable_config_partitioned);
2139 $cb!($($args)*, ufp1, UnorderedFixedP1Db, fixed_config_partitioned);
2140 $cb!($($args)*, uvp1, UnorderedVariableP1Db, variable_config_partitioned);
2141 $cb!($($args)*, ofp2, OrderedFixedP2Db, fixed_config_partitioned);
2142 $cb!($($args)*, ovp2, OrderedVariableP2Db, variable_config_partitioned);
2143 $cb!($($args)*, ufp2, UnorderedFixedP2Db, fixed_config_partitioned);
2144 $cb!($($args)*, uvp2, UnorderedVariableP2Db, variable_config_partitioned);
2145 $cb!($($args)*, of_mmb, OrderedFixedMmbDb, fixed_config);
2146 $cb!($($args)*, ov_mmb, OrderedVariableMmbDb, variable_config);
2147 $cb!($($args)*, uf_mmb, UnorderedFixedMmbDb, fixed_config);
2148 $cb!($($args)*, uv_mmb, UnorderedVariableMmbDb, variable_config);
2149 $cb!($($args)*, ofp1_mmb, OrderedFixedMmbP1Db, fixed_config_partitioned);
2150 $cb!($($args)*, ovp1_mmb, OrderedVariableMmbP1Db, variable_config_partitioned);
2151 $cb!($($args)*, ufp1_mmb, UnorderedFixedMmbP1Db, fixed_config_partitioned);
2152 $cb!($($args)*, uvp1_mmb, UnorderedVariableMmbP1Db, variable_config_partitioned);
2153 $cb!($($args)*, ofp2_mmb, OrderedFixedMmbP2Db, fixed_config_partitioned);
2154 $cb!($($args)*, ovp2_mmb, OrderedVariableMmbP2Db, variable_config_partitioned);
2155 $cb!($($args)*, ufp2_mmb, UnorderedFixedMmbP2Db, fixed_config_partitioned);
2156 $cb!($($args)*, uvp2_mmb, UnorderedVariableMmbP2Db, variable_config_partitioned);
2157 };
2158 }
2159
2160 macro_rules! with_ordered_variants {
2162 ($cb:ident!($($args:tt)*)) => {
2163 $cb!($($args)*, of, OrderedFixedDb, fixed_config);
2164 $cb!($($args)*, ov, OrderedVariableDb, variable_config);
2165 $cb!($($args)*, ofp1, OrderedFixedP1Db, fixed_config_partitioned);
2166 $cb!($($args)*, ovp1, OrderedVariableP1Db, variable_config_partitioned);
2167 $cb!($($args)*, ofp2, OrderedFixedP2Db, fixed_config_partitioned);
2168 $cb!($($args)*, ovp2, OrderedVariableP2Db, variable_config_partitioned);
2169 $cb!($($args)*, of_mmb, OrderedFixedMmbDb, fixed_config);
2170 $cb!($($args)*, ov_mmb, OrderedVariableMmbDb, variable_config);
2171 $cb!($($args)*, ofp1_mmb, OrderedFixedMmbP1Db, fixed_config_partitioned);
2172 $cb!($($args)*, ovp1_mmb, OrderedVariableMmbP1Db, variable_config_partitioned);
2173 $cb!($($args)*, ofp2_mmb, OrderedFixedMmbP2Db, fixed_config_partitioned);
2174 $cb!($($args)*, ovp2_mmb, OrderedVariableMmbP2Db, variable_config_partitioned);
2175 };
2176 }
2177
2178 macro_rules! with_unordered_variants {
2180 ($cb:ident!($($args:tt)*)) => {
2181 $cb!($($args)*, uf, UnorderedFixedDb, fixed_config);
2182 $cb!($($args)*, uv, UnorderedVariableDb, variable_config);
2183 $cb!($($args)*, ufp1, UnorderedFixedP1Db, fixed_config_partitioned);
2184 $cb!($($args)*, uvp1, UnorderedVariableP1Db, variable_config_partitioned);
2185 $cb!($($args)*, ufp2, UnorderedFixedP2Db, fixed_config_partitioned);
2186 $cb!($($args)*, uvp2, UnorderedVariableP2Db, variable_config_partitioned);
2187 $cb!($($args)*, uf_mmb, UnorderedFixedMmbDb, fixed_config);
2188 $cb!($($args)*, uv_mmb, UnorderedVariableMmbDb, variable_config);
2189 $cb!($($args)*, ufp1_mmb, UnorderedFixedMmbP1Db, fixed_config_partitioned);
2190 $cb!($($args)*, uvp1_mmb, UnorderedVariableMmbP1Db, variable_config_partitioned);
2191 $cb!($($args)*, ufp2_mmb, UnorderedFixedMmbP2Db, fixed_config_partitioned);
2192 $cb!($($args)*, uvp2_mmb, UnorderedVariableMmbP2Db, variable_config_partitioned);
2193 };
2194 }
2195
2196 macro_rules! test_for_variant {
2201 ($f:ident, $traced:literal, $label:ident, $db:ty, $cfg:ident) => {
2202 paste::paste! {
2203 #[test_group("slow")]
2204 #[test_traced($traced)]
2205 fn [<$f _ $label>]() {
2206 let executor = deterministic::Runner::default();
2207 executor.start(|context| async move {
2208 Box::pin($f(context, open_db_fn!($db, $cfg))).await
2211 });
2212 }
2213 }
2214 };
2215 }
2216
2217 macro_rules! test_for_all_variants {
2219 ($f:ident, $traced:literal) => {
2220 with_all_variants!(test_for_variant!($f, $traced));
2221 };
2222 }
2223
2224 macro_rules! test_for_ordered_variants {
2226 ($f:ident, $traced:literal) => {
2227 with_ordered_variants!(test_for_variant!($f, $traced));
2228 };
2229 }
2230
2231 macro_rules! test_for_unordered_variants {
2233 ($f:ident, $traced:literal) => {
2234 with_unordered_variants!(test_for_variant!($f, $traced));
2235 };
2236 }
2237
2238 async fn test_ordered_build_big<M, C, F, Fut>(context: Context, open_db: F)
2240 where
2241 M: merkle::Graftable,
2242 C: DbAny<M>,
2243 C::Key: TestKey,
2244 <C as DbAny<M>>::Value: TestValue,
2245 F: FnMut(Context, String) -> Fut + Clone,
2246 Fut: Future<Output = C>,
2247 {
2248 test_current_db_build_big::<M, C, F, Fut>(context, open_db).await;
2249 }
2250
2251 async fn test_unordered_build_big<M, C, F, Fut>(context: Context, open_db: F)
2252 where
2253 M: merkle::Graftable,
2254 C: DbAny<M>,
2255 C::Key: TestKey,
2256 <C as DbAny<M>>::Value: TestValue,
2257 F: FnMut(Context, String) -> Fut + Clone,
2258 Fut: Future<Output = C>,
2259 {
2260 test_current_db_build_big::<M, C, F, Fut>(context, open_db).await;
2261 }
2262
2263 test_for_all_variants!(test_build_random_close_reopen, "WARN");
2264 test_for_all_variants!(test_simulate_write_failures, "WARN");
2265 test_for_all_variants!(test_different_pruning_delays_same_root, "WARN");
2266 test_for_all_variants!(test_sync_persists_bitmap_pruning_boundary, "WARN");
2267 test_for_all_variants!(test_commit_after_sync_recovery, "WARN");
2268 test_for_all_variants!(test_stale_batch_side_effect_free, "WARN");
2269 test_for_all_variants!(test_chained_schedules_match_sequential, "WARN");
2270
2271 test_for_ordered_variants!(test_ordered_build_big, "WARN");
2272 test_for_ordered_variants!(test_ordered_build_small_close_reopen, "DEBUG");
2273
2274 test_for_unordered_variants!(test_unordered_build_big, "WARN");
2275 test_for_unordered_variants!(test_unordered_build_small_close_reopen, "DEBUG");
2276
2277 #[test_traced]
2283 fn test_current_foreign_db_merkleize_rejected() {
2284 deterministic::Runner::default().start(|context| async move {
2285 let db_a = UnorderedFixedDb::init(
2286 context.child("a"),
2287 fixed_config::<OneCap>("foreign-a"),
2288 None,
2289 )
2290 .await
2291 .unwrap();
2292 let db_b = UnorderedFixedDb::init(
2293 context.child("b"),
2294 fixed_config::<OneCap>("foreign-b"),
2295 None,
2296 )
2297 .await
2298 .unwrap();
2299
2300 let seed_a = db_a
2301 .new_batch()
2302 .write(key(1), Some(val(1)))
2303 .merkleize(&db_a, None, &mut Proportional)
2304 .await
2305 .unwrap();
2306 let db_a = db_a.apply_batch(seed_a).await.unwrap();
2307 let seed_b = db_b
2308 .new_batch()
2309 .write(key(2), Some(val(2)))
2310 .merkleize(&db_b, None, &mut Proportional)
2311 .await
2312 .unwrap();
2313 let db_b = db_b.apply_batch(seed_b).await.unwrap();
2314
2315 assert_eq!(db_a.bounds().end, db_b.bounds().end);
2316 assert_ne!(db_a.ops_root(), db_b.ops_root());
2317 assert_ne!(db_a.root(), db_b.root());
2318
2319 let staged_keys = [key(1)];
2320 let staged_refs: Vec<_> = staged_keys.iter().collect();
2321 assert!(matches!(
2322 db_a.new_batch().stage(&staged_refs, &db_b).await,
2323 Err(Error::StaleRead)
2324 ));
2325
2326 let batch = db_a.new_batch().write(key(1), Some(val(3)));
2327 assert!(matches!(
2328 batch.merkleize(&db_b, None, &mut Proportional).await,
2329 Err(Error::StaleBatch)
2330 ));
2331 });
2332 }
2333
2334 #[test_traced]
2335 fn test_current_unordered_merkleize_rejects_foreign_instance() {
2336 deterministic::Runner::default().start(|context| async move {
2337 let db_a = UnorderedFixedDb::init(
2339 context.child("a"),
2340 fixed_config::<OneCap>("foreign-instance-unordered-a"),
2341 None,
2342 )
2343 .await
2344 .unwrap();
2345 let db_b = UnorderedFixedDb::init(
2346 context.child("b"),
2347 fixed_config::<OneCap>("foreign-instance-unordered-b"),
2348 None,
2349 )
2350 .await
2351 .unwrap();
2352 assert_eq!(db_a.root(), db_b.root());
2353
2354 let pending = db_a.new_batch().write(key(2), Some(val(2)));
2356 let staged_keys = [key(2)];
2357 let staged_refs: Vec<_> = staged_keys.iter().collect();
2358 let (_, staged) = db_a.new_batch().stage(&staged_refs, &db_a).await.unwrap();
2359
2360 let sibling = db_a
2362 .new_batch()
2363 .write(key(1), Some(val(1)))
2364 .merkleize(&db_a, None, &mut Proportional)
2365 .await
2366 .unwrap();
2367 let _db_a = db_a.apply_batch(sibling).await.unwrap();
2368
2369 assert!(matches!(
2371 pending.merkleize(&db_b, None, &mut Proportional).await,
2372 Err(Error::StaleBatch)
2373 ));
2374 assert!(matches!(
2375 staged
2376 .merkleize(
2377 vec![Slot::Put(val(3))],
2378 Vec::new(),
2379 None,
2380 &db_b,
2381 &mut Proportional
2382 )
2383 .await,
2384 Err(Error::StaleBatch)
2385 ));
2386 });
2387 }
2388
2389 #[test_traced]
2390 fn test_current_ordered_merkleize_rejects_foreign_instance() {
2391 deterministic::Runner::default().start(|context| async move {
2392 let db_a = OrderedFixedDb::init(
2394 context.child("a"),
2395 fixed_config::<OneCap>("foreign-instance-ordered-a"),
2396 None,
2397 )
2398 .await
2399 .unwrap();
2400 let db_b = OrderedFixedDb::init(
2401 context.child("b"),
2402 fixed_config::<OneCap>("foreign-instance-ordered-b"),
2403 None,
2404 )
2405 .await
2406 .unwrap();
2407 assert_eq!(db_a.root(), db_b.root());
2408
2409 let pending = db_a.new_batch().write(key(2), Some(val(2)));
2411 let staged_keys = [key(2)];
2412 let staged_refs: Vec<_> = staged_keys.iter().collect();
2413 let (_, staged) = db_a.new_batch().stage(&staged_refs, &db_a).await.unwrap();
2414
2415 let sibling = db_a
2417 .new_batch()
2418 .write(key(1), Some(val(1)))
2419 .merkleize(&db_a, None, &mut Proportional)
2420 .await
2421 .unwrap();
2422 let _db_a = db_a.apply_batch(sibling).await.unwrap();
2423
2424 assert!(matches!(
2426 pending.merkleize(&db_b, None, &mut Proportional).await,
2427 Err(Error::StaleBatch)
2428 ));
2429 assert!(matches!(
2430 staged
2431 .merkleize(
2432 vec![Slot::Put(val(3))],
2433 Vec::new(),
2434 None,
2435 &db_b,
2436 &mut Proportional
2437 )
2438 .await,
2439 Err(Error::StaleBatch)
2440 ));
2441 });
2442 }
2443
2444 #[test_traced]
2445 fn test_current_merkleize_rejects_stale_sibling() {
2446 deterministic::Runner::default().start(|context| async move {
2447 let db = OrderedFixedDb::init(
2448 context.child("db"),
2449 fixed_config::<OneCap>("stale-unmerkleized"),
2450 None,
2451 )
2452 .await
2453 .unwrap();
2454
2455 let seed = db
2456 .new_batch()
2457 .write(key(1), Some(val(1)))
2458 .merkleize(&db, None, &mut Proportional)
2459 .await
2460 .unwrap();
2461 let db = db.apply_batch(seed).await.unwrap();
2462
2463 let stale = db.new_batch().write(key(1), Some(val(2)));
2464 let sibling = db
2465 .new_batch()
2466 .write(key(1), Some(val(3)))
2467 .merkleize(&db, None, &mut Proportional)
2468 .await
2469 .unwrap();
2470 let db = db.apply_batch(sibling).await.unwrap();
2471
2472 assert!(matches!(
2473 stale.merkleize(&db, None, &mut Proportional).await,
2474 Err(Error::StaleBatch)
2475 ));
2476 });
2477 }
2478
2479 #[test_traced]
2480 fn test_current_staged_merkleize_rejects_stale_sibling() {
2481 deterministic::Runner::default().start(|context| async move {
2482 let db = UnorderedFixedDb::init(
2483 context.child("db"),
2484 fixed_config::<OneCap>("stale-staged"),
2485 None,
2486 )
2487 .await
2488 .unwrap();
2489 let target = key(1);
2490
2491 let seed = db
2492 .new_batch()
2493 .write(target, Some(val(1)))
2494 .merkleize(&db, None, &mut Proportional)
2495 .await
2496 .unwrap();
2497 let db = db.apply_batch(seed).await.unwrap();
2498
2499 let staged_keys = [&target];
2500 let (_, staged) = db.new_batch().stage(&staged_keys, &db).await.unwrap();
2501 let sibling = db
2502 .new_batch()
2503 .write(target, Some(val(2)))
2504 .merkleize(&db, None, &mut Proportional)
2505 .await
2506 .unwrap();
2507 let db = db.apply_batch(sibling).await.unwrap();
2508
2509 assert!(matches!(
2510 staged
2511 .merkleize(
2512 vec![Slot::Put(val(3))],
2513 Vec::new(),
2514 None,
2515 &db,
2516 &mut Proportional
2517 )
2518 .await,
2519 Err(Error::StaleBatch)
2520 ));
2521 });
2522 }
2523
2524 const CHUNK_BITS: u64 = commonware_utils::bitmap::BitMap::<32>::CHUNK_SIZE_BITS;
2526
2527 fn key(i: u64) -> Digest {
2528 Sha256::hash(&[&i.to_be_bytes()])
2529 }
2530
2531 fn val(i: u64) -> Digest {
2532 Sha256::hash(&[&(i + 10000).to_be_bytes()])
2533 }
2534
2535 #[boxed]
2536 async fn mmb_commit(
2537 db: UnorderedVariableMmbDb,
2538 writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
2539 ) -> UnorderedVariableMmbDb {
2540 let mut batch = db.new_batch();
2541 for (k, v) in writes {
2542 batch = batch.write(k, v);
2543 }
2544 let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
2545 let db = db.apply_batch(merkleized).await.unwrap();
2546 db.commit().await.unwrap()
2547 }
2548
2549 #[boxed]
2550 async fn commit_writes_with_metadata(
2551 db: UnorderedVariableDb,
2552 writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
2553 metadata: Option<Digest>,
2554 ) -> UnorderedVariableDb {
2555 let mut batch = db.new_batch();
2556 for (k, v) in writes {
2557 batch = batch.write(k, v);
2558 }
2559 let merkleized = batch
2560 .merkleize(&db, metadata, &mut Proportional)
2561 .await
2562 .unwrap();
2563 let expected_end = merkleized.bounds().tip.size;
2564 let db = db.apply_batch(merkleized).await.unwrap();
2565 assert_eq!(db.bounds().end, expected_end);
2566 db.commit().await.unwrap()
2567 }
2568
2569 struct Generation {
2571 size: Location<mmr::Family>,
2573 floor: Location<mmr::Family>,
2575 sync_boundary: Location<mmr::Family>,
2577 root: Digest,
2579 }
2580
2581 async fn build_generations(
2585 ctx: &Context,
2586 partition: &str,
2587 generations: u64,
2588 ) -> (UnorderedVariableDb, Vec<Generation>) {
2589 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2590 ctx.child("storage"),
2591 variable_config::<OneCap>(partition),
2592 None,
2593 )
2594 .await
2595 .unwrap();
2596 let mut history = Vec::new();
2597 for generation in 0..generations {
2598 db = commit_writes_with_metadata(
2599 db,
2600 (0..384).map(|i| (key(i), Some(val(generation * 1_000 + i)))),
2601 None,
2602 )
2603 .await;
2604 history.push(Generation {
2605 size: db.bounds().end,
2606 floor: db.inactivity_floor_loc(),
2607 sync_boundary: db.sync_boundary(),
2608 root: db.root(),
2609 });
2610 }
2611 (db, history)
2612 }
2613
2614 #[test_traced("INFO")]
2615 fn test_current_bounded_initialization_recovery() {
2616 let executor = deterministic::Runner::default();
2617 executor.start(|context| async move {
2618 let partition = "current-rewind-recovery";
2619 let ctx = context.child("db");
2620 let db: UnorderedVariableDb = UnorderedVariableDb::init(
2621 ctx.child("storage"),
2622 variable_config::<OneCap>(partition),
2623 None,
2624 )
2625 .await
2626 .unwrap();
2627 let initial_size = db.bounds().end;
2628 let initial_root = db.root();
2629 let initial_ops_root = db.ops_root();
2630 let initial_floor = db.inactivity_floor_loc();
2631
2632 let metadata_a = val(900);
2633 let db = commit_writes_with_metadata(
2634 db,
2635 [(key(0), Some(val(0))), (key(1), Some(val(1)))],
2636 Some(metadata_a),
2637 )
2638 .await;
2639 let size_before = db.bounds().end;
2640 let root_before = db.root();
2641 let ops_root_before = db.ops_root();
2642 let floor_before = db.inactivity_floor_loc();
2643
2644 let metadata_b = val(901);
2645 let db = commit_writes_with_metadata(
2646 db,
2647 [
2648 (key(0), Some(val(100))),
2649 (key(1), None),
2650 (key(2), Some(val(2))),
2651 ],
2652 Some(metadata_b),
2653 )
2654 .await;
2655 assert_ne!(db.root(), root_before);
2656 assert_eq!(db.get_metadata().await.unwrap(), Some(val(901)));
2657 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(100)));
2658 assert_eq!(db.get(&key(1)).await.unwrap(), None);
2659 assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
2660
2661 let db = {
2662 _ = db.sync().await.unwrap();
2663 UnorderedVariableDb::init(
2664 ctx.child("cap"),
2665 variable_config::<OneCap>(partition),
2666 Some(size_before),
2667 )
2668 .await
2669 }
2670 .unwrap();
2671 assert_eq!(db.bounds().end, size_before);
2672 assert_eq!(db.root(), root_before);
2673 assert_eq!(db.ops_root(), ops_root_before);
2674 assert_eq!(db.inactivity_floor_loc(), floor_before);
2675 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a));
2676 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2677 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2678 assert_eq!(db.get(&key(2)).await.unwrap(), None);
2679
2680 let db = db.commit().await.unwrap();
2681 drop(db);
2682
2683 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
2684 context.child("reopen"),
2685 variable_config::<OneCap>(partition),
2686 None,
2687 )
2688 .await
2689 .unwrap();
2690 assert_eq!(reopened.bounds().end, size_before);
2691 assert_eq!(reopened.root(), root_before);
2692 assert_eq!(reopened.ops_root(), ops_root_before);
2693 assert_eq!(reopened.inactivity_floor_loc(), floor_before);
2694 assert_eq!(reopened.get_metadata().await.unwrap(), Some(val(900)));
2695 assert_eq!(reopened.get(&key(0)).await.unwrap(), Some(val(0)));
2696 assert_eq!(reopened.get(&key(1)).await.unwrap(), Some(val(1)));
2697 assert_eq!(reopened.get(&key(2)).await.unwrap(), None);
2698
2699 let reopened = {
2700 _ = reopened.sync().await.unwrap();
2701 UnorderedVariableDb::init(
2702 ctx.child("cap"),
2703 variable_config::<OneCap>(partition),
2704 Some(initial_size),
2705 )
2706 .await
2707 }
2708 .unwrap();
2709 assert_eq!(reopened.bounds().end, initial_size);
2710 assert_eq!(reopened.root(), initial_root);
2711 assert_eq!(reopened.ops_root(), initial_ops_root);
2712 assert_eq!(reopened.inactivity_floor_loc(), initial_floor);
2713 assert_eq!(reopened.get_metadata().await.unwrap(), None);
2714 assert_eq!(reopened.get(&key(0)).await.unwrap(), None);
2715 assert_eq!(reopened.get(&key(1)).await.unwrap(), None);
2716 assert_eq!(reopened.get(&key(2)).await.unwrap(), None);
2717
2718 let reopened = reopened.commit().await.unwrap();
2719 drop(reopened);
2720
2721 let reopened_initial: UnorderedVariableDb = UnorderedVariableDb::init(
2722 context.child("reopen_initial"),
2723 variable_config::<OneCap>(partition),
2724 None,
2725 )
2726 .await
2727 .unwrap();
2728 assert_eq!(reopened_initial.bounds().end, initial_size);
2729 assert_eq!(reopened_initial.root(), initial_root);
2730 assert_eq!(reopened_initial.ops_root(), initial_ops_root);
2731 assert_eq!(reopened_initial.inactivity_floor_loc(), initial_floor);
2732 assert_eq!(reopened_initial.get_metadata().await.unwrap(), None);
2733 assert_eq!(reopened_initial.get(&key(0)).await.unwrap(), None);
2734 assert_eq!(reopened_initial.get(&key(1)).await.unwrap(), None);
2735 assert_eq!(reopened_initial.get(&key(2)).await.unwrap(), None);
2736
2737 reopened_initial.destroy().await.unwrap();
2738 });
2739 }
2740
2741 #[test_traced("INFO")]
2742 fn test_current_bounded_initialization_recovery_pruned_repeated_updates() {
2743 let executor = deterministic::Runner::default();
2744 executor.start(|context| async move {
2745 const COMMITS: u64 = 200;
2746
2747 let partition = "current-rewind-pruned-recovery";
2748 let ctx = context.child("db");
2749 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2750 ctx.child("storage"),
2751 variable_config::<OneCap>(partition),
2752 None,
2753 )
2754 .await
2755 .unwrap();
2756
2757 let key0 = key(0);
2758 let mut history = Vec::new();
2759 for round in 0..COMMITS {
2760 db = commit_writes_with_metadata(db, [(key0, Some(val(20_000 + round)))], None)
2761 .await;
2762 history.push((
2763 db.bounds().end,
2764 db.inactivity_floor_loc(),
2765 db.root(),
2766 db.ops_root(),
2767 val(20_000 + round),
2768 ));
2769 }
2770
2771 let (_, older_floor, _, _, _) = history[history.len() - 4];
2775 let prune_loc = Location::new(*older_floor / CHUNK_BITS * CHUNK_BITS);
2776 let db = db.prune(prune_loc).await.unwrap();
2777 let pruned_bits = db.pruned_bits();
2778 assert!(
2779 pruned_bits > 0,
2780 "expected bitmap pruning: prune_loc={prune_loc} bounds={:?}",
2781 db.bounds()
2782 );
2783 assert_eq!(pruned_bits, *prune_loc);
2784 let bounds = db.bounds();
2785
2786 let (target_size, target_root, target_ops_root, target_value) = history
2787 .iter()
2788 .enumerate()
2789 .find_map(|(idx, (size, floor, root, ops_root, value))| {
2790 let removed_commits = history.len() - idx - 1;
2791 if removed_commits >= 3 && *size > bounds.start && *floor >= pruned_bits {
2792 Some((*size, *root, *ops_root, *value))
2793 } else {
2794 None
2795 }
2796 })
2797 .unwrap_or_else(|| {
2798 panic!(
2799 "expected recoverable pruned target with repeated updates. \
2800 bounds={bounds:?}, pruned_bits={pruned_bits}, latest_floor={:?}, \
2801 history={history:?}",
2802 db.inactivity_floor_loc()
2803 )
2804 });
2805
2806 _ = db.sync().await.unwrap();
2807 let db = UnorderedVariableDb::init(
2808 ctx.child("cap"),
2809 variable_config::<OneCap>(partition),
2810 Some(target_size),
2811 )
2812 .await
2813 .unwrap();
2814 assert_eq!(db.root(), target_root);
2815 assert_eq!(db.ops_root(), target_ops_root);
2816 assert_eq!(db.bounds().end, target_size);
2817 assert_eq!(db.get(&key0).await.unwrap(), Some(target_value));
2818
2819 let db = db.commit().await.unwrap();
2820 drop(db);
2821
2822 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
2823 context.child("reopen_pruned_recovery"),
2824 variable_config::<OneCap>(partition),
2825 None,
2826 )
2827 .await
2828 .unwrap();
2829 assert_eq!(reopened.root(), target_root);
2830 assert_eq!(reopened.ops_root(), target_ops_root);
2831 assert_eq!(reopened.bounds().end, target_size);
2832 assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_value));
2833
2834 let metadata_after_reopen = val(30_000);
2835 let new_key = key(1);
2836 let new_value = val(30_001);
2837 let reopened = commit_writes_with_metadata(
2838 reopened,
2839 [(new_key, Some(new_value))],
2840 Some(metadata_after_reopen),
2841 )
2842 .await;
2843 let size_after_new_write = reopened.bounds().end;
2844 let root_after_new_write = reopened.root();
2845 let ops_root_after_new_write = reopened.ops_root();
2846 assert_eq!(
2847 reopened.get_metadata().await.unwrap(),
2848 Some(metadata_after_reopen)
2849 );
2850 assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_value));
2851 assert_eq!(reopened.get(&new_key).await.unwrap(), Some(new_value));
2852
2853 drop(reopened);
2854 let reopened_after_new_write: UnorderedVariableDb = UnorderedVariableDb::init(
2855 context.child("reopen_pruned_after_new_write"),
2856 variable_config::<OneCap>(partition),
2857 None,
2858 )
2859 .await
2860 .unwrap();
2861 assert_eq!(reopened_after_new_write.root(), root_after_new_write);
2862 assert_eq!(
2863 reopened_after_new_write.ops_root(),
2864 ops_root_after_new_write
2865 );
2866 assert_eq!(reopened_after_new_write.bounds().end, size_after_new_write);
2867 assert_eq!(
2868 reopened_after_new_write.get_metadata().await.unwrap(),
2869 Some(metadata_after_reopen)
2870 );
2871 assert_eq!(
2872 reopened_after_new_write.get(&key0).await.unwrap(),
2873 Some(target_value)
2874 );
2875 assert_eq!(
2876 reopened_after_new_write.get(&new_key).await.unwrap(),
2877 Some(new_value)
2878 );
2879
2880 reopened_after_new_write.destroy().await.unwrap();
2881 });
2882 }
2883
2884 #[test_traced("INFO")]
2887 fn test_current_mmb_settlement_guard_defers_pruning() {
2888 let executor = deterministic::Runner::default();
2889 executor.start(|context| async move {
2890 const COMMITS: u64 = 100;
2891
2892 let partition = "current-mmb-reopen-prove-after-prune";
2893 let ctx = context.child("db");
2894 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2895 ctx.child("storage"),
2896 variable_config::<OneCap>(partition),
2897 None,
2898 )
2899 .await
2900 .unwrap();
2901
2902 let k = key(0);
2903 let mut expected = None;
2904 for round in 0..COMMITS {
2905 expected = Some(val(50_000 + round));
2906 let mut batch = db.new_batch();
2907 batch = batch.write(k, expected);
2908 let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
2909 db = db.apply_batch(merkleized).await.unwrap();
2910 db = db.commit().await.unwrap();
2911 }
2912
2913 let root_before = db.root();
2914 assert!(
2915 *db.inactivity_floor_loc() >= 256,
2916 "expected inactivity floor past chunk 0"
2917 );
2918 assert_eq!(
2919 *db.sync_boundary(),
2920 0,
2921 "settlement guard should hold boundary at 0 during unsettled window"
2922 );
2923
2924 let Err(err) = db.prune(Location::<mmb::Family>::new(1)).await else {
2926 panic!("expected PruneBeyondMinRequired");
2927 };
2928 assert!(
2929 matches!(err, Error::PruneBeyondMinRequired(_, _)),
2930 "expected PruneBeyondMinRequired, got {err:?}"
2931 );
2932
2933 let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2935 context.child("reopen"),
2936 variable_config::<OneCap>(partition),
2937 None,
2938 )
2939 .await
2940 .unwrap();
2941
2942 assert_eq!(reopened.pruned_bits(), 0);
2943 assert_eq!(reopened.root(), root_before);
2944 assert_eq!(reopened.get(&k).await.unwrap(), expected);
2945
2946 let _proof = reopened.key_value_proof(k).await.unwrap();
2948
2949 reopened.destroy().await.unwrap();
2950 });
2951 }
2952
2953 #[test_traced("INFO")]
2954 fn test_current_mmb_bounded_initialization_rejects_unsettled_pruned_window() {
2955 let executor = deterministic::Runner::default();
2956 executor.start(|context| async move {
2957 const COMMITS: u64 = 320;
2958 const N: usize = 32;
2959
2960 let partition = "current-mmb-rewind-unsettled-window";
2961 let ctx = context.child("db");
2962 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2963 ctx.child("storage"),
2964 variable_config::<OneCap>(partition),
2965 None,
2966 )
2967 .await
2968 .unwrap();
2969
2970 let key0 = key(0);
2971 let mut history = Vec::new();
2972 for round in 0..COMMITS {
2973 let mut batch = db.new_batch();
2974 batch = batch.write(key0, Some(val(60_000 + round)));
2975 let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
2976 db = db.apply_batch(merkleized).await.unwrap();
2977 db = db.commit().await.unwrap();
2978 history.push((db.bounds().end, db.inactivity_floor_loc()));
2979 }
2980
2981 let boundary = db.sync_boundary();
2982 let db = db.prune(boundary).await.unwrap();
2983 let pruned_bits = db.pruned_bits();
2984 assert!(pruned_bits > 0, "expected MMB bitmap pruning to be active");
2985 let db = db.sync().await.unwrap();
2986
2987 let chunk_bits = commonware_utils::bitmap::BitMap::<N>::CHUNK_SIZE_BITS;
2988 let pruned_chunks = (pruned_bits / chunk_bits) as u64;
2989 let gh = super::grafting::height::<N>();
2990 let youngest = pruned_chunks - 1;
2991 let pair_chunk = youngest & !1;
2992 let pair_start = pair_chunk << gh;
2993 let pair_pos = <mmb::Family as merkle::Graftable>::subtree_root_position(
2994 merkle::Location::<mmb::Family>::new(pair_start),
2995 gh + 1,
2996 );
2997 let absorbed_after =
2998 <mmb::Family as merkle::Graftable>::peak_birth_size(pair_pos, gh + 1);
2999
3000 let unsafe_target = history
3001 .iter()
3002 .filter_map(|(size, floor)| {
3003 let s = **size;
3004 if s >= pruned_bits && s < absorbed_after && **floor >= pruned_bits {
3005 Some(s)
3006 } else {
3007 None
3008 }
3009 })
3010 .max()
3011 .unwrap_or_else(|| {
3012 panic!(
3013 "expected initialization target in unsettled window. \
3014 pruned_bits={pruned_bits}, absorbed_after={absorbed_after}, \
3015 history={history:?}"
3016 )
3017 });
3018
3019 let original_root = db.root();
3020 _ = db.sync().await.unwrap();
3021 let Err(err) = UnorderedVariableMmbDb::init(
3022 ctx.child("cap"),
3023 variable_config::<OneCap>(partition),
3024 Some(merkle::Location::<mmb::Family>::new(unsafe_target)),
3025 )
3026 .await
3027 else {
3028 panic!("expected initialization rejection in unsettled delayed-merge window");
3029 };
3030 assert!(
3031 matches!(err, Error::HistoricalFloorPruned(_)),
3032 "unexpected bounded initialization error for unsettled delayed-merge window. \
3033 {err:?}"
3034 );
3035 let db = UnorderedVariableMmbDb::init(
3036 ctx.child("unchanged"),
3037 variable_config::<OneCap>(partition),
3038 None,
3039 )
3040 .await
3041 .unwrap();
3042 assert_eq!(db.root(), original_root);
3043 });
3044 }
3045
3046 #[test_traced]
3051 fn test_current_mmb_prune_respects_sync_boundary() {
3052 let executor = deterministic::Runner::default();
3053 executor.start(|context| async move {
3054 const COMMITS: u64 = 320;
3055
3056 let ctx = context.child("db");
3057 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3058 ctx.child("storage"),
3059 variable_config::<OneCap>("prune-clip-mmb"),
3060 None,
3061 )
3062 .await
3063 .unwrap();
3064
3065 let k = key(0);
3066 for round in 0..COMMITS {
3067 db = mmb_commit(db, [(k, Some(val(70_000 + round)))]).await;
3068 }
3069
3070 let prune_boundary = db.sync_boundary();
3071 let db = db.prune(prune_boundary).await.unwrap();
3072
3073 let boundary = db.sync_boundary();
3074 let floor = db.inactivity_floor_loc();
3075 assert!(
3076 boundary < floor,
3077 "delayed-merge lag must be strictly active: boundary={boundary}, floor={floor}"
3078 );
3079 assert!(
3080 db.bounds().start <= boundary,
3081 "ops journal was pruned past the settled bitmap boundary: \
3082 bounds.start={}, boundary={boundary}",
3083 db.bounds().start
3084 );
3085
3086 db.destroy().await.unwrap();
3087 });
3088 }
3089
3090 #[test_traced]
3095 fn test_current_mmr_prune_boundary_lag_is_only_chunk_alignment() {
3096 let executor = deterministic::Runner::default();
3097 executor.start(|context| async move {
3098 const COMMITS: u64 = 320;
3099 const N: usize = 32;
3100
3101 let ctx = context.child("db");
3102 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3103 ctx.child("storage"),
3104 variable_config::<OneCap>("prune-clip-mmr"), None,
3105 )
3106 .await
3107 .unwrap();
3108
3109 for round in 0..COMMITS {
3110 db = commit_writes_with_metadata(
3111 db,
3112 [(key(0), Some(val(80_000 + round)))],
3113 None,
3114 )
3115 .await;
3116 }
3117
3118 let prune_boundary = db.sync_boundary();
3119 let db = db.prune(prune_boundary).await.unwrap();
3120
3121 let boundary = db.sync_boundary();
3122 let floor = db.inactivity_floor_loc();
3123 let chunk_bits = commonware_utils::bitmap::BitMap::<N>::CHUNK_SIZE_BITS;
3124 assert!(
3125 boundary <= floor && *floor - *boundary < chunk_bits,
3126 "MMR lag should be only chunk alignment: boundary={boundary}, floor={floor}, chunk_bits={chunk_bits}"
3127 );
3128 assert!(
3129 db.bounds().start <= boundary,
3130 "ops journal bounds must be <= sync_boundary: bounds.start={}, boundary={boundary}",
3131 db.bounds().start
3132 );
3133
3134 db.destroy().await.unwrap();
3135 });
3136 }
3137
3138 #[test_traced]
3141 fn test_current_prune_below_settled_boundary_is_honored() {
3142 let executor = deterministic::Runner::default();
3143 executor.start(|context| async move {
3144 const COMMITS: u64 = 100;
3145
3146 let ctx = context.child("db");
3147 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3148 ctx.child("storage"),
3149 variable_config::<OneCap>("prune-below-boundary"), None,
3150 )
3151 .await
3152 .unwrap();
3153
3154 for round in 0..COMMITS {
3155 db = commit_writes_with_metadata(db, [(key(0), Some(val(90_000 + round)))], None)
3156 .await;
3157 }
3158
3159 assert!(*db.inactivity_floor_loc() > 1);
3160 let small = Location::new(1);
3161 let db = db.prune(small).await.unwrap();
3162
3163 assert!(
3164 db.bounds().start <= small,
3165 "journal pruning exceeded the caller-supplied target: bounds.start={}, requested={small}",
3166 db.bounds().start
3167 );
3168
3169 db.destroy().await.unwrap();
3170 });
3171 }
3172
3173 #[test_traced]
3176 fn test_current_mmb_reopen_and_prove_after_prune_delayed_merge() {
3177 let executor = deterministic::Runner::default();
3178 executor.start(|context| async move {
3179 let db_ctx = context.child("db_init");
3180 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3181 db_ctx.child("db"),
3182 variable_config::<OneCap>("test_prune_delayed_merge"),
3183 None,
3184 )
3185 .await
3186 .unwrap();
3187
3188 let k = key(0);
3189
3190 for round in 0..200u64 {
3191 db = mmb_commit(db, [(k, Some(val(60_000 + round)))]).await;
3192 }
3193
3194 let boundary = db.sync_boundary();
3195 db = db.prune(boundary).await.unwrap();
3196 db = db.sync().await.unwrap();
3197
3198 for round in 200..300u64 {
3200 db = mmb_commit(db, [(key(1), Some(val(round)))]).await;
3201 }
3202
3203 let proof = db.key_value_proof(k).await.unwrap();
3204 assert!(UnorderedVariableMmbDb::verify_key_value_proof(
3205 k,
3206 val(60_000 + 199),
3207 &proof,
3208 &db.root()
3209 ));
3210
3211 let target_root = db.root();
3212 drop(db);
3213
3214 let reopen_ctx = context.child("db_reopen");
3215 let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3216 reopen_ctx.child("db"),
3217 variable_config::<OneCap>("test_prune_delayed_merge"),
3218 None,
3219 )
3220 .await
3221 .unwrap();
3222
3223 assert_eq!(reopened.root(), target_root);
3224
3225 let proof = reopened.key_value_proof(k).await.unwrap();
3226 assert!(UnorderedVariableMmbDb::verify_key_value_proof(
3227 k,
3228 val(60_000 + 199),
3229 &proof,
3230 &reopened.root()
3231 ));
3232
3233 reopened.destroy().await.unwrap();
3234 });
3235 }
3236
3237 #[test_traced]
3239 fn test_current_mmb_reopen_after_prune_two_chunks() {
3240 let executor = deterministic::Runner::default();
3241 executor.start(|context| async move {
3242 let db_ctx = context.child("db");
3243 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3244 db_ctx.child("db"),
3245 variable_config::<OneCap>("test_prune_two"),
3246 None,
3247 )
3248 .await
3249 .unwrap();
3250
3251 let k = key(0);
3252 let mut expected;
3254
3255 let mut round = 0u64;
3258 loop {
3259 expected = Some(val(60_000 + round));
3260 db = mmb_commit(db, [(k, expected)]).await;
3261 round += 1;
3262 let boundary = db.sync_boundary();
3263 db = db.prune(boundary).await.unwrap();
3264 if db.pruned_bits() >= 512 {
3265 break;
3266 }
3267 assert!(
3268 round < 500,
3269 "failed to reach 2 pruned chunks after {round} commits"
3270 );
3271 }
3272 let db = db.sync().await.unwrap();
3273
3274 let target_root = db.root();
3275 drop(db);
3276
3277 let reopen_ctx = context.child("db_reopen");
3278 let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3279 reopen_ctx.child("db"),
3280 variable_config::<OneCap>("test_prune_two"),
3281 None,
3282 )
3283 .await
3284 .unwrap();
3285
3286 assert_eq!(reopened.root(), target_root);
3287 assert_eq!(reopened.get(&k).await.unwrap(), expected);
3288 reopened.destroy().await.unwrap();
3289 });
3290 }
3291
3292 #[test_traced]
3294 fn test_current_mmb_repeated_prune() {
3295 let executor = deterministic::Runner::default();
3296 executor.start(|context| async move {
3297 let mut db_ctx = context.child("db_init");
3298 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3299 db_ctx.child("db"),
3300 variable_config::<OneCap>("test_repeated_prune"),
3301 None,
3302 )
3303 .await
3304 .unwrap();
3305
3306 for round in 0..3u64 {
3307 let k = key(round * 1000);
3308 let mut expected = None;
3309 for i in 0..90 {
3310 expected = Some(val(round * 1000 + i));
3311 db = mmb_commit(db, [(k, expected)]).await;
3312 }
3313
3314 let boundary = db.sync_boundary();
3315 db = db.prune(boundary).await.unwrap();
3316 db = db.sync().await.unwrap();
3317
3318 let root_before = db.root();
3319 db_ctx = context.child("db").with_attribute("round", round);
3320
3321 drop(db);
3322 db = UnorderedVariableMmbDb::init(
3323 db_ctx.child("db"),
3324 variable_config::<OneCap>("test_repeated_prune"),
3325 None,
3326 )
3327 .await
3328 .unwrap();
3329
3330 assert_eq!(db.root(), root_before);
3331 assert_eq!(db.get(&k).await.unwrap(), expected);
3332 }
3333
3334 db.destroy().await.unwrap();
3335 });
3336 }
3337
3338 #[test_traced]
3340 fn test_current_mmb_stepwise_growth_matches_unpruned_reference() {
3341 let executor = deterministic::Runner::default();
3342 executor.start(|context| async move {
3343 let db_ctx = context.child("db_stepwise");
3344 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3345 db_ctx.child("db"),
3346 variable_config::<OneCap>("test_stepwise"),
3347 None,
3348 )
3349 .await
3350 .unwrap();
3351
3352 let ref_ctx = context.child("ref_stepwise");
3353 let mut ref_db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3354 ref_ctx.child("db"),
3355 variable_config::<OneCap>("test_stepwise_ref"),
3356 None,
3357 )
3358 .await
3359 .unwrap();
3360
3361 let k = key(0);
3362 let mut commit_idx = 0u64;
3363
3364 while *db.inactivity_floor_loc() < 1024 {
3366 let value = Some(val(80_000 + commit_idx));
3367 db = mmb_commit(db, [(k, value)]).await;
3368 ref_db = mmb_commit(ref_db, [(k, value)]).await;
3369 commit_idx += 1;
3370 }
3371
3372 let boundary = db.sync_boundary();
3373 db = db.prune(boundary).await.unwrap();
3374 db = db.sync().await.unwrap();
3375 assert_eq!(
3376 db.root(),
3377 ref_db.root(),
3378 "root mismatch immediately after prune"
3379 );
3380
3381 loop {
3383 let db_leaves =
3384 *Location::<mmb::Family>::try_from(db.any.log.merkle.size()).unwrap();
3385 if db_leaves >= 1560 {
3386 break;
3387 }
3388
3389 let value = Some(val(80_000 + commit_idx));
3390 db = mmb_commit(db, [(k, value)]).await;
3391 ref_db = mmb_commit(ref_db, [(k, value)]).await;
3392 commit_idx += 1;
3393
3394 let db_leaves =
3395 *Location::<mmb::Family>::try_from(db.any.log.merkle.size()).unwrap();
3396 assert_eq!(
3397 db.root(),
3398 ref_db.root(),
3399 "stepwise root mismatch: leaves={db_leaves}, commit_idx={commit_idx}"
3400 );
3401 }
3402
3403 db.destroy().await.unwrap();
3404 ref_db.destroy().await.unwrap();
3405 });
3406 }
3407
3408 #[test_traced]
3410 fn test_current_mmb_large_repeated_prune_matches_unpruned_reference() {
3411 let executor = deterministic::Runner::default();
3412 executor.start(|context| async move {
3413 const ROUNDS: u64 = 8;
3414 const COMMITS_PER_ROUND: u64 = 120;
3415
3416 let mut db_ctx = context.child("db_init");
3417 let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3418 db_ctx.child("db"),
3419 variable_config::<OneCap>("test_large_prune"),
3420 None,
3421 )
3422 .await
3423 .unwrap();
3424
3425 let ref_ctx = context.child("ref");
3426 let mut ref_db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
3427 ref_ctx.child("db"),
3428 variable_config::<OneCap>("test_large_prune_ref"),
3429 None,
3430 )
3431 .await
3432 .unwrap();
3433
3434 let k = key(0);
3435 let mut expected = None;
3436
3437 for round in 0..ROUNDS {
3438 for i in 0..COMMITS_PER_ROUND {
3439 let value = Some(val(round * 10_000 + i));
3440 expected = value;
3441 db = mmb_commit(db, [(k, value)]).await;
3442 ref_db = mmb_commit(ref_db, [(k, value)]).await;
3443 }
3444
3445 assert_eq!(
3446 db.root(),
3447 ref_db.root(),
3448 "root mismatch before prune at round {round}"
3449 );
3450
3451 let boundary = db.sync_boundary();
3452 db = db.prune(boundary).await.unwrap();
3453 db = db.sync().await.unwrap();
3454
3455 assert_eq!(
3456 db.root(),
3457 ref_db.root(),
3458 "root mismatch after prune at round {round}"
3459 );
3460
3461 let proof = db.key_value_proof(k).await.unwrap();
3462 assert!(
3463 UnorderedVariableMmbDb::verify_key_value_proof(
3464 k,
3465 expected.expect("value should exist"),
3466 &proof,
3467 &db.root()
3468 ),
3469 "proof verification failed at round {round}"
3470 );
3471
3472 db_ctx = context.child("db_reopen").with_attribute("round", round);
3473 drop(db);
3474 db = UnorderedVariableMmbDb::init(
3475 db_ctx.child("db"),
3476 variable_config::<OneCap>("test_large_prune"),
3477 None,
3478 )
3479 .await
3480 .unwrap();
3481
3482 assert_eq!(
3483 db.root(),
3484 ref_db.root(),
3485 "root mismatch after reopen at round {round}"
3486 );
3487 assert_eq!(
3488 db.get(&k).await.unwrap(),
3489 expected,
3490 "value mismatch after reopen at round {round}"
3491 );
3492
3493 let proof = db.key_value_proof(k).await.unwrap();
3494 assert!(
3495 UnorderedVariableMmbDb::verify_key_value_proof(
3496 k,
3497 expected.expect("value should exist"),
3498 &proof,
3499 &db.root()
3500 ),
3501 "proof verification failed after reopen at round {round}"
3502 );
3503 }
3504
3505 db.destroy().await.unwrap();
3506 ref_db.destroy().await.unwrap();
3507 });
3508 }
3509
3510 #[test_traced]
3512 fn test_current_prune_rejects_beyond_sync_boundary_without_mutation() {
3513 let executor = deterministic::Runner::default();
3514 executor.start(|context| async move {
3515 const COMMITS: u64 = 160;
3516
3517 let partition = "current-prune-beyond-boundary";
3518 let ctx = context.child("db");
3519 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3520 ctx.child("storage"),
3521 variable_config::<OneCap>(partition),
3522 None,
3523 )
3524 .await
3525 .unwrap();
3526
3527 let key0 = key(0);
3528 for round in 0..COMMITS {
3529 db = commit_writes_with_metadata(db, [(key0, Some(val(40_000 + round)))], None)
3530 .await;
3531 }
3532
3533 let expected_root = db.root();
3534 let expected_ops_root = db.ops_root();
3535 let expected_boundary = db.sync_boundary();
3536 let expected_pruned_bits = db.pruned_bits();
3537 let expected_value = db.get(&key0).await.unwrap();
3538
3539 let invalid_prune_loc = expected_boundary + 256;
3541 let Err(err) = db.prune(invalid_prune_loc).await else {
3542 panic!("expected prune rejection above sync boundary");
3543 };
3544 assert!(
3545 matches!(err, Error::PruneBeyondMinRequired(loc, boundary)
3546 if loc == invalid_prune_loc && boundary == expected_boundary),
3547 "expected prune rejection above sync boundary, got {err:?}"
3548 );
3549
3550 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
3551 context.child("reopen"),
3552 variable_config::<OneCap>(partition),
3553 None,
3554 )
3555 .await
3556 .unwrap();
3557 assert_eq!(reopened.root(), expected_root);
3558 assert_eq!(reopened.ops_root(), expected_ops_root);
3559 assert_eq!(reopened.pruned_bits(), expected_pruned_bits);
3560 assert_eq!(reopened.get(&key0).await.unwrap(), expected_value);
3561
3562 reopened.destroy().await.unwrap();
3563 });
3564 }
3565
3566 #[test_traced("INFO")]
3567 fn test_current_bounded_initialization_small_delta_large_history() {
3568 let executor = deterministic::Runner::default();
3569 executor.start(|context| async move {
3570 const COMMITS: u64 = 200;
3571
3572 let partition = "current-rewind-small-delta";
3573 let ctx = context.child("db");
3574 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3575 ctx.child("storage"),
3576 variable_config::<OneCap>(partition),
3577 None,
3578 )
3579 .await
3580 .unwrap();
3581
3582 let key0 = key(0);
3583 let key1 = key(1);
3584 let mut history = Vec::new();
3585
3586 for round in 0..COMMITS {
3587 let key0_value = val(40_000 + round);
3588 let key1_value = if round % 3 == 1 {
3589 None
3590 } else {
3591 Some(val(50_000 + round))
3592 };
3593
3594 db = commit_writes_with_metadata(
3595 db,
3596 [(key0, Some(key0_value)), (key1, key1_value)],
3597 None,
3598 )
3599 .await;
3600
3601 history.push((
3602 db.bounds().end,
3603 db.root(),
3604 db.ops_root(),
3605 key0_value,
3606 key1_value,
3607 ));
3608 }
3609
3610 let target = *history
3611 .get(history.len() - 3)
3612 .expect("history should contain at least three commits");
3613 let (target_size, target_root, target_ops_root, target_key0, target_key1) = target;
3614
3615 let db = {
3616 _ = db.sync().await.unwrap();
3617 UnorderedVariableDb::init(
3618 ctx.child("cap"),
3619 variable_config::<OneCap>(partition),
3620 Some(target_size),
3621 )
3622 .await
3623 }
3624 .unwrap();
3625 assert_eq!(db.bounds().end, target_size);
3626 assert_eq!(db.root(), target_root);
3627 assert_eq!(db.ops_root(), target_ops_root);
3628 assert_eq!(db.get(&key0).await.unwrap(), Some(target_key0));
3629 assert_eq!(db.get(&key1).await.unwrap(), target_key1);
3630
3631 let db = db.commit().await.unwrap();
3632 drop(db);
3633
3634 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
3635 context.child("reopen_small_delta"),
3636 variable_config::<OneCap>(partition),
3637 None,
3638 )
3639 .await
3640 .unwrap();
3641 assert_eq!(reopened.bounds().end, target_size);
3642 assert_eq!(reopened.root(), target_root);
3643 assert_eq!(reopened.ops_root(), target_ops_root);
3644 assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_key0));
3645 assert_eq!(reopened.get(&key1).await.unwrap(), target_key1);
3646
3647 reopened.destroy().await.unwrap();
3648 });
3649 }
3650
3651 #[test_traced("INFO")]
3652 fn test_current_bounded_initialization_pruned_target_errors() {
3653 let executor = deterministic::Runner::default();
3654 executor.start(|context| async move {
3655 const KEYS: u64 = 384;
3656
3657 let partition = "current-rewind-pruned";
3658 let ctx = context.child("db");
3659 let db: UnorderedVariableDb =
3660 UnorderedVariableDb::init(
3661 ctx.child("storage"),
3662 variable_config::<OneCap>(partition), None,
3663 )
3664 .await
3665 .unwrap();
3666
3667 let first_start = db.bounds().end;
3668 let db = commit_writes_with_metadata(
3669 db,
3670 (0..KEYS).map(|i| (key(i), Some(val(i)))),
3671 None,
3672 )
3673 .await;
3674 let db = commit_writes_with_metadata(
3675 db,
3676 (0..KEYS).map(|i| (key(i), Some(val(1000 + i)))),
3677 None,
3678 )
3679 .await;
3680
3681 let boundary = db.sync_boundary();
3682 let db = db.prune(boundary).await.unwrap();
3683 let pruned_bits = db.pruned_bits();
3684 assert!(
3685 pruned_bits > *first_start,
3686 "expected bitmap pruning boundary above initialization bound: pruned_bits={pruned_bits}, target={:?}",
3687 first_start
3688 );
3689
3690 let oldest_retained = db.bounds().start;
3691 _ = db.sync().await.unwrap();
3692 let Err(boundary_err) = UnorderedVariableDb::init(
3693 ctx.child("cap"),
3694 variable_config::<OneCap>(partition),
3695 Some(oldest_retained),
3696 )
3697 .await else {
3698 panic!("expected initialization rejection at retained boundary");
3699 };
3700 assert!(
3701 matches!(
3702 boundary_err,
3703 Error::Journal(crate::journal::Error::ItemPruned(_))
3704 ),
3705 "unexpected bounded initialization error at retained boundary: {boundary_err:?}"
3706 );
3707
3708 let db: UnorderedVariableDb = UnorderedVariableDb::init(
3709 ctx.child("reopen"),
3710 variable_config::<OneCap>(partition), None,
3711 )
3712 .await
3713 .unwrap();
3714 let expected_pruned_loc = *first_start;
3715 _ = db.sync().await.unwrap();
3716 let Err(err) = UnorderedVariableDb::init(
3717 ctx.child("cap"),
3718 variable_config::<OneCap>(partition),
3719 Some(first_start),
3720 )
3721 .await else {
3722 panic!("expected initialization rejection at pruned target");
3723 };
3724 assert!(
3725 matches!(
3726 err,
3727 Error::Journal(crate::journal::Error::ItemPruned(loc))
3728 if loc == expected_pruned_loc
3729 ),
3730 "unexpected bounded initialization error: {err:?}"
3731 );
3732 });
3733 }
3734
3735 #[test_traced("INFO")]
3739 fn test_current_bounded_initialization_rejects_target_below_bitmap_floor() {
3740 let executor = deterministic::Runner::default();
3741 executor.start(|context| async move {
3742 const COMMITS: u64 = 120;
3743
3744 let partition = "current-rewind-bitmap-floor";
3745 let ctx = context.child("db");
3746 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3747 ctx.child("storage"),
3748 variable_config::<OneCap>(partition),
3749 None,
3750 )
3751 .await
3752 .unwrap();
3753
3754 let mut history = Vec::new();
3757 for round in 0..COMMITS {
3758 db = commit_writes_with_metadata(db, [(key(0), Some(val(10_000 + round)))], None)
3759 .await;
3760 history.push((db.bounds().end, db.inactivity_floor_loc()));
3761 }
3762
3763 let prune_loc = Location::new(CHUNK_BITS);
3766 assert!(prune_loc <= db.sync_boundary());
3767 let db = db.prune(prune_loc).await.unwrap();
3768 let pruned_bits = db.pruned_bits();
3769 assert_eq!(pruned_bits, CHUNK_BITS);
3770 let retained_start = db.bounds().start;
3771 assert!(retained_start < prune_loc);
3772
3773 let (target_size, target_floor) = history
3775 .iter()
3776 .copied()
3777 .find(|(size, floor)| {
3778 **size >= pruned_bits && *floor >= retained_start && **floor < pruned_bits
3779 })
3780 .unwrap_or_else(|| {
3781 panic!(
3782 "expected initialization target below bitmap boundary. \
3783 retained_start={retained_start:?}, pruned_bits={pruned_bits}, \
3784 history={history:?}"
3785 )
3786 });
3787 assert!(retained_start <= target_floor);
3788 assert!(*target_floor < pruned_bits);
3789 assert!(pruned_bits <= *target_size);
3790
3791 let original_root = db.root();
3792 _ = db.sync().await.unwrap();
3793 let Err(err) = UnorderedVariableDb::init(
3794 ctx.child("cap"),
3795 variable_config::<OneCap>(partition),
3796 Some(target_size),
3797 )
3798 .await
3799 else {
3800 panic!("expected initialization rejection below bitmap floor");
3801 };
3802 assert!(
3803 matches!(err, Error::HistoricalFloorPruned(loc) if loc == target_size),
3804 "unexpected bounded initialization error: {err:?}"
3805 );
3806 let db = UnorderedVariableDb::init(
3807 ctx.child("unchanged"),
3808 variable_config::<OneCap>(partition),
3809 None,
3810 )
3811 .await
3812 .unwrap();
3813 assert_eq!(db.root(), original_root);
3814 });
3815 }
3816
3817 #[test_traced("INFO")]
3822 fn test_current_prune_keeps_retained_checkpoints_initializable() {
3823 let executor = deterministic::Runner::default();
3824 executor.start(|context| async move {
3825 let partition = "current-prune-retained-checkpoints";
3826 let ctx = context.child("db");
3827 let (db, history) = build_generations(&ctx, partition, 4).await;
3828
3829 assert!(history[2].floor < db.sync_boundary());
3832
3833 let prune_loc = history[1].sync_boundary;
3838 assert!(prune_loc >= Location::new(CHUNK_BITS));
3839 let db = db.prune(prune_loc).await.unwrap();
3840 assert_eq!(db.pruned_bits(), *prune_loc);
3841 let retained_start = db.bounds().start;
3842 assert!(retained_start > Location::new(0));
3843 assert!(history[0].size <= retained_start);
3844 let db = db.sync().await.unwrap();
3845 drop(db);
3846
3847 for (label, generation) in [
3848 ("gen3", &history[3]),
3849 ("gen2", &history[2]),
3850 ("gen1", &history[1]),
3851 ] {
3852 let db = UnorderedVariableDb::init(
3853 ctx.child(label),
3854 variable_config::<OneCap>(partition),
3855 Some(generation.size),
3856 )
3857 .await
3858 .unwrap();
3859 assert_eq!(db.bounds().end, generation.size);
3860 assert_eq!(db.root(), generation.root);
3861 assert_eq!(db.sync_boundary(), generation.sync_boundary);
3862 }
3863
3864 let Err(err) = UnorderedVariableDb::init(
3865 ctx.child("gen0"),
3866 variable_config::<OneCap>(partition),
3867 Some(history[0].size),
3868 )
3869 .await
3870 else {
3871 panic!("expected initialization rejection below the retained start");
3872 };
3873 assert!(
3874 matches!(
3875 err,
3876 Error::Journal(crate::journal::Error::ItemPruned(loc))
3877 if loc == *history[0].size
3878 ),
3879 "unexpected bounded initialization error: {err:?}"
3880 );
3881 });
3882 }
3883
3884 pub async fn test_speculative_root_matches_committed<M, C, F, Fut>(
3890 context: Context,
3891 mut open_db: F,
3892 ) where
3893 M: merkle::Graftable,
3894 C: DbAny<M> + 'static,
3895 C::Key: TestKey,
3896 <C as DbAny<M>>::Value: TestValue,
3897 F: FnMut(Context, String) -> Fut + Clone,
3898 Fut: Future<Output = C>,
3899 {
3900 let mut open_db_clone = open_db.clone();
3901 let partition = "speculative-root".to_string();
3902
3903 let db: C = Box::pin(open_db_clone(context.child("init"), partition.clone())).await;
3908 let mut batch = db.new_batch();
3909 for i in 0..260 {
3910 batch = batch.write(TestKey::from_seed(i), Some(TestValue::from_seed(i + 1000)));
3911 }
3912 let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
3913 let db = db.apply_batch(merkleized).await.unwrap();
3914 let speculative_root = db.root();
3915
3916 db.sync().await.unwrap();
3918
3919 let db: C = Box::pin(open_db(context.child("reopen"), partition)).await;
3920 assert_eq!(db.root(), speculative_root);
3921
3922 db.destroy().await.unwrap();
3923 }
3924
3925 test_for_all_variants!(test_speculative_root_matches_committed, "INFO");
3926
3927 #[test_traced("INFO")]
3929 fn test_current_batch_merkleized_get() {
3930 let executor = deterministic::Runner::default();
3931 executor.start(|context| async move {
3932 let ctx = context.child("db");
3933 let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3934 ctx.child("storage"),
3935 variable_config::<OneCap>("mg"),
3936 None,
3937 )
3938 .await
3939 .unwrap();
3940
3941 let ka = key(0);
3942 let kb = key(1);
3943 let kc = key(2);
3944
3945 {
3947 let mut batch = db.new_batch();
3948 batch = batch.write(ka, Some(val(0)));
3949 let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
3950 db = db.apply_batch(merkleized).await.unwrap();
3951 }
3952
3953 let va2 = val(100);
3955 let vb = val(1);
3956 let mut batch = db.new_batch();
3957 batch = batch.write(ka, Some(va2));
3958 batch = batch.write(kb, Some(vb));
3959 let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
3960
3961 assert_eq!(merkleized.get(&ka, &db).await.unwrap(), Some(va2));
3962 assert_eq!(merkleized.get(&kb, &db).await.unwrap(), Some(vb));
3963 assert_eq!(merkleized.get(&kc, &db).await.unwrap(), None);
3964
3965 db.destroy().await.unwrap();
3966 });
3967 }
3968
3969 #[test_traced("INFO")]
3972 fn test_current_batch_chaining() {
3973 let executor = deterministic::Runner::default();
3974 executor.start(|context| async move {
3975 let ctx = context.child("db");
3976 let db: UnorderedVariableDb = UnorderedVariableDb::init(
3977 ctx.child("storage"),
3978 variable_config::<OneCap>("ch"),
3979 None,
3980 )
3981 .await
3982 .unwrap();
3983
3984 let mut parent = db.new_batch();
3986 for i in 0..5 {
3987 parent = parent.write(key(i), Some(val(i)));
3988 }
3989 let parent_m = parent
3990 .merkleize(&db, None, &mut Proportional)
3991 .await
3992 .unwrap();
3993
3994 let mut child = parent_m.new_batch::<Sha256>();
3996 for i in 5..10 {
3997 child = child.write(key(i), Some(val(i)));
3998 }
3999 child = child.write(key(0), Some(val(999)));
4000 let child_m = child.merkleize(&db, None, &mut Proportional).await.unwrap();
4001
4002 let child_root = child_m.root();
4003
4004 assert_eq!(child_m.get(&key(0), &db).await.unwrap(), Some(val(999)));
4006 assert_eq!(child_m.get(&key(3), &db).await.unwrap(), Some(val(3)));
4007 assert_eq!(child_m.get(&key(7), &db).await.unwrap(), Some(val(7)));
4008
4009 let db = db.apply_batch(child_m).await.unwrap();
4010 assert_eq!(db.root(), child_root);
4011
4012 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(999)));
4014 for i in 1..10 {
4015 assert_eq!(db.get(&key(i)).await.unwrap(), Some(val(i)));
4016 }
4017
4018 db.destroy().await.unwrap();
4019 });
4020 }
4021
4022 #[test_traced("INFO")]
4023 fn test_current_unordered_root_matches_between_pending_and_committed_paths() {
4024 let executor = deterministic::Runner::default();
4025 executor.start(|context| async move {
4026 let ctx = context.child("db");
4027 let db: UnorderedFixedDb =
4028 UnorderedFixedDb::init(ctx.child("storage"), fixed_config::<OneCap>("ucr"), None)
4029 .await
4030 .unwrap();
4031 let key_a = colliding_digest(0xAA, 1);
4032 let key_b = colliding_digest(0xAA, 0);
4033
4034 let mut initial = db.new_batch();
4039 for i in 0..4 {
4040 initial = initial.write(colliding_digest(0xAA, i), Some(colliding_digest(0xBB, i)));
4041 }
4042 let merkleized = initial
4043 .merkleize(&db, None, &mut Proportional)
4044 .await
4045 .unwrap();
4046 let db = db.apply_batch(merkleized).await.unwrap();
4047 let db = db.commit().await.unwrap();
4048
4049 let parent = db
4053 .new_batch()
4054 .write(key_a, Some(colliding_digest(0xCC, 1)))
4055 .merkleize(&db, None, &mut Proportional)
4056 .await
4057 .unwrap();
4058
4059 let pending_child = parent
4063 .new_batch::<Sha256>()
4064 .write(key_a, Some(colliding_digest(0xDD, 1)))
4065 .write(key_b, Some(colliding_digest(0xDD, 0)))
4066 .merkleize(&db, None, &mut Proportional)
4067 .await
4068 .unwrap();
4069
4070 let pending_root = pending_child.root();
4071 let pending_ops_root = pending_child.ops_root();
4072
4073 let db = db.apply_batch(parent).await.unwrap();
4074 let db = db.commit().await.unwrap();
4075
4076 let committed_child = db
4077 .new_batch()
4078 .write(key_a, Some(colliding_digest(0xDD, 1)))
4079 .write(key_b, Some(colliding_digest(0xDD, 0)))
4080 .merkleize(&db, None, &mut Proportional)
4081 .await
4082 .unwrap();
4083
4084 assert_eq!(pending_root, committed_child.root());
4085 assert_eq!(pending_ops_root, committed_child.ops_root());
4086
4087 let db = db.apply_batch(pending_child).await.unwrap();
4090 assert_eq!(db.root(), committed_child.root());
4091 assert_eq!(db.ops_root(), committed_child.ops_root());
4092
4093 db.destroy().await.unwrap();
4094 });
4095 }
4096
4097 #[test_traced("INFO")]
4098 fn test_current_ordered_root_matches_between_pending_and_committed_paths() {
4099 let executor = deterministic::Runner::default();
4100 executor.start(|context| async move {
4101 let ctx = context.child("db");
4102 let db: OrderedFixedDb =
4103 OrderedFixedDb::init(ctx.child("storage"), fixed_config::<OneCap>("ocr"), None)
4104 .await
4105 .unwrap();
4106 let key_a = colliding_digest(0xAA, 1);
4107 let key_b = colliding_digest(0xAA, 0);
4108
4109 let mut initial = db.new_batch();
4112 for i in 0..4 {
4113 initial = initial.write(colliding_digest(0xAA, i), Some(colliding_digest(0xBB, i)));
4114 }
4115 let merkleized = initial
4116 .merkleize(&db, None, &mut Proportional)
4117 .await
4118 .unwrap();
4119 let db = db.apply_batch(merkleized).await.unwrap();
4120 let db = db.commit().await.unwrap();
4121
4122 let parent = db
4126 .new_batch()
4127 .write(key_a, Some(colliding_digest(0xCC, 1)))
4128 .merkleize(&db, None, &mut Proportional)
4129 .await
4130 .unwrap();
4131
4132 let pending_child = parent
4135 .new_batch::<Sha256>()
4136 .write(key_a, Some(colliding_digest(0xDD, 1)))
4137 .write(key_b, Some(colliding_digest(0xDD, 0)))
4138 .merkleize(&db, None, &mut Proportional)
4139 .await
4140 .unwrap();
4141
4142 let pending_root = pending_child.root();
4143 let pending_ops_root = pending_child.ops_root();
4144
4145 let db = db.apply_batch(parent).await.unwrap();
4146 let db = db.commit().await.unwrap();
4147
4148 let committed_child = db
4149 .new_batch()
4150 .write(key_a, Some(colliding_digest(0xDD, 1)))
4151 .write(key_b, Some(colliding_digest(0xDD, 0)))
4152 .merkleize(&db, None, &mut Proportional)
4153 .await
4154 .unwrap();
4155
4156 assert_eq!(pending_root, committed_child.root());
4157 assert_eq!(pending_ops_root, committed_child.ops_root());
4158
4159 let db = db.apply_batch(pending_child).await.unwrap();
4162 assert_eq!(db.root(), committed_child.root());
4163 assert_eq!(db.ops_root(), committed_child.ops_root());
4164
4165 db.destroy().await.unwrap();
4166 });
4167 }
4168
4169 #[test_traced("INFO")]
4171 fn test_current_batch_apply_requires_commit_for_recovery() {
4172 let executor = deterministic::Runner::default();
4173 executor.start(|context| async move {
4174 let partition = "apply_requires_commit";
4175 let ctx = context.child("db");
4176 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4177 ctx.child("storage"),
4178 variable_config::<OneCap>(partition),
4179 None,
4180 )
4181 .await
4182 .unwrap();
4183
4184 let committed_root = db.root();
4185
4186 let merkleized = db
4187 .new_batch()
4188 .write(key(0), Some(val(0)))
4189 .merkleize(&db, None, &mut Proportional)
4190 .await
4191 .unwrap();
4192 let db = db.apply_batch(merkleized).await.unwrap();
4193
4194 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
4195
4196 drop(db);
4197
4198 let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
4199 context.child("reopen"),
4200 variable_config::<OneCap>(partition),
4201 None,
4202 )
4203 .await
4204 .unwrap();
4205 assert_eq!(reopened.root(), committed_root);
4206 assert_eq!(reopened.get(&key(0)).await.unwrap(), None);
4207
4208 reopened.destroy().await.unwrap();
4209 });
4210 }
4211
4212 #[test_traced("INFO")]
4214 fn test_current_batch_single_stage_pipeline() {
4215 let executor = deterministic::Runner::default();
4216 executor.start(|context| async move {
4217 let ctx = context.child("db");
4218 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4219 ctx.child("storage"),
4220 variable_config::<OneCap>("pipe"),
4221 None,
4222 )
4223 .await
4224 .unwrap();
4225
4226 let mut batch = db.new_batch();
4227 batch = batch.write(key(0), Some(val(0)));
4228 let parent_merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
4229 let db = db.apply_batch(parent_merkleized).await.unwrap();
4230
4231 let child_merkleized = {
4232 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
4233 let mut child = db.new_batch();
4234 child = child.write(key(1), Some(val(1)));
4235 child.merkleize(&db, None, &mut Proportional).await.unwrap()
4236 };
4237 let db = db.commit().await.unwrap();
4238
4239 let db = db.apply_batch(child_merkleized).await.unwrap();
4240 let db = db.commit().await.unwrap();
4241
4242 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
4243 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
4244
4245 db.destroy().await.unwrap();
4246 });
4247 }
4248
4249 #[test_traced("INFO")]
4252 fn test_current_sequential_commit() {
4253 let executor = deterministic::Runner::default();
4254 executor.start(|context| async move {
4255 let ctx = context.child("db");
4256 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4257 ctx.child("storage"),
4258 variable_config::<OneCap>("ff"),
4259 None,
4260 )
4261 .await
4262 .unwrap();
4263
4264 let parent_m = db
4266 .new_batch()
4267 .write(key(0), Some(val(0)))
4268 .merkleize(&db, None, &mut Proportional)
4269 .await
4270 .unwrap();
4271
4272 let child_m = parent_m
4274 .new_batch::<Sha256>()
4275 .write(key(1), Some(val(1)))
4276 .merkleize(&db, None, &mut Proportional)
4277 .await
4278 .unwrap();
4279
4280 let db = db.apply_batch(parent_m).await.unwrap();
4281 let db = db.apply_batch(child_m).await.unwrap();
4282
4283 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
4285 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
4286
4287 let ctx2 = context.child("db").with_attribute("index", 2);
4290 let db2: UnorderedVariableDb =
4291 UnorderedVariableDb::init(ctx2.child("db"), variable_config::<OneCap>("ff2"), None)
4292 .await
4293 .unwrap();
4294 let m1 = db2
4295 .new_batch()
4296 .write(key(0), Some(val(0)))
4297 .merkleize(&db2, None, &mut Proportional)
4298 .await
4299 .unwrap();
4300 let db2 = db2.apply_batch(m1).await.unwrap();
4301 let m2 = db2
4302 .new_batch()
4303 .write(key(1), Some(val(1)))
4304 .merkleize(&db2, None, &mut Proportional)
4305 .await
4306 .unwrap();
4307 let db2 = db2.apply_batch(m2).await.unwrap();
4308
4309 assert_eq!(db.root(), db2.root());
4310
4311 db.destroy().await.unwrap();
4312 db2.destroy().await.unwrap();
4313 });
4314 }
4315
4316 #[test_traced("INFO")]
4319 fn test_current_to_batch_then_chain() {
4320 let executor = deterministic::Runner::default();
4321 executor.start(|context| async move {
4322 let ctx = context.child("db");
4323 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4324 ctx.child("storage"),
4325 variable_config::<OneCap>("tb"),
4326 None,
4327 )
4328 .await
4329 .unwrap();
4330
4331 let m = db
4333 .new_batch()
4334 .write(key(0), Some(val(0)))
4335 .merkleize(&db, None, &mut Proportional)
4336 .await
4337 .unwrap();
4338 let db = db.apply_batch(m).await.unwrap();
4339
4340 let snapshot = db.to_batch();
4342 assert_eq!(snapshot.root(), db.root());
4343
4344 let child = snapshot
4346 .new_batch::<Sha256>()
4347 .write(key(1), Some(val(1)))
4348 .merkleize(&db, None, &mut Proportional)
4349 .await
4350 .unwrap();
4351
4352 assert_ne!(child.root(), snapshot.root());
4354
4355 let db = db.apply_batch(child).await.unwrap();
4357 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
4358 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
4359
4360 db.destroy().await.unwrap();
4361 });
4362 }
4363
4364 #[test_traced("INFO")]
4369 fn test_current_live_batch_safe_across_prune() {
4370 let executor = deterministic::Runner::default();
4371 executor.start(|context| async move {
4372 let ctx = context.child("db");
4373 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4374 ctx.child("storage"),
4375 variable_config::<OneCap>("prune-live"),
4376 None,
4377 )
4378 .await
4379 .unwrap();
4380
4381 let mut seed = db.new_batch();
4383 for i in 0u64..300 {
4384 seed = seed.write(key(i), Some(val(i)));
4385 }
4386 let seed_m = seed.merkleize(&db, None, &mut Proportional).await.unwrap();
4387 let db = db.apply_batch(seed_m).await.unwrap();
4388 let db = db.commit().await.unwrap();
4389
4390 let mut p = db.new_batch();
4392 for i in 0u64..250 {
4393 p = p.write(key(i), Some(val(i + 10_000)));
4394 }
4395 let p_m = p.merkleize(&db, None, &mut Proportional).await.unwrap();
4396 let db = db.apply_batch(Arc::clone(&p_m)).await.unwrap();
4397 let db = db.commit().await.unwrap();
4398
4399 let c = p_m
4401 .new_batch::<Sha256>()
4402 .write(key(250), Some(val(99_999)))
4403 .merkleize(&db, None, &mut Proportional)
4404 .await
4405 .unwrap();
4406
4407 let boundary = db.sync_boundary();
4409 let db = db.prune(boundary).await.unwrap();
4410
4411 assert_eq!(c.get(&key(250), &db).await.unwrap(), Some(val(99_999)));
4413
4414 let db = db.apply_batch(c).await.unwrap();
4417 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(10_000)));
4418 assert_eq!(db.get(&key(250)).await.unwrap(), Some(val(99_999)));
4419
4420 db.destroy().await.unwrap();
4421 });
4422 }
4423
4424 #[test_traced("INFO")]
4432 fn test_current_extend_applied_batch() {
4433 let executor = deterministic::Runner::default();
4434 executor.start(|context| async move {
4435 let ctx = context.child("db");
4436 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4437 ctx.child("storage"),
4438 variable_config::<OneCap>("xtend"),
4439 None,
4440 )
4441 .await
4442 .unwrap();
4443
4444 let a = db
4446 .new_batch()
4447 .write(key(0), Some(val(0)))
4448 .merkleize(&db, None, &mut Proportional)
4449 .await
4450 .unwrap();
4451 let db = db.apply_batch(Arc::clone(&a)).await.unwrap();
4452
4453 let b = a
4457 .new_batch::<Sha256>()
4458 .write(key(1), Some(val(1)))
4459 .merkleize(&db, None, &mut Proportional)
4460 .await
4461 .unwrap();
4462 let db = db.apply_batch(b).await.unwrap();
4463
4464 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
4465 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
4466
4467 let c = db
4469 .new_batch()
4470 .write(key(2), Some(val(2)))
4471 .merkleize(&db, None, &mut Proportional)
4472 .await
4473 .unwrap();
4474 let db = db.apply_batch(c).await.unwrap();
4475
4476 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
4477 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
4478 assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
4479
4480 db.destroy().await.unwrap();
4481 });
4482 }
4483
4484 #[test_traced("INFO")]
4491 fn test_current_live_batch_child_after_prune() {
4492 let executor = deterministic::Runner::default();
4493 executor.start(|context| async move {
4494 let ctx = context.child("db");
4495 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4496 ctx.child("storage"),
4497 variable_config::<OneCap>("child-after-prune"),
4498 None,
4499 )
4500 .await
4501 .unwrap();
4502
4503 let mut seed = db.new_batch();
4505 for i in 0u64..300 {
4506 seed = seed.write(key(i), Some(val(i)));
4507 }
4508 let seed_m = seed.merkleize(&db, None, &mut Proportional).await.unwrap();
4509 let db = db.apply_batch(seed_m).await.unwrap();
4510 let db = db.commit().await.unwrap();
4511
4512 let mut a_batch = db.new_batch();
4514 for i in 0u64..250 {
4515 a_batch = a_batch.write(key(i), Some(val(i + 10_000)));
4516 }
4517 let a = a_batch
4518 .merkleize(&db, None, &mut Proportional)
4519 .await
4520 .unwrap();
4521 let db = db.apply_batch(Arc::clone(&a)).await.unwrap();
4522 let db = db.commit().await.unwrap();
4523
4524 let boundary = db.sync_boundary();
4526 let db = db.prune(boundary).await.unwrap();
4527
4528 let b = a
4532 .new_batch::<Sha256>()
4533 .write(key(300), Some(val(300)))
4534 .merkleize(&db, None, &mut Proportional)
4535 .await
4536 .unwrap();
4537
4538 let db = db.apply_batch(b).await.unwrap();
4539 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(10_000)));
4540 assert_eq!(db.get(&key(249)).await.unwrap(), Some(val(10_249)));
4541 assert_eq!(db.get(&key(300)).await.unwrap(), Some(val(300)));
4542
4543 db.destroy().await.unwrap();
4544 });
4545 }
4546
4547 #[test_traced("WARN")]
4551 fn test_current_apply_after_ancestor_dropped() {
4552 let executor = deterministic::Runner::default();
4553 executor.start(|context| async move {
4554 let ctx = context.child("db");
4555 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4556 ctx.child("storage"),
4557 variable_config::<OneCap>("adrop"),
4558 None,
4559 )
4560 .await
4561 .unwrap();
4562
4563 let mut a = db.new_batch();
4565 for i in 0..3 {
4566 a = a.write(key(i), Some(val(i)));
4567 }
4568 let a_m = a.merkleize(&db, None, &mut Proportional).await.unwrap();
4569
4570 let mut b = a_m.new_batch::<Sha256>();
4571 for i in 3..6 {
4572 b = b.write(key(i), Some(val(i)));
4573 }
4574 let b_m = b.merkleize(&db, None, &mut Proportional).await.unwrap();
4575
4576 let mut c = b_m.new_batch::<Sha256>();
4577 for i in 6..9 {
4578 c = c.write(key(i), Some(val(i)));
4579 }
4580 let c_m = c.merkleize(&db, None, &mut Proportional).await.unwrap();
4581
4582 drop(a_m);
4584 drop(b_m);
4585
4586 let db = db.apply_batch(c_m).await.unwrap().commit().await.unwrap();
4588
4589 for i in 0..9 {
4591 assert_eq!(
4592 db.get(&key(i)).await.unwrap(),
4593 Some(val(i)),
4594 "key({i}) missing after apply_batch with dropped ancestors"
4595 );
4596 }
4597
4598 db.destroy().await.unwrap();
4599 });
4600 }
4601
4602 #[test_traced("WARN")]
4611 fn test_current_chain_bitmap_order_matches_sequential() {
4612 let executor = deterministic::Runner::default();
4613 executor.start(|context| async move {
4614 let ctx1 = context.child("db").with_attribute("index", 1);
4615 let db1: UnorderedVariableDb = UnorderedVariableDb::init(
4616 ctx1.child("db"),
4617 variable_config::<OneCap>("ord1"),
4618 None,
4619 )
4620 .await
4621 .unwrap();
4622
4623 let db1 = commit_writes_with_metadata(
4625 db1,
4626 [(key(10), Some(val(10))), (key(11), Some(val(11)))],
4627 None,
4628 )
4629 .await;
4630
4631 let a = db1
4638 .new_batch()
4639 .write(key(10), Some(val(100)))
4640 .write(key(11), None) .merkleize(&db1, None, &mut Proportional)
4642 .await
4643 .unwrap();
4644
4645 let b = a
4646 .new_batch::<Sha256>()
4647 .write(key(12), Some(val(120)))
4648 .write(key(13), Some(val(130)))
4649 .merkleize(&db1, None, &mut Proportional)
4650 .await
4651 .unwrap();
4652
4653 let c = b
4654 .new_batch::<Sha256>()
4655 .write(key(14), Some(val(140)))
4656 .merkleize(&db1, None, &mut Proportional)
4657 .await
4658 .unwrap();
4659
4660 let db1 = db1.apply_batch(c).await.unwrap().commit().await.unwrap();
4661
4662 let d1 = db1
4664 .new_batch()
4665 .write(key(20), Some(val(200)))
4666 .merkleize(&db1, None, &mut Proportional)
4667 .await
4668 .unwrap();
4669 let chain_then_d_root = d1.root();
4670
4671 let ctx2 = context.child("db").with_attribute("index", 2);
4672 let db2: UnorderedVariableDb = UnorderedVariableDb::init(
4673 ctx2.child("db"),
4674 variable_config::<OneCap>("ord2"),
4675 None,
4676 )
4677 .await
4678 .unwrap();
4679
4680 let db2 = commit_writes_with_metadata(
4681 db2,
4682 [(key(10), Some(val(10))), (key(11), Some(val(11)))],
4683 None,
4684 )
4685 .await;
4686
4687 let a2 = db2
4688 .new_batch()
4689 .write(key(10), Some(val(100)))
4690 .write(key(11), None)
4691 .merkleize(&db2, None, &mut Proportional)
4692 .await
4693 .unwrap();
4694 let db2 = db2.apply_batch(a2).await.unwrap().commit().await.unwrap();
4695
4696 let b2 = db2
4697 .new_batch()
4698 .write(key(12), Some(val(120)))
4699 .write(key(13), Some(val(130)))
4700 .merkleize(&db2, None, &mut Proportional)
4701 .await
4702 .unwrap();
4703 let db2 = db2.apply_batch(b2).await.unwrap().commit().await.unwrap();
4704
4705 let c2 = db2
4706 .new_batch()
4707 .write(key(14), Some(val(140)))
4708 .merkleize(&db2, None, &mut Proportional)
4709 .await
4710 .unwrap();
4711 let db2 = db2.apply_batch(c2).await.unwrap().commit().await.unwrap();
4712
4713 let d2 = db2
4714 .new_batch()
4715 .write(key(20), Some(val(200)))
4716 .merkleize(&db2, None, &mut Proportional)
4717 .await
4718 .unwrap();
4719 let sequential_then_d_root = d2.root();
4720
4721 assert_eq!(
4722 chain_then_d_root, sequential_then_d_root,
4723 "batch D's root on top of chain-applied state must match sequential state"
4724 );
4725
4726 db1.destroy().await.unwrap();
4727 db2.destroy().await.unwrap();
4728 });
4729 }
4730
4731 #[test_traced("WARN")]
4743 fn test_current_stale_bitmap_clears_after_prune() {
4744 let executor = deterministic::Runner::default();
4745 executor.start(|context| async move {
4746 let ctx = context.child("db");
4747 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4748 ctx.child("storage"),
4749 variable_config::<OneCap>("stale-clears"),
4750 None,
4751 )
4752 .await
4753 .unwrap();
4754
4755 let mut seed = db.new_batch();
4757 for i in 0u64..255 {
4758 seed = seed.write(key(i), Some(val(i)));
4759 }
4760 let seed_m = seed.merkleize(&db, None, &mut Proportional).await.unwrap();
4761 let db = db.apply_batch(seed_m).await.unwrap();
4762 let db = db.commit().await.unwrap();
4763
4764 let mut p = db.new_batch();
4767 for i in 1u64..255 {
4768 p = p.write(key(i), Some(val(i + 10000)));
4769 }
4770 let p_m = p.merkleize(&db, None, &mut Proportional).await.unwrap();
4771
4772 let c_m = p_m
4774 .new_batch::<Sha256>()
4775 .write(key(0), Some(val(9999)))
4776 .merkleize(&db, None, &mut Proportional)
4777 .await
4778 .unwrap();
4779
4780 let db = db.apply_batch(p_m).await.unwrap().commit().await.unwrap();
4782
4783 let floor = *db.inactivity_floor_loc();
4784 assert!(floor >= 256, "floor must be past chunk 0: floor={floor}",);
4785
4786 let boundary = db.sync_boundary();
4787 let db = db.prune(boundary).await.unwrap();
4788 let db = db.apply_batch(c_m).await.unwrap();
4789
4790 db.destroy().await.unwrap();
4791 });
4792 }
4793
4794 #[test_traced("INFO")]
4797 fn test_current_partial_ancestor_commit() {
4798 let executor = deterministic::Runner::default();
4799 executor.start(|context| async move {
4800 let ctx = context.child("db");
4801 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4802 ctx.child("storage"),
4803 variable_config::<OneCap>("pac"),
4804 None,
4805 )
4806 .await
4807 .unwrap();
4808
4809 let a = db
4810 .new_batch()
4811 .write(key(0), Some(val(0)))
4812 .merkleize(&db, None, &mut Proportional)
4813 .await
4814 .unwrap();
4815 let b = a
4816 .new_batch::<Sha256>()
4817 .write(key(1), Some(val(1)))
4818 .merkleize(&db, None, &mut Proportional)
4819 .await
4820 .unwrap();
4821 let c = b
4822 .new_batch::<Sha256>()
4823 .write(key(2), Some(val(2)))
4824 .merkleize(&db, None, &mut Proportional)
4825 .await
4826 .unwrap();
4827
4828 let expected_root = c.root();
4829
4830 let db = db.apply_batch(a).await.unwrap();
4831 let db = db.apply_batch(c).await.unwrap();
4832
4833 assert_eq!(db.root(), expected_root);
4834 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
4835 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
4836 assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
4837
4838 db.destroy().await.unwrap();
4839 });
4840 }
4841
4842 #[test_traced("INFO")]
4846 fn test_current_partial_ancestor_bitmap_ordering() {
4847 let executor = deterministic::Runner::default();
4848 executor.start(|context| async move {
4849 let ctx = context.child("db");
4850 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4851 ctx.child("storage"),
4852 variable_config::<OneCap>("bmo"),
4853 None,
4854 )
4855 .await
4856 .unwrap();
4857
4858 let a = db
4860 .new_batch()
4861 .write(key(0), Some(val(0)))
4862 .merkleize(&db, None, &mut Proportional)
4863 .await
4864 .unwrap();
4865 let b = a
4866 .new_batch::<Sha256>()
4867 .write(key(1), Some(val(1)))
4868 .merkleize(&db, None, &mut Proportional)
4869 .await
4870 .unwrap();
4871 let c = b
4872 .new_batch::<Sha256>()
4873 .write(key(2), Some(val(2)))
4874 .merkleize(&db, None, &mut Proportional)
4875 .await
4876 .unwrap();
4877 let d = c
4878 .new_batch::<Sha256>()
4879 .write(key(3), Some(val(3)))
4880 .merkleize(&db, None, &mut Proportional)
4881 .await
4882 .unwrap();
4883
4884 let db = db.apply_batch(a).await.unwrap();
4888 let db = db.apply_batch(d.clone()).await.unwrap();
4889
4890 let e = db
4895 .new_batch()
4896 .write(key(4), Some(val(4)))
4897 .merkleize(&db, None, &mut Proportional)
4898 .await
4899 .unwrap();
4900 let db = db.apply_batch(e).await.unwrap();
4901
4902 let ref_ctx = context.child("ref");
4904 let mut ref_db: UnorderedVariableDb = UnorderedVariableDb::init(
4905 ref_ctx.child("db"),
4906 variable_config::<OneCap>("bmo_ref"),
4907 None,
4908 )
4909 .await
4910 .unwrap();
4911 for i in 0..5 {
4912 let batch = ref_db
4913 .new_batch()
4914 .write(key(i), Some(val(i)))
4915 .merkleize(&ref_db, None, &mut Proportional)
4916 .await
4917 .unwrap();
4918 ref_db = ref_db.apply_batch(batch).await.unwrap();
4919 }
4920
4921 assert_eq!(
4922 db.root(),
4923 ref_db.root(),
4924 "root mismatch: bitmap ordering bug"
4925 );
4926
4927 db.destroy().await.unwrap();
4928 ref_db.destroy().await.unwrap();
4929 });
4930 }
4931
4932 #[test_traced("INFO")]
4942 fn test_current_apply_chunks_match_speculative_chunks() {
4943 const N: usize = 32;
4944 const CHUNK_SIZE_BITS: u64 = commonware_utils::bitmap::Prunable::<N>::CHUNK_SIZE_BITS;
4945 const SEED_KEYS: u64 = CHUNK_SIZE_BITS + 50;
4948
4949 let executor = deterministic::Runner::default();
4950 executor.start(|context| async move {
4951 let ctx = context.child("db");
4952 let db: UnorderedVariableDb = UnorderedVariableDb::init(
4953 ctx.child("storage"),
4954 variable_config::<OneCap>("spec_eq"),
4955 None,
4956 )
4957 .await
4958 .unwrap();
4959
4960 let seed = (0..SEED_KEYS).fold(db.new_batch(), |b, i| b.write(key(i), Some(val(i))));
4962 let seed = seed.merkleize(&db, None, &mut Proportional).await.unwrap();
4963 let db = db.apply_batch(seed).await.unwrap().commit().await.unwrap();
4964
4965 assert!(
4967 Readable::<N>::len(db.any.bitmap.as_ref()) > CHUNK_SIZE_BITS,
4968 "setup must cross a chunk boundary",
4969 );
4970
4971 let parent = db
4973 .new_batch()
4974 .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, &mut Proportional)
4980 .await
4981 .unwrap();
4982
4983 let child = parent
4988 .new_batch::<Sha256>()
4989 .write(key(10), Some(val(210)))
4990 .write(key(SEED_KEYS), None)
4991 .write(key(75), None)
4992 .write(key(CHUNK_SIZE_BITS + 30), Some(val(220)))
4993 .merkleize(&db, None, &mut Proportional)
4994 .await
4995 .unwrap();
4996
4997 let speculative_chunks: Vec<[u8; N]> = {
4999 let len = Readable::<N>::len(&child.bitmap);
5000 let chunk_count = len.div_ceil(CHUNK_SIZE_BITS) as usize;
5001 (0..chunk_count)
5002 .map(|idx| Readable::<N>::get_chunk(&child.bitmap, idx))
5003 .collect()
5004 };
5005 assert!(speculative_chunks.len() >= 2);
5007
5008 let db = db.apply_batch(child).await.unwrap();
5012 let committed_chunks: Vec<[u8; N]> = {
5013 let len = Readable::<N>::len(db.any.bitmap.as_ref());
5014 let chunk_count = len.div_ceil(CHUNK_SIZE_BITS) as usize;
5015 (0..chunk_count)
5016 .map(|idx| Readable::<N>::get_chunk(db.any.bitmap.as_ref(), idx))
5017 .collect()
5018 };
5019
5020 assert_eq!(
5021 speculative_chunks, committed_chunks,
5022 "speculative chunks must equal post-apply committed chunks across all chunks",
5023 );
5024
5025 db.destroy().await.unwrap();
5026 });
5027 }
5028
5029 #[test_traced("INFO")]
5031 fn test_current_mmb_ops_historical_proof_verifies_with_backward_bagging() {
5032 let executor = deterministic::Runner::default();
5033 executor.start(|context| async move {
5034 let ctx = context.child("db");
5035 let db: UnorderedFixedMmbDb = UnorderedFixedMmbDb::init(
5036 ctx.child("storage"),
5037 fixed_config::<OneCap>("mmb-ops-proof"),
5038 None,
5039 )
5040 .await
5041 .unwrap();
5042
5043 let writes: Vec<(Digest, Option<Digest>)> =
5045 (0u64..16).map(|i| (key(i), Some(val(i)))).collect();
5046 let db = commit_writes(db, writes).await.unwrap();
5047
5048 let ops_root = db.ops_root();
5049 let historical_size = db.bounds().end;
5050 let (proof, ops) = db
5051 .ops_historical_proof(historical_size, Location::new(0), NZU64!(32))
5052 .await
5053 .unwrap();
5054
5055 assert!(verify_proof::<Sha256, _, _>(
5057 &proof,
5058 Location::new(0),
5059 &ops,
5060 &ops_root
5061 ));
5062
5063 db.destroy().await.unwrap();
5064 });
5065 }
5066
5067 #[test_traced("INFO")]
5070 fn test_current_ordered_policy_replace_and_ancestor_proofs() {
5071 deterministic::Runner::default().start(|context| async move {
5072 let ctx = context.child("db");
5073 let partition = "current-ordered-policy-ancestor";
5074 let db: OrderedFixedDb = OrderedFixedDb::init(
5075 ctx.child("storage"),
5076 fixed_config::<OneCap>(partition),
5077 None,
5078 )
5079 .await
5080 .unwrap();
5081
5082 let mut keys = [key(1), key(2), key(3)];
5084 keys.sort();
5085 let seed = keys
5086 .into_iter()
5087 .enumerate()
5088 .fold(db.new_batch(), |batch, (i, key)| {
5089 batch.write(key, Some(val(i as u64)))
5090 });
5091 let seed = seed.merkleize(&db, None, &mut Hold).await.unwrap();
5092 let db = db.apply_batch(seed).await.unwrap().commit().await.unwrap();
5093
5094 let parent = db
5096 .new_batch()
5097 .write(keys[1], Some(val(11)))
5098 .merkleize(&db, None, &mut Hold)
5099 .await
5100 .unwrap();
5101
5102 let oldest = keys[0];
5106 let mut policy = Script::new(usize::MAX, 2, move |key: &Digest| {
5107 if *key == oldest {
5108 Choice::Replace(val(10))
5109 } else {
5110 Choice::Evict
5111 }
5112 });
5113 let child = parent
5114 .new_batch::<Sha256>()
5115 .merkleize(&db, None, &mut policy)
5116 .await
5117 .unwrap();
5118 let first = policy.visited[0].0;
5119 assert_eq!(
5120 policy.visited,
5121 [
5122 (first, keys[0], val(0)),
5123 (Location::new(*first + 2), keys[2], val(2)),
5124 ]
5125 );
5126 assert_eq!(child.bounds().inactivity_floor, Location::new(*first + 3));
5127
5128 let speculative_root = child.root();
5131 let db = db.apply_batch(parent).await.unwrap();
5132 let db = db.apply_batch(child).await.unwrap();
5133 let root = db.root();
5134 assert_eq!(root, speculative_root);
5135 let proof = db.key_value_proof(keys[0]).await.unwrap();
5136 assert_eq!(proof.next_key, keys[1]);
5137 assert!(
5138 proof.verify::<Sha256, crate::qmdb::any::value::FixedEncoding<Digest>>(
5139 keys[0],
5140 val(10),
5141 &root
5142 )
5143 );
5144 let proof = db.key_value_proof(keys[1]).await.unwrap();
5145 assert_eq!(proof.next_key, keys[0]);
5146 assert!(
5147 proof.verify::<Sha256, crate::qmdb::any::value::FixedEncoding<Digest>>(
5148 keys[1],
5149 val(11),
5150 &root
5151 )
5152 );
5153 let exclusion = db.exclusion_proof(&keys[2]).await.unwrap();
5154 assert!(exclusion.verify::<Sha256>(&keys[2], &root));
5155
5156 let db = db.sync().await.unwrap();
5158 assert_eq!(db.root(), root);
5159 drop(db);
5160 let reopened: OrderedFixedDb =
5161 OrderedFixedDb::init(ctx.child("reopen"), fixed_config::<OneCap>(partition), None)
5162 .await
5163 .unwrap();
5164 assert_eq!(reopened.root(), root);
5165 assert!(
5166 reopened
5167 .exclusion_proof(&keys[2])
5168 .await
5169 .unwrap()
5170 .verify::<Sha256>(&keys[2], &root)
5171 );
5172 reopened.destroy().await.unwrap();
5173 });
5174 }
5175
5176 #[test_traced("INFO")]
5179 fn test_current_ordered_policy_to_empty_proves_exclusion() {
5180 deterministic::Runner::default().start(|context| async move {
5181 let ctx = context.child("db");
5182 let db: OrderedFixedDb = OrderedFixedDb::init(
5183 ctx.child("storage"),
5184 fixed_config::<OneCap>("current-ordered-policy-empty"),
5185 None,
5186 )
5187 .await
5188 .unwrap();
5189
5190 let k = key(7);
5192 let seed = db
5193 .new_batch()
5194 .write(k, Some(val(7)))
5195 .merkleize(&db, None, &mut Proportional)
5196 .await
5197 .unwrap();
5198 let db = db.apply_batch(seed).await.unwrap();
5199 assert!(!db.is_empty());
5200
5201 let mut policy = Script::new(usize::MAX, u64::MAX, |_: &Digest| Choice::Evict);
5203 let batch = db
5204 .new_batch()
5205 .merkleize(&db, None, &mut policy)
5206 .await
5207 .unwrap();
5208 assert_eq!(policy.visited.len(), 1);
5209 assert_eq!((policy.visited[0].1, policy.visited[0].2), (k, val(7)));
5210
5211 let db = db.apply_batch(batch).await.unwrap();
5213 assert_eq!(db.get(&k).await.unwrap(), None);
5214 assert!(db.is_empty());
5215 let proof = db.exclusion_proof(&k).await.unwrap();
5216 assert!(matches!(
5217 proof,
5218 ordered::proof::constant::ExclusionProof::Commit(..)
5219 ));
5220 assert!(proof.verify::<Sha256>(&k, &db.root()));
5221 db.destroy().await.unwrap();
5222 });
5223 }
5224
5225 #[test_traced("INFO")]
5228 fn test_current_policy_skips_updates_superseded_by_pending_parent() {
5229 deterministic::Runner::default().start(|context| async move {
5230 let ctx = context.child("db");
5231 let db: UnorderedFixedDb = UnorderedFixedDb::init(
5232 ctx.child("storage"),
5233 fixed_config::<OneCap>("current-policy-speculative-skip"),
5234 None,
5235 )
5236 .await
5237 .unwrap();
5238
5239 let mut keys: Vec<_> = (40..50).map(key).collect();
5241 keys.sort();
5242 let seed = keys
5243 .iter()
5244 .enumerate()
5245 .fold(db.new_batch(), |batch, (i, key)| {
5246 batch.write(*key, Some(val(i as u64)))
5247 })
5248 .merkleize(&db, None, &mut Hold)
5249 .await
5250 .unwrap();
5251 let db = db.apply_batch(seed).await.unwrap().commit().await.unwrap();
5252
5253 let parent = keys[..9]
5255 .iter()
5256 .fold(db.new_batch(), |batch, key| {
5257 batch.write(*key, Some(val(100)))
5258 })
5259 .merkleize(&db, None, &mut Hold)
5260 .await
5261 .unwrap();
5262
5263 let items_read = || counter(&context, "log_journal_items_read_total");
5265 let before = items_read();
5266 let mut reads = Vec::new();
5267 let mut policy = Script::new(1, u64::MAX, |_: &Digest| {
5268 reads.push(items_read());
5269 Choice::Keep
5270 });
5271 let child = parent
5272 .new_batch::<Sha256>()
5273 .merkleize(&db, None, &mut policy)
5274 .await
5275 .unwrap();
5276 assert_eq!(policy.visited.len(), 1);
5277 assert_eq!(policy.visited[0].1, keys[9]);
5278 assert_eq!(reads, [before + 1]);
5279
5280 drop((child, parent));
5281 db.destroy().await.unwrap();
5282 });
5283 }
5284
5285 #[test_traced("INFO")]
5289 fn test_current_staged_walk_continues_past_applied_candidates() {
5290 deterministic::Runner::default().start(|context| async move {
5291 let ctx = context.child("db");
5292 let mut db: UnorderedFixedDb = UnorderedFixedDb::init(
5293 ctx.child("storage"),
5294 fixed_config::<OneCap>("current-staged-walk"),
5295 None,
5296 )
5297 .await
5298 .unwrap();
5299
5300 let rounds = CHUNK_BITS / 2;
5304 for i in 0..rounds {
5305 let batch = db
5306 .new_batch()
5307 .write(key(0), Some(val(i)))
5308 .merkleize(&db, None, &mut Proportional)
5309 .await
5310 .unwrap();
5311 db = db.apply_batch(batch).await.unwrap();
5312 }
5313 let db = db.commit().await.unwrap();
5314 let floor = db.inactivity_floor_loc();
5315 let db = db
5316 .prune(Location::new(*floor / CHUNK_BITS * CHUNK_BITS))
5317 .await
5318 .unwrap();
5319 assert!(db.pruned_bits() > 0);
5320
5321 let mut keys: Vec<_> = (1..5).map(key).collect();
5325 keys.sort();
5326 let seed = keys
5327 .iter()
5328 .fold(db.new_batch(), |batch, k| batch.write(*k, Some(val(1))))
5329 .merkleize(&db, None, &mut Hold)
5330 .await
5331 .unwrap();
5332 let db = db.apply_batch(seed).await.unwrap();
5333 let parent = keys
5334 .iter()
5335 .fold(db.new_batch(), |batch, k| batch.write(*k, Some(val(2))))
5336 .merkleize(&db, None, &mut Hold)
5337 .await
5338 .unwrap();
5339
5340 let (_, staged) = parent
5344 .new_batch::<Sha256>()
5345 .stage(&[&keys[0]], &db)
5346 .await
5347 .unwrap();
5348 let staged = staged
5349 .merkleize(
5350 vec![Slot::Put(val(3))],
5351 Vec::new(),
5352 None,
5353 &db,
5354 &mut Proportional,
5355 )
5356 .await
5357 .unwrap();
5358 let direct = parent
5359 .new_batch::<Sha256>()
5360 .write(keys[0], Some(val(3)))
5361 .merkleize(&db, None, &mut Proportional)
5362 .await
5363 .unwrap();
5364 assert_eq!(staged.root(), direct.root());
5365 let (_, operations) = staged.operations();
5366 assert_eq!(
5367 operations[1..3],
5368 [
5369 Operation::Update(update::Unordered(key(0), val(rounds - 1))),
5370 Operation::Update(update::Unordered(keys[1], val(2))),
5371 ]
5372 );
5373
5374 let db = db.apply_batch(parent).await.unwrap();
5376 let twin = db
5377 .new_batch()
5378 .write(keys[0], Some(val(3)))
5379 .merkleize(&db, None, &mut Proportional)
5380 .await
5381 .unwrap();
5382 assert_eq!(twin.root(), staged.root());
5383 drop((staged, direct, twin));
5384 db.destroy().await.unwrap();
5385 });
5386 }
5387
5388 trait Proves {
5390 async fn assert_proves(&self, live: &BTreeMap<Digest, Digest>, evicted: &[Digest]);
5393 }
5394
5395 macro_rules! unordered_proves {
5397 ($($db:ty),+) => {$(
5398 impl Proves for $db {
5399 async fn assert_proves(&self, live: &BTreeMap<Digest, Digest>, evicted: &[Digest]) {
5400 let root = self.root();
5401 for (key, value) in live {
5402 let proof = self.key_value_proof(*key).await.unwrap();
5403 assert!(
5404 Self::verify_key_value_proof(*key, *value, &proof, &root),
5405 "{key} fails to prove its value",
5406 );
5407 }
5408 for key in evicted {
5409 assert_eq!(self.get(key).await.unwrap(), None, "{key} is live");
5410 assert!(matches!(
5411 self.key_value_proof(*key).await,
5412 Err(Error::KeyNotFound)
5413 ));
5414 }
5415 }
5416 }
5417 )+};
5418 }
5419
5420 macro_rules! ordered_proves {
5423 ($($db:ty),+) => {$(
5424 impl Proves for $db {
5425 async fn assert_proves(&self, live: &BTreeMap<Digest, Digest>, evicted: &[Digest]) {
5426 let keys: Vec<_> = live.keys().copied().collect();
5427 for (i, (key, value)) in live.iter().enumerate() {
5428 self.assert_link(*key, *value, keys[(i + 1) % keys.len()])
5429 .await;
5430 }
5431 for key in evicted {
5432 self.assert_absent(*key).await;
5433 }
5434 }
5435 }
5436 )+};
5437 }
5438
5439 unordered_proves!(UnorderedFixedDb, UnorderedFixedMmbDb);
5440 ordered_proves!(OrderedFixedDb, OrderedFixedMmbDb);
5441
5442 macro_rules! policy_consistency_test {
5452 ($name:ident, $db:ty) => {
5453 #[test_traced("WARN")]
5454 fn $name() {
5455 deterministic::Runner::default().start(|context| async move {
5456 let ctx = context.child("db");
5457 let partition = stringify!($name);
5458 let mut db: $db = <$db>::init(
5459 ctx.child("storage"),
5460 fixed_config::<OneCap>(partition),
5461 None,
5462 )
5463 .await
5464 .unwrap();
5465
5466 let keys: Vec<Digest> = (0..7).map(key).collect();
5469 let created = [key(7), key(8)];
5470 let fillers: Vec<Digest> = (100..110).map(key).collect();
5471 let mut values = BTreeMap::new();
5472 let mut round = 0;
5473 while *db.size() + 11 <= CHUNK_BITS - 6 {
5474 let writes: Vec<Digest> = if round == 0 {
5475 keys.iter().chain(&fillers).copied().collect()
5476 } else {
5477 fillers.clone()
5478 };
5479 let batch = writes
5480 .iter()
5481 .fold(db.new_batch(), |batch, key| {
5482 batch.write(*key, Some(val(round)))
5483 })
5484 .merkleize(&db, None, &mut Hold)
5485 .await
5486 .unwrap();
5487 db = db.apply_batch(batch).await.unwrap();
5488 for key in writes {
5489 values.insert(key, val(round));
5490 }
5491 round += 1;
5492 }
5493 assert_eq!(*db.size(), CHUNK_BITS - 6);
5494
5495 let parent_writes = [(keys[2], val(1002)), (keys[3], val(1003))];
5497 let parent = parent_writes
5498 .iter()
5499 .fold(db.new_batch(), |batch, (key, value)| {
5500 batch.write(*key, Some(*value))
5501 })
5502 .merkleize(&db, None, &mut Hold)
5503 .await
5504 .unwrap();
5505 values.extend(parent_writes);
5506
5507 let child_writes = [
5509 (keys[4], val(2004)),
5510 (keys[5], val(2005)),
5511 (keys[6], val(2006)),
5512 (created[0], val(2007)),
5513 (created[1], val(2008)),
5514 ];
5515 let write = |batch: <$db as crate::qmdb::any::traits::BatchableDb>::Batch| {
5516 child_writes
5517 .iter()
5518 .fold(batch, |batch, (key, value)| batch.write(*key, Some(*value)))
5519 };
5520 values.extend(child_writes);
5521
5522 let evict = [keys[0], keys[2], keys[6], created[0]];
5526 let replace = [(keys[3], val(3003)), (keys[5], val(3005))];
5527 let choose = move |key: &Digest| {
5528 if evict.contains(key) {
5529 Choice::Evict
5530 } else if let Some((_, value)) = replace.iter().find(|(k, _)| k == key) {
5531 Choice::Replace(*value)
5532 } else {
5533 Choice::Keep
5534 }
5535 };
5536
5537 let mut live = db.live().await;
5541 let (start, ops) = parent.operations();
5542 replay(&mut live, start, &ops);
5543 let held = write(parent.new_batch::<Sha256>())
5544 .merkleize(&db, None, &mut Hold)
5545 .await
5546 .unwrap();
5547 let (start, ops) = held.operations();
5548 let tip = start + (ops.len() as u64 - 1);
5549 assert!(*parent.bounds().tip.size < CHUNK_BITS && CHUNK_BITS < *tip);
5550 let mut written = live.clone();
5551 replay(&mut written, start, &ops);
5552 let mut expected: Vec<_> = written
5553 .iter()
5554 .map(|(key, loc)| (*loc, *key, values[key]))
5555 .collect();
5556 expected.sort();
5557 drop(held);
5558 let mut policy = Script::new(usize::MAX, u64::MAX, choose);
5559 let child = write(parent.new_batch::<Sha256>())
5560 .merkleize(&db, None, &mut policy)
5561 .await
5562 .unwrap();
5563 assert_eq!(policy.visited, expected);
5564 assert_eq!(child.bounds().inactivity_floor, tip);
5565
5566 let mut model = values.clone();
5569 for key in &evict {
5570 model.remove(key);
5571 }
5572 model.extend(replace);
5573 let (start, ops) = child.operations();
5574 replay(&mut live, start, &ops);
5575 assert!(live.keys().eq(model.keys()));
5576 assert_eq!(Readable::<32>::len(&child.bitmap), *child.bounds().tip.size);
5577 assert_bits(&child.bitmap, &live);
5578 for key in values.keys() {
5579 assert_eq!(db.read(&child, key).await, model.get(key).copied());
5580 }
5581
5582 let root = child.root();
5585 let db = db.apply_batch(parent).await.unwrap();
5586 let mut twin_policy = Script::new(usize::MAX, u64::MAX, choose);
5587 let twin = write(db.new_batch())
5588 .merkleize(&db, None, &mut twin_policy)
5589 .await
5590 .unwrap();
5591 assert_eq!(twin_policy.visited, policy.visited);
5592 assert_eq!(twin.operations(), child.operations());
5593 assert_eq!(twin.bounds().inactivity_floor, tip);
5594 assert_eq!(twin.root(), root);
5595 assert_bits(&twin.bitmap, &live);
5596 drop(twin);
5597
5598 let db = db.apply_batch(child).await.unwrap();
5601 assert_eq!(db.root(), root);
5602 assert_eq!(db.inactivity_floor_loc(), tip);
5603 assert_eq!(db.live().await, live);
5604 assert_bits(db.bitmap(), &live);
5605 db.assert_exact().await;
5606 db.assert_proves(&model, &evict).await;
5607
5608 let db = db.sync().await.unwrap();
5610 drop(db);
5611 let db: $db =
5612 <$db>::init(ctx.child("reopen"), fixed_config::<OneCap>(partition), None)
5613 .await
5614 .unwrap();
5615 assert_eq!(db.root(), root);
5616 assert_bits(db.bitmap(), &live);
5617 db.assert_exact().await;
5618 db.assert_proves(&model, &evict).await;
5619 db.destroy().await.unwrap();
5620 });
5621 }
5622 };
5623 }
5624
5625 policy_consistency_test!(
5626 test_current_unordered_policy_bits_and_proofs,
5627 UnorderedFixedDb
5628 );
5629 policy_consistency_test!(test_current_ordered_policy_bits_and_proofs, OrderedFixedDb);
5630
5631 macro_rules! policy_prune_test {
5648 ($name:ident, $db:ty, $family:ty, $pending:literal, $pruned:literal) => {
5649 #[test_traced("WARN")]
5650 fn $name() {
5651 deterministic::Runner::default().start(|context| async move {
5652 let ctx = context.child("db");
5653 let partition = stringify!($name);
5654 let mut db: $db = <$db>::init(
5655 ctx.child("storage"),
5656 fixed_config::<OneCap>(partition),
5657 None,
5658 )
5659 .await
5660 .unwrap();
5661
5662 let keys: Vec<Digest> = (0..8).map(key).collect();
5666 let created = [key(8), key(9)];
5667 let fillers: Vec<Digest> = (100..220).map(key).collect();
5668 let start = 3 * CHUNK_BITS - 8;
5669 let mut values = BTreeMap::new();
5670 let mut round = 0;
5671 while *db.size() < start {
5672 let writes: Vec<Digest> = if round == 0 {
5673 keys.iter().chain(&fillers).copied().collect()
5674 } else {
5675 fillers[..(start - *db.size() - 1).min(10) as usize].to_vec()
5676 };
5677 let batch = writes
5678 .iter()
5679 .fold(db.new_batch(), |batch, key| {
5680 batch.write(*key, Some(val(round)))
5681 })
5682 .merkleize(&db, None, &mut Hold)
5683 .await
5684 .unwrap();
5685 db = db.apply_batch(batch).await.unwrap();
5686 for key in writes {
5687 values.insert(key, val(round));
5688 }
5689 round += 1;
5690 }
5691 assert_eq!(*db.size(), start);
5692
5693 let grand_writes: Vec<_> = [keys[1], keys[2]]
5696 .iter()
5697 .chain(&fillers[10..16])
5698 .zip(1000..)
5699 .map(|(key, i)| (*key, val(i)))
5700 .collect();
5701 let parent_writes = [(keys[3], val(2003)), (keys[7], val(2007))];
5702 let grandparent = grand_writes
5703 .iter()
5704 .fold(db.new_batch(), |batch, (key, value)| {
5705 batch.write(*key, Some(*value))
5706 })
5707 .merkleize(&db, None, &mut Hold)
5708 .await
5709 .unwrap();
5710 let parent = parent_writes
5711 .iter()
5712 .fold(grandparent.new_batch::<Sha256>(), |batch, (key, value)| {
5713 batch.write(*key, Some(*value))
5714 })
5715 .merkleize(&db, None, &mut Hold)
5716 .await
5717 .unwrap();
5718 values.extend(grand_writes.iter().copied());
5719 values.extend(parent_writes);
5720
5721 let child_writes = [
5724 (keys[4], val(3004)),
5725 (keys[5], val(3005)),
5726 (keys[6], val(3006)),
5727 (created[0], val(3008)),
5728 (created[1], val(3009)),
5729 ];
5730 values.extend(child_writes);
5731 let [held, pending, child] = [(); 3].map(|_| {
5732 child_writes
5733 .iter()
5734 .fold(parent.new_batch::<Sha256>(), |batch, (key, value)| {
5735 batch.write(*key, Some(*value))
5736 })
5737 });
5738
5739 let mut live = db.live().await;
5741 for batch in [&grandparent, &parent] {
5742 let (start, ops) = batch.operations();
5743 replay(&mut live, start, &ops);
5744 }
5745 let held = held.merkleize(&db, None, &mut Hold).await.unwrap();
5746 let (child_start, ops) = held.operations();
5747 let mut written = live.clone();
5748 replay(&mut written, child_start, &ops);
5749 drop(held);
5750
5751 let mut own: Vec<_> = child_writes
5757 .iter()
5758 .map(|(key, _)| (written[key], *key))
5759 .collect();
5760 own.sort();
5761 let (stopped, stop) = own[4];
5762 let choices: BTreeMap<Digest, Choice> = [
5763 (keys[0], Choice::Evict),
5764 (keys[2], Choice::Evict),
5765 (keys[3], Choice::Evict),
5766 (keys[1], Choice::Replace(val(4001))),
5767 (keys[7], Choice::Replace(val(4007))),
5768 (own[0].1, Choice::Evict),
5769 (own[1].1, Choice::Replace(val(4100))),
5770 (own[2].1, Choice::Evict),
5771 (stop, Choice::Stop),
5772 ]
5773 .into_iter()
5774 .collect();
5775 for key in [keys[0], keys[1], keys[2], keys[3], keys[7]] {
5776 assert!(written[&key] < child_start, "the child rewrites {key}");
5777 }
5778 let choose = |key: &Digest| choices.get(key).copied().unwrap_or(Choice::Keep);
5779
5780 let mut expected: Vec<_> = written
5783 .iter()
5784 .filter(|(_, loc)| **loc <= stopped)
5785 .map(|(key, loc)| (*loc, *key, values[key]))
5786 .collect();
5787 expected.sort();
5788 let mut policy = Script::new(usize::MAX, u64::MAX, choose);
5789 let pending = pending.merkleize(&db, None, &mut policy).await.unwrap();
5790 assert_eq!(policy.visited, expected);
5791 assert_eq!(pending.bounds().inactivity_floor, stopped);
5792
5793 let ungrafted = |db: &$db| {
5796 let complete = db.bitmap().complete_chunks() as u64;
5797 let graftable = grafting::graftable_chunks::<$family>(
5798 *db.size(),
5799 grafting::height::<32>(),
5800 );
5801 complete - graftable.min(complete)
5802 };
5803 let freed = Arc::downgrade(&grandparent.inner);
5804 let db = db.apply_batch(grandparent).await.unwrap();
5805 assert!(freed.upgrade().is_none());
5806 assert_eq!(*db.size(), 3 * CHUNK_BITS + 1);
5807 assert_eq!(ungrafted(&db), $pending);
5808
5809 let mut policy = Script::new(usize::MAX, u64::MAX, choose);
5812 let child = child.merkleize(&db, None, &mut policy).await.unwrap();
5813 assert_eq!(policy.visited, expected);
5814 assert_eq!(child.operations(), pending.operations());
5815 assert_eq!(child.bounds().inactivity_floor, stopped);
5816 let root = child.root();
5817 assert_eq!(pending.root(), root);
5818 drop(pending);
5819
5820 let mut model = values.clone();
5822 let mut evicted = Vec::new();
5823 for (key, choice) in &choices {
5824 match choice {
5825 Choice::Evict => {
5826 model.remove(key);
5827 evicted.push(*key);
5828 }
5829 Choice::Replace(value) => {
5830 model.insert(*key, *value);
5831 }
5832 Choice::Keep | Choice::Stop => {}
5833 }
5834 }
5835 let (start, ops) = child.operations();
5836 replay(&mut live, start, &ops);
5837 assert!(live.keys().eq(model.keys()));
5838
5839 let db = db.apply_batch(child).await.unwrap();
5842 drop(parent);
5843 assert_eq!(db.root(), root);
5844 assert_eq!(db.inactivity_floor_loc(), stopped);
5845 assert_eq!(db.bitmap().complete_chunks(), 3);
5846 assert_eq!(ungrafted(&db), 0);
5847 assert_eq!(db.live().await, live);
5848 assert_bits(db.bitmap(), &live);
5849 db.assert_exact().await;
5850 db.assert_proves(&model, &evicted).await;
5851
5852 let db = db.sync().await.unwrap();
5854 let boundary = db.sync_boundary();
5855 assert_eq!(*boundary, $pruned * CHUNK_BITS);
5856 let db = db.prune(boundary).await.unwrap();
5857 assert_eq!(db.pruned_bits(), *boundary);
5858 assert!(*db.bounds().start > 0 && db.bounds().start <= boundary);
5859 assert_eq!(db.root(), root);
5860 assert_eq!(db.live().await, live);
5861 assert_bits(db.bitmap(), &live);
5862 db.assert_exact().await;
5863 db.assert_proves(&model, &evicted).await;
5864
5865 let bounds = db.bounds();
5867 drop(db);
5868 let db: $db =
5869 <$db>::init(ctx.child("reopen"), fixed_config::<OneCap>(partition), None)
5870 .await
5871 .unwrap();
5872 assert_eq!(db.root(), root);
5873 assert_eq!(db.bounds(), bounds);
5874 assert_eq!(db.inactivity_floor_loc(), stopped);
5875 assert_eq!(db.pruned_bits(), *boundary);
5876 assert_eq!(db.live().await, live);
5877 assert_bits(db.bitmap(), &live);
5878 db.assert_exact().await;
5879 db.assert_proves(&model, &evicted).await;
5880 db.destroy().await.unwrap();
5881 });
5882 }
5883 };
5884 }
5885
5886 policy_prune_test!(
5887 test_current_unordered_policy_prune,
5888 UnorderedFixedDb,
5889 mmr::Family,
5890 0,
5891 3
5892 );
5893 policy_prune_test!(
5894 test_current_ordered_policy_prune,
5895 OrderedFixedDb,
5896 mmr::Family,
5897 0,
5898 3
5899 );
5900 policy_prune_test!(
5901 test_current_unordered_mmb_policy_prune,
5902 UnorderedFixedMmbDb,
5903 mmb::Family,
5904 1,
5905 2
5906 );
5907 policy_prune_test!(
5908 test_current_ordered_mmb_policy_prune,
5909 OrderedFixedMmbDb,
5910 mmb::Family,
5911 1,
5912 2
5913 );
5914
5915 async fn assert_policy_matches_any<M, A, C>(mut any: A, mut current: C)
5920 where
5921 M: merkle::Graftable,
5922 A: Inspect<M>,
5923 C: Inspect<M, Update = A::Update>,
5924 Operation<M, A::Update>: PartialEq + core::fmt::Debug,
5925 {
5926 let seed: Vec<_> = (0..24).map(|i| (key(i), Some(val(i)))).collect();
5928 let churn: Vec<_> = (0..24)
5929 .step_by(4)
5930 .map(|i| (key(i), Some(val(i + 100))))
5931 .collect();
5932 for writes in [&seed, &churn] {
5933 any = hold(any, writes).await;
5934 current = hold(current, writes).await;
5935 }
5936
5937 let parent: Vec<_> = (1..24)
5939 .step_by(3)
5940 .map(|i| (key(i), Some(val(i + 200))))
5941 .collect();
5942 let any_parent = hold_batch(&any, any.new_batch(), &parent).await;
5943 let current_parent = hold_batch(¤t, current.new_batch(), &parent).await;
5944
5945 let stop = key(5);
5948 let choose = |action: usize| {
5949 move |k: &Digest| match action {
5950 0 => Choice::Keep,
5951 1 => Choice::Evict,
5952 2 => Choice::Replace(val(1000)),
5953 3 if *k == stop => Choice::Stop,
5954 3 => Choice::Keep,
5955 _ => match k.as_ref()[0] % 3 {
5956 0 => Choice::Keep,
5957 1 => Choice::Evict,
5958 _ => Choice::Replace(val(1001)),
5959 },
5960 }
5961 };
5962 for (entries, skips) in [
5963 (usize::MAX, u64::MAX),
5964 (5, u64::MAX),
5965 (usize::MAX, 6),
5966 (3, 2),
5967 (0, 4),
5968 ] {
5969 for action in 0..5 {
5970 let label = format!("action={action} entries={entries} skips={skips}");
5971 let mut any_policy = Script::new(entries, skips, choose(action));
5972 let any_batch = A::child(&any_parent)
5973 .merkleize(&any, None, &mut any_policy)
5974 .await
5975 .unwrap();
5976 let mut current_policy = Script::new(entries, skips, choose(action));
5977 let current_batch = C::child(¤t_parent)
5978 .merkleize(¤t, None, &mut current_policy)
5979 .await
5980 .unwrap();
5981 assert_eq!(any_policy.visited, current_policy.visited, "{label}");
5982 assert_eq!(
5983 A::span(&any_batch).inactivity_floor,
5984 C::span(¤t_batch).inactivity_floor,
5985 "{label}"
5986 );
5987 assert_eq!(A::ops(&any_batch), C::ops(¤t_batch), "{label}");
5988
5989 if (entries, skips) == (usize::MAX, u64::MAX) {
5992 let keys: Vec<_> = any_policy.visited.iter().map(|(_, k, _)| *k).collect();
5993 match action {
5994 3 => assert_eq!(keys.last(), Some(&stop)),
5995 4 => assert!((0..3).all(|r| keys.iter().any(|k| k.as_ref()[0] % 3 == r))),
5996 _ => assert_eq!(keys.len(), 24),
5997 }
5998 }
5999 }
6000 }
6001
6002 drop((any_parent, current_parent));
6003 any.destroy().await.unwrap();
6004 current.destroy().await.unwrap();
6005 }
6006
6007 #[test_traced("INFO")]
6009 fn test_current_policy_matches_any() {
6010 type UnorderedAny = crate::qmdb::any::unordered::fixed::Db<
6011 mmr::Family,
6012 Context,
6013 Digest,
6014 Digest,
6015 Sha256,
6016 OneCap,
6017 Sequential,
6018 >;
6019 type OrderedAny = crate::qmdb::any::ordered::fixed::Db<
6020 mmr::Family,
6021 Context,
6022 Digest,
6023 Digest,
6024 Sha256,
6025 OneCap,
6026 Sequential,
6027 >;
6028
6029 deterministic::Runner::default().start(|context| async move {
6030 let any_config = |name: &str| crate::qmdb::any::test::fixed_db_config::<OneCap>(name);
6031 let any = UnorderedAny::init(
6032 context.child("unordered_any"),
6033 any_config("unordered-any"),
6034 None,
6035 )
6036 .await
6037 .unwrap();
6038 let current = UnorderedFixedDb::init(
6039 context.child("unordered_current"),
6040 fixed_config::<OneCap>("unordered-current"),
6041 None,
6042 )
6043 .await
6044 .unwrap();
6045 assert_policy_matches_any(any, current).await;
6046
6047 let any = OrderedAny::init(
6048 context.child("ordered_any"),
6049 any_config("ordered-any"),
6050 None,
6051 )
6052 .await
6053 .unwrap();
6054 let current = OrderedFixedDb::init(
6055 context.child("ordered_current"),
6056 fixed_config::<OneCap>("ordered-current"),
6057 None,
6058 )
6059 .await
6060 .unwrap();
6061 assert_policy_matches_any(any, current).await;
6062 });
6063 }
6064
6065 fn is_send<T: Send>(_: T) {}
6066
6067 #[allow(dead_code)]
6068 fn assert_policy_futures_are_send(
6069 unordered: &UnorderedFixedDb,
6070 ordered: &OrderedFixedDb,
6071 key: Digest,
6072 ) {
6073 let mut policy = Bounded {
6074 entries: 1,
6075 skips: 1,
6076 };
6077 is_send(
6078 unordered
6079 .new_batch()
6080 .merkleize(unordered, None, &mut policy),
6081 );
6082 is_send(ordered.new_batch().merkleize(ordered, None, &mut policy));
6083 is_send(async move {
6084 let (_, staged) = unordered.new_batch().stage(&[&key], unordered).await?;
6085 staged
6086 .merkleize(Vec::new(), Vec::new(), None, unordered, &mut policy)
6087 .await
6088 });
6089 is_send(async move {
6090 let (_, staged) = ordered.new_batch().stage(&[&key], ordered).await?;
6091 staged
6092 .merkleize(Vec::new(), Vec::new(), None, ordered, &mut policy)
6093 .await
6094 });
6095 }
6096
6097 impl<F, C, I, U, const N: usize, S> Inspect<F> for db::Db<F, Context, C, I, Sha256, U, N, S>
6098 where
6099 F: merkle::Graftable,
6100 C: Mutable<Item = Operation<F, U>>,
6101 I: UnorderedIndex<Value = Location<F>> + 'static,
6102 U: Update<Key = Digest, Value = Digest>,
6103 S: Strategy,
6104 Operation<F, U>: Codec,
6105 Self: DbAny<
6106 F,
6107 Key = Digest,
6108 Value = Digest,
6109 Digest = Digest,
6110 Merkleized = Arc<batch::MerkleizedBatch<F, Digest, U, N, S>>,
6111 Batch = batch::UnmerkleizedBatch<F, Sha256, U, N, S>,
6112 >,
6113 {
6114 type Update = U;
6115
6116 async fn assert_exact(&self) {
6117 assert_exact(&self.any).await;
6118 }
6119
6120 async fn live(&self) -> BTreeMap<Digest, Location<F>> {
6121 live(&self.any).await
6122 }
6123
6124 fn child(batch: &Self::Merkleized) -> Self::Batch {
6125 batch.new_batch::<Sha256>()
6126 }
6127
6128 fn span(batch: &Self::Merkleized) -> &Bounds<F, Digest> {
6129 batch.bounds()
6130 }
6131
6132 fn ops(batch: &Self::Merkleized) -> (Location<F>, Arc<Vec<Operation<F, U>>>) {
6133 batch.operations()
6134 }
6135
6136 async fn read(&self, batch: &Self::Merkleized, key: &Digest) -> Option<Digest> {
6137 batch.get(key, self).await.unwrap()
6138 }
6139 }
6140
6141 impl<F, C, I, V, const N: usize, S> Links<F>
6142 for db::Db<F, Context, C, I, Sha256, update::Ordered<Digest, V>, N, S>
6143 where
6144 F: merkle::Graftable,
6145 C: Mutable<Item = Operation<F, update::Ordered<Digest, V>>>,
6146 I: OrderedIndex<Value = Location<F>> + 'static,
6147 V: ValueEncoding<Value = Digest>,
6148 S: Strategy,
6149 Operation<F, update::Ordered<Digest, V>>: Codec,
6150 Self: Inspect<F>,
6151 {
6152 async fn assert_link(&self, key: Digest, value: Digest, next: Digest) {
6153 assert_eq!(self.get(&key).await.unwrap(), Some(value), "{key} diverged");
6154 let proof = self.key_value_proof(key).await.unwrap();
6155 assert_eq!(proof.next_key, next, "{key} links to the wrong key");
6156 assert!(
6157 proof.verify::<Sha256, V>(key, value, &self.root()),
6158 "{key} fails to prove its link",
6159 );
6160 }
6161
6162 async fn assert_absent(&self, key: Digest) {
6163 assert_eq!(self.get(&key).await.unwrap(), None, "{key} is live");
6164 let proof = self.exclusion_proof(&key).await.unwrap();
6165 assert!(
6166 proof.verify::<Sha256>(&key, &self.root()),
6167 "{key} fails to prove its exclusion",
6168 );
6169 }
6170 }
6171
6172 impl<F, C, I, V, const N: usize, S> Neighbors<F>
6173 for db::Db<F, Context, C, I, Sha256, update::Ordered<Digest, V>, N, S>
6174 where
6175 F: merkle::Graftable,
6176 C: Mutable<Item = Operation<F, update::Ordered<Digest, V>>>,
6177 I: OrderedIndex<Value = Location<F>> + 'static,
6178 V: ValueEncoding<Value = Digest>,
6179 S: Strategy,
6180 Operation<F, update::Ordered<Digest, V>>: Codec,
6181 Self: Links<F>
6182 + DbAny<
6183 F,
6184 Merkleized = Arc<
6185 batch::MerkleizedBatch<F, Digest, update::Ordered<Digest, V>, N, S>,
6186 >,
6187 >,
6188 {
6189 async fn neighbors(
6190 &self,
6191 batch: Option<&Self::Merkleized>,
6192 key: &Digest,
6193 ) -> (Option<Digest>, Option<Digest>) {
6194 match batch {
6195 Some(batch) => (
6196 batch.get_next_key(key, self).await.unwrap(),
6197 batch.get_prev_key(key, self).await.unwrap(),
6198 ),
6199 None => (
6200 self.get_next_key(key).await.unwrap(),
6201 self.get_prev_key(key).await.unwrap(),
6202 ),
6203 }
6204 }
6205 }
6206
6207 async fn current_db<C, F, Fut>(
6210 context: Context,
6211 open_db: F,
6212 partition: &'static str,
6213 ) -> (
6214 C,
6215 impl Fn(Context) -> Pin<Box<dyn Future<Output = C> + Send>> + Clone,
6216 )
6217 where
6218 F: Fn(Context, String) -> Fut + Clone + Send + 'static,
6219 Fut: Future<Output = C> + Send + 'static,
6220 {
6221 let db = open_db(context, partition.into()).await;
6222 let reopen = move |ctx: Context| -> Pin<Box<dyn Future<Output = C> + Send>> {
6223 Box::pin(open_db(ctx, partition.into()))
6224 };
6225 (db, reopen)
6226 }
6227
6228 macro_rules! current_test {
6232 ($variants:ident, $name:ident, $any:ident, $partition:literal, $bound:ident) => {
6233 current_test!(@define $variants, $name, $partition, $bound, |context, db, _| {
6234 $any(context, db, val)
6235 });
6236 };
6237 ($variants:ident, $name:ident, $any:ident, $partition:literal, $bound:ident, reopen) => {
6238 current_test!(@define $variants, $name, $partition, $bound, |context, db, reopen| {
6239 $any(context, db, reopen, val)
6240 });
6241 };
6242 (@define $variants:ident, $name:ident, $partition:literal, $bound:ident, $run:expr) => {
6243 async fn $name<M, C, F, Fut>(context: Context, open_db: F)
6244 where
6245 M: merkle::Graftable,
6246 C: $bound<M>,
6247 C::Merkleized: Clone,
6248 Operation<M, C::Update>: Codec,
6249 F: Fn(Context, String) -> Fut + Clone + Send + 'static,
6250 Fut: Future<Output = C> + Send + 'static,
6251 {
6252 let (db, reopen) = current_db(context.child("db"), open_db, $partition).await;
6253 ($run)(context, db, reopen).await;
6254 }
6255
6256 $variants!($name, "WARN");
6257 };
6258 }
6259
6260 current_test!(
6263 test_for_all_variants,
6264 test_current_activity_depths,
6265 test_any_activity_depths,
6266 "activity",
6267 Inspect,
6268 reopen
6269 );
6270 current_test!(
6271 test_for_all_variants,
6272 test_current_proportional_bound,
6273 test_any_proportional_bound,
6274 "bound",
6275 Inspect
6276 );
6277 current_test!(
6278 test_for_all_variants,
6279 test_current_policy_matches_proportional,
6280 test_any_policy_matches_proportional,
6281 "proportional",
6282 Inspect
6283 );
6284 current_test!(
6285 test_for_all_variants,
6286 test_current_policy_decisions_match_writes,
6287 test_any_policy_decisions_match_writes,
6288 "decisions",
6289 Inspect
6290 );
6291 current_test!(
6292 test_for_all_variants,
6293 test_current_policy_limits_after_colliding_writes,
6294 test_any_policy_limits_after_colliding_writes,
6295 "colliding",
6296 Inspect
6297 );
6298 current_test!(
6299 test_for_all_variants,
6300 test_current_policy_hold,
6301 test_any_policy_hold,
6302 "hold",
6303 Inspect
6304 );
6305 current_test!(
6306 test_for_all_variants,
6307 test_current_policy_limits_with_writes,
6308 test_any_policy_limits_with_writes,
6309 "writes",
6310 Inspect
6311 );
6312 current_test!(
6313 test_for_all_variants,
6314 test_current_proportional_one_batch,
6315 test_any_proportional_one_batch,
6316 "one-batch",
6317 Inspect
6318 );
6319 current_test!(
6320 test_for_all_variants,
6321 test_current_policy_own_writes,
6322 test_any_policy_own_writes,
6323 "own",
6324 Inspect,
6325 reopen
6326 );
6327 current_test!(
6328 test_for_all_variants,
6329 test_current_policy_evicts_parent_created_key,
6330 test_any_policy_evicts_parent_created_key,
6331 "parent-created",
6332 Changes,
6333 reopen
6334 );
6335 current_test!(
6336 test_for_all_variants,
6337 test_current_policy_ancestor_twins,
6338 test_any_policy_ancestor_twins,
6339 "twins",
6340 Inspect
6341 );
6342 current_test!(
6343 test_for_all_variants,
6344 test_current_policy_keep_evict_and_recover,
6345 test_any_policy_keep_evict_and_recover,
6346 "recover",
6347 Inspect,
6348 reopen
6349 );
6350
6351 current_test!(
6354 test_for_ordered_variants,
6355 test_current_ordered_policy_eviction_matrix,
6356 test_any_ordered_policy_eviction_matrix,
6357 "matrix",
6358 Neighbors,
6359 reopen
6360 );
6361 current_test!(
6362 test_for_ordered_variants,
6363 test_current_ordered_policy_repair_across_ancestors,
6364 test_any_ordered_policy_repair_across_ancestors,
6365 "repair",
6366 Neighbors,
6367 reopen
6368 );
6369}