1use crate::{
67 index::Factory as IndexFactory,
68 journal::{
69 authenticated::Inner,
70 contiguous::{fixed::Config as FConfig, variable::Config as VConfig},
71 },
72 merkle::{full::Config as MerkleConfig, Family, Location},
73 qmdb::{
74 any::operation::{Operation, Update},
75 bitmap::Shared,
76 metrics::Metrics,
77 operation::Committable,
78 single_operation_root, ROOT_BAGGING,
79 },
80 translator::Translator,
81 Context,
82};
83use commonware_codec::{CodecShared, Encode};
84use commonware_cryptography::Hasher;
85use commonware_macros::boxed;
86use commonware_parallel::Strategy;
87use core::num::NonZeroUsize;
88use std::sync::Arc;
89use tracing::warn;
90
91pub mod batch;
92pub mod db;
93pub mod operation;
94#[cfg(any(test, feature = "test-traits"))]
95pub mod traits;
96pub mod value;
97pub use value::{FixedValue, ValueEncoding, VariableValue};
98pub mod ordered;
99pub(crate) mod sync;
100pub mod unordered;
101
102pub fn initial_root<F, U, H>() -> H::Digest
107where
108 F: Family,
109 H: Hasher,
110 U: Update,
111 Operation<F, U>: Encode,
112{
113 single_operation_root::<F, H>(&Operation::<F, U>::CommitFloor(None, Location::new(0)))
114}
115
116pub(crate) const BITMAP_CHUNK_BYTES: usize = 64;
117
118#[derive(Clone)]
120pub struct Config<T: Translator, J, S: Strategy> {
121 pub merkle_config: MerkleConfig<S>,
123
124 pub journal_config: J,
126
127 pub translator: T,
129
130 pub init_cache_size: Option<NonZeroUsize>,
133}
134
135pub type FixedConfig<T, S> = Config<T, FConfig, S>;
137
138pub type VariableConfig<T, C, S> = Config<T, VConfig<C>, S>;
140
141pub async fn init<F, E, U, H, T, I, J, S>(
143 context: E,
144 cfg: Config<T, J::Config, S>,
145) -> Result<db::Db<F, E, J, I, H, U, BITMAP_CHUNK_BYTES, S>, crate::qmdb::Error<F>>
146where
147 F: Family,
148 E: Context,
149 U: Update + Send + Sync,
150 H: Hasher,
151 T: Translator,
152 I: IndexFactory<T, Value = Location<F>>,
153 J: Inner<E, Item = Operation<F, U>>,
154 S: Strategy,
155 Operation<F, U>: Committable + CodecShared,
156{
157 init_with_bitmap::<F, E, U, H, T, I, J, S, BITMAP_CHUNK_BYTES>(context, cfg, None).await
158}
159
160#[boxed]
163pub(crate) async fn init_with_bitmap<F, E, U, H, T, I, J, S, const N: usize>(
164 context: E,
165 cfg: Config<T, J::Config, S>,
166 bitmap: Option<Arc<Shared<N>>>,
167) -> Result<db::Db<F, E, J, I, H, U, N, S>, crate::qmdb::Error<F>>
168where
169 F: Family,
170 E: Context,
171 U: Update + Send + Sync,
172 H: Hasher,
173 T: Translator,
174 I: IndexFactory<T, Value = Location<F>>,
175 J: Inner<E, Item = Operation<F, U>>,
176 S: Strategy,
177 Operation<F, U>: Committable + CodecShared,
178{
179 let mut log = J::init::<F, H, S>(
180 context.child("log"),
181 cfg.merkle_config,
182 cfg.journal_config,
183 Operation::is_commit,
184 ROOT_BAGGING,
185 )
186 .await?;
187
188 if log.size() == 0 {
189 warn!("Authenticated log is empty, initializing new db");
190 let commit_floor = Operation::CommitFloor(None, Location::new(0));
191 log.append(&commit_floor).await?;
192 log.sync().await?;
193 }
194
195 let index = I::new(context.child("index"), cfg.translator);
196 let metrics = Metrics::new(context);
197 db::Db::init_from_log(index, log, bitmap, cfg.init_cache_size, metrics).await
198}
199
200#[cfg(test)]
201pub(crate) mod test {
203 use super::*;
204 use crate::{
205 journal::contiguous::{fixed::Config as FConfig, variable::Config as VConfig},
206 qmdb::any::{FixedConfig, MerkleConfig, VariableConfig},
207 translator::OneCap,
208 };
209 use commonware_codec::{Codec, CodecShared};
210 use commonware_cryptography::{sha256::Digest, Hasher, Sha256};
211 use commonware_runtime::{
212 buffer::paged::CacheRef, deterministic::Context, BufferPooler, Supervisor as _,
213 };
214 use commonware_utils::{NZUsize, NZU16, NZU64};
215 use core::{future::Future, pin::Pin};
216 use std::{
217 collections::HashMap,
218 num::{NonZeroU16, NonZeroUsize},
219 };
220
221 pub(crate) fn colliding_digest(prefix: u8, suffix: u64) -> Digest {
222 let mut bytes = [0u8; 32];
223 bytes[0] = prefix;
224 bytes[24..].copy_from_slice(&suffix.to_be_bytes());
225 Digest::from(bytes)
226 }
227
228 const PAGE_SIZE: NonZeroU16 = NZU16!(101);
230 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(11);
231
232 pub(crate) fn fixed_db_config<T: Translator + Default>(
233 suffix: &str,
234 pooler: &impl BufferPooler,
235 ) -> FixedConfig<T, Sequential> {
236 fixed_db_config_with_strategy(suffix, pooler, Sequential)
237 }
238
239 pub(crate) fn fixed_db_config_with_strategy<
240 T: Translator + Default,
241 S: commonware_parallel::Strategy,
242 >(
243 suffix: &str,
244 pooler: &impl BufferPooler,
245 strategy: S,
246 ) -> FixedConfig<T, S> {
247 let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE);
248 FixedConfig {
249 merkle_config: MerkleConfig {
250 journal_partition: format!("journal-{suffix}"),
251 metadata_partition: format!("metadata-{suffix}"),
252 items_per_blob: NZU64!(11),
253 write_buffer: NZUsize!(1024),
254 strategy,
255 page_cache: page_cache.clone(),
256 },
257 journal_config: FConfig {
258 partition: format!("log-journal-{suffix}"),
259 items_per_blob: NZU64!(7),
260 page_cache,
261 write_buffer: NZUsize!(1024),
262 },
263 translator: T::default(),
264 init_cache_size: Some(NZUsize!(1024)),
265 }
266 }
267
268 pub(crate) fn variable_db_config<T: Translator + Default>(
269 suffix: &str,
270 pooler: &impl BufferPooler,
271 ) -> VariableConfig<T, ((), ()), Sequential> {
272 let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE);
273 VariableConfig {
274 merkle_config: MerkleConfig {
275 journal_partition: format!("journal-{suffix}"),
276 metadata_partition: format!("metadata-{suffix}"),
277 items_per_blob: NZU64!(11),
278 write_buffer: NZUsize!(1024),
279 strategy: Sequential,
280 page_cache: page_cache.clone(),
281 },
282 journal_config: VConfig {
283 partition: format!("log-journal-{suffix}"),
284 items_per_section: NZU64!(7),
285 compression: None,
286 codec_config: ((), ()),
287 page_cache,
288 write_buffer: NZUsize!(1024),
289 },
290 translator: T::default(),
291 init_cache_size: Some(NZUsize!(1024)),
292 }
293 }
294
295 use crate::{
296 index::Unordered as UnorderedIndex,
297 journal::contiguous::Mutable,
298 merkle::mmr,
299 qmdb::any::{
300 db::Db as AnyDb,
301 operation::{update::Update as UpdateTrait, Operation as AnyOperation},
302 traits::{DbAny, Provable, UnmerkleizedBatch as _},
303 },
304 };
305
306 type Error = crate::qmdb::Error<mmr::Family>;
307 type Location = mmr::Location;
308
309 pub(crate) trait RewindableDb {
310 fn rewind_to_size(
311 &mut self,
312 size: Location,
313 ) -> impl Future<Output = Result<(), Error>> + Send;
314 }
315
316 impl<E, U, C, I, H, const N: usize, S> RewindableDb for AnyDb<mmr::Family, E, C, I, H, U, N, S>
317 where
318 E: crate::Context,
319 U: UpdateTrait,
320 C: Mutable<Item = AnyOperation<mmr::Family, U>>,
321 I: UnorderedIndex<Value = Location>,
322 H: Hasher,
323 AnyOperation<mmr::Family, U>: Codec,
324 S: Strategy,
325 {
326 async fn rewind_to_size(&mut self, size: Location) -> Result<(), Error> {
327 self.rewind(size).await?;
328 Ok(())
329 }
330 }
331
332 pub(crate) async fn test_any_db_non_empty_recovery<F: Family, D, V: Clone + CodecShared>(
334 context: Context,
335 mut db: D,
336 reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
337 make_value: impl Fn(u64) -> V,
338 ) where
339 D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
340 {
341 const ELEMENTS: u64 = 1000;
342
343 {
345 let mut batch = db.new_batch();
346 for i in 0u64..ELEMENTS {
347 let k = Sha256::hash(&i.to_be_bytes());
348 let v = make_value(i * 1000);
349 batch = batch.write(k, Some(v));
350 }
351 let merkleized = batch.merkleize(&db, None).await.unwrap();
352 db.apply_batch(merkleized).await.unwrap();
353 }
354 db.commit().await.unwrap();
355 db.prune(db.sync_boundary()).await.unwrap();
356 let root = db.root();
357 let op_count = db.size();
358 let inactivity_floor_loc = db.inactivity_floor_loc();
359
360 let db = reopen_db(context.child("reopen").with_attribute("index", 1)).await;
361 assert_eq!(db.size(), op_count);
362 assert_eq!(db.inactivity_floor_loc(), inactivity_floor_loc);
363 assert_eq!(db.root(), root);
364
365 {
367 let mut batch = db.new_batch();
368 for i in 0u64..ELEMENTS {
369 let k = Sha256::hash(&i.to_be_bytes());
370 let v = make_value((i + 1) * 10000);
371 batch = batch.write(k, Some(v));
372 }
373 let _merkleized = batch.merkleize(&db, None).await.unwrap();
374 }
375 let db = reopen_db(context.child("reopen").with_attribute("index", 2)).await;
376 assert_eq!(db.size(), op_count);
377 assert_eq!(db.inactivity_floor_loc(), inactivity_floor_loc);
378 assert_eq!(db.root(), root);
379
380 {
382 let mut batch = db.new_batch();
383 for i in 0u64..ELEMENTS {
384 let k = Sha256::hash(&i.to_be_bytes());
385 let v = make_value((i + 1) * 10000);
386 batch = batch.write(k, Some(v));
387 }
388 let _merkleized = batch.merkleize(&db, None).await.unwrap();
389 }
390 let db = reopen_db(context.child("reopen").with_attribute("index", 3)).await;
391 assert_eq!(db.size(), op_count);
392 assert_eq!(db.root(), root);
393
394 for _ in 0..3 {
396 let mut batch = db.new_batch();
397 for i in 0u64..ELEMENTS {
398 let k = Sha256::hash(&i.to_be_bytes());
399 let v = make_value((i + 1) * 10000);
400 batch = batch.write(k, Some(v));
401 }
402 let _merkleized = batch.merkleize(&db, None).await.unwrap();
403 }
404 let mut db = reopen_db(context.child("reopen").with_attribute("index", 4)).await;
405 assert_eq!(db.size(), op_count);
406 assert_eq!(db.root(), root);
407
408 {
410 let mut batch = db.new_batch();
411 for i in 0u64..ELEMENTS {
412 let k = Sha256::hash(&i.to_be_bytes());
413 let v = make_value((i + 1) * 10000);
414 batch = batch.write(k, Some(v));
415 }
416 let merkleized = batch.merkleize(&db, None).await.unwrap();
417 db.apply_batch(merkleized).await.unwrap();
418 }
419 db.commit().await.unwrap();
420 let db = reopen_db(context.child("reopen").with_attribute("index", 5)).await;
421 assert!(db.size() > op_count);
422 assert_ne!(db.inactivity_floor_loc(), inactivity_floor_loc);
423 assert_ne!(db.root(), root);
424
425 db.destroy().await.unwrap();
426 }
427
428 pub(crate) async fn test_any_db_empty_recovery<F: Family, D, V: Clone + CodecShared>(
430 context: Context,
431 db: D,
432 reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
433 make_value: impl Fn(u64) -> V,
434 ) where
435 D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
436 {
437 let root = db.root();
438
439 let db = reopen_db(context.child("reopen").with_attribute("index", 1)).await;
440 assert_eq!(db.size(), 1);
441 assert_eq!(db.root(), root);
442
443 {
445 let mut batch = db.new_batch();
446 for i in 0u64..1000 {
447 let k = Sha256::hash(&i.to_be_bytes());
448 let v = make_value((i + 1) * 10000);
449 batch = batch.write(k, Some(v));
450 }
451 let _merkleized = batch.merkleize(&db, None).await.unwrap();
452 }
453 let db = reopen_db(context.child("reopen").with_attribute("index", 2)).await;
454 assert_eq!(db.size(), 1);
455 assert_eq!(db.root(), root);
456
457 {
459 let mut batch = db.new_batch();
460 for i in 0u64..1000 {
461 let k = Sha256::hash(&i.to_be_bytes());
462 let v = make_value((i + 1) * 10000);
463 batch = batch.write(k, Some(v));
464 }
465 let _merkleized = batch.merkleize(&db, None).await.unwrap();
466 }
467 drop(db);
468 let db = reopen_db(context.child("reopen").with_attribute("index", 3)).await;
469 assert_eq!(db.size(), 1);
470 assert_eq!(db.root(), root);
471
472 for _ in 0..3 {
474 let mut batch = db.new_batch();
475 for i in 0u64..1000 {
476 let k = Sha256::hash(&i.to_be_bytes());
477 let v = make_value((i + 1) * 10000);
478 batch = batch.write(k, Some(v));
479 }
480 let _merkleized = batch.merkleize(&db, None).await.unwrap();
481 }
482 drop(db);
483 let mut db = reopen_db(context.child("reopen").with_attribute("index", 4)).await;
484 assert_eq!(db.size(), 1);
485 assert_eq!(db.root(), root);
486
487 {
489 let mut batch = db.new_batch();
490 for i in 0u64..1000 {
491 let k = Sha256::hash(&i.to_be_bytes());
492 let v = make_value((i + 1) * 10000);
493 batch = batch.write(k, Some(v));
494 }
495 let merkleized = batch.merkleize(&db, None).await.unwrap();
496 db.apply_batch(merkleized).await.unwrap();
497 }
498 db.commit().await.unwrap();
499 drop(db);
500 let db = reopen_db(context.child("reopen").with_attribute("index", 5)).await;
501 assert!(db.size() > 1);
502 assert_ne!(db.root(), root);
503
504 db.destroy().await.unwrap();
505 }
506
507 pub(crate) async fn test_any_db_commit_after_sync_recovery<F: Family, D, V>(
509 context: Context,
510 mut db: D,
511 reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
512 make_value: impl Fn(u64) -> V,
513 ) where
514 D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
515 V: Clone + CodecShared + Eq + std::fmt::Debug,
516 {
517 let key0 = Sha256::hash(&0u64.to_be_bytes());
518 let key1 = Sha256::hash(&1u64.to_be_bytes());
519 let value0 = make_value(100);
520 let value1 = make_value(200);
521
522 let merkleized = db
524 .new_batch()
525 .write(key0, Some(value0.clone()))
526 .merkleize(&db, None)
527 .await
528 .unwrap();
529 db.apply_batch(merkleized).await.unwrap();
530 db.commit().await.unwrap();
531 db.sync().await.unwrap();
532
533 let merkleized = db
535 .new_batch()
536 .write(key1, Some(value1.clone()))
537 .merkleize(&db, None)
538 .await
539 .unwrap();
540 db.apply_batch(merkleized).await.unwrap();
541 db.commit().await.unwrap();
542 let committed_root = db.root();
543 let committed_size = db.size();
544 drop(db);
545
546 let db = reopen_db(context.child("reopen").with_attribute("index", 1)).await;
547 assert_eq!(db.root(), committed_root);
548 assert_eq!(db.size(), committed_size);
549 assert_eq!(db.get(&key0).await.unwrap(), Some(value0));
550 assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
551
552 db.destroy().await.unwrap();
553 }
554
555 pub(crate) async fn test_any_db_prune_after_unsynced_floor_recovery<
559 F: Family,
560 D,
561 V: Clone + CodecShared,
562 >(
563 context: Context,
564 mut db: D,
565 reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
566 make_value: impl Fn(u64) -> V,
567 ) where
568 D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
569 {
570 const ELEMENTS: u64 = 1000;
571
572 {
574 let mut batch = db.new_batch();
575 for i in 0u64..ELEMENTS {
576 let k = Sha256::hash(&i.to_be_bytes());
577 batch = batch.write(k, Some(make_value(i)));
578 }
579 let merkleized = batch.merkleize(&db, None).await.unwrap();
580 db.apply_batch(merkleized).await.unwrap();
581 }
582 db.commit().await.unwrap();
583 let durable_floor = db.inactivity_floor_loc();
584
585 {
588 let mut batch = db.new_batch();
589 for i in 0u64..ELEMENTS {
590 let k = Sha256::hash(&i.to_be_bytes());
591 batch = batch.write(k, Some(make_value(i + 1)));
592 }
593 let merkleized = batch.merkleize(&db, None).await.unwrap();
594 db.apply_batch(merkleized).await.unwrap();
595 }
596 let unsynced_floor = db.inactivity_floor_loc();
597 assert!(unsynced_floor > durable_floor);
598
599 db.prune(db.sync_boundary()).await.unwrap();
601 let root = db.root();
602 let op_count = db.size();
603 drop(db);
604
605 let db = reopen_db(context.child("reopen").with_attribute("index", 1)).await;
608 assert_eq!(db.size(), op_count);
609 assert_eq!(db.inactivity_floor_loc(), unsynced_floor);
610 assert_eq!(db.root(), root);
611
612 db.destroy().await.unwrap();
613 }
614
615 pub(crate) async fn test_any_db_rewind_recovery<D, V>(
617 context: Context,
618 mut db: D,
619 reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
620 make_value: impl Fn(u64) -> V,
621 ) where
622 D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + RewindableDb,
623 V: Clone + CodecShared + Eq + std::fmt::Debug,
624 {
625 let key0 = Sha256::hash(&0u64.to_be_bytes());
626 let key1 = Sha256::hash(&1u64.to_be_bytes());
627 let key2 = Sha256::hash(&2u64.to_be_bytes());
628 let initial_root = db.root();
629 let initial_size = db.size();
630 let initial_floor = db.inactivity_floor_loc();
631
632 let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
634 let empty_range = db.apply_batch(merkleized).await.unwrap();
635 db.commit().await.unwrap();
636 assert_eq!(empty_range.start, initial_size);
637 assert_eq!(db.size(), empty_range.end);
638 db.rewind_to_size(initial_size).await.unwrap();
639 assert_eq!(db.root(), initial_root);
640 assert_eq!(db.size(), initial_size);
641 assert_eq!(db.inactivity_floor_loc(), initial_floor);
642 assert_eq!(db.get_metadata().await.unwrap(), None);
643
644 let value0_a = make_value(10);
645 let value1_a = make_value(11);
646 let metadata_a = make_value(12);
647
648 let merkleized = db
649 .new_batch()
650 .write(key0, Some(value0_a.clone()))
651 .write(key1, Some(value1_a.clone()))
652 .merkleize(&db, Some(metadata_a.clone()))
653 .await
654 .unwrap();
655 let range_a = db.apply_batch(merkleized).await.unwrap();
656 db.commit().await.unwrap();
657
658 let root_a = db.root();
659 let size_a = db.size();
660 let floor_a = db.inactivity_floor_loc();
661 assert_eq!(size_a, range_a.end);
662
663 let value0_b = make_value(20);
664 let value2_b = make_value(21);
665 let metadata_b = make_value(22);
666
667 let merkleized = db
668 .new_batch()
669 .write(key0, Some(value0_b))
670 .write(key1, None)
671 .write(key2, Some(value2_b))
672 .merkleize(&db, Some(metadata_b))
673 .await
674 .unwrap();
675 let range_b = db.apply_batch(merkleized).await.unwrap();
676 db.commit().await.unwrap();
677 assert_eq!(range_b.start, size_a);
678 assert_ne!(db.root(), root_a);
679
680 let value0_c = make_value(30);
681 let value1_c = make_value(31);
682 let metadata_c = make_value(32);
683 let merkleized = db
684 .new_batch()
685 .write(key0, Some(value0_c))
686 .write(key1, Some(value1_c))
687 .write(key2, None)
688 .merkleize(&db, Some(metadata_c))
689 .await
690 .unwrap();
691 db.apply_batch(merkleized).await.unwrap();
692 db.commit().await.unwrap();
693
694 db.rewind_to_size(size_a).await.unwrap();
698 assert_eq!(db.root(), root_a);
699 assert_eq!(db.size(), size_a);
700 assert_eq!(db.inactivity_floor_loc(), floor_a);
701 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a.clone()));
702 assert_eq!(db.get(&key0).await.unwrap(), Some(value0_a));
703 assert_eq!(db.get(&key1).await.unwrap(), Some(value1_a));
704 assert_eq!(db.get(&key2).await.unwrap(), None);
705
706 db.commit().await.unwrap();
707 drop(db);
708 let mut db = reopen_db(context.child("reopen_after_rewind")).await;
709 assert_eq!(db.root(), root_a);
710 assert_eq!(db.size(), size_a);
711 assert_eq!(db.inactivity_floor_loc(), floor_a);
712 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a));
713 assert_eq!(db.get(&key0).await.unwrap(), Some(make_value(10)));
714 assert_eq!(db.get(&key1).await.unwrap(), Some(make_value(11)));
715 assert_eq!(db.get(&key2).await.unwrap(), None);
716
717 let value2_d = make_value(40);
720 let metadata_d = make_value(41);
721 let merkleized = db
722 .new_batch()
723 .write(key2, Some(value2_d.clone()))
724 .merkleize(&db, Some(metadata_d.clone()))
725 .await
726 .unwrap();
727 db.apply_batch(merkleized).await.unwrap();
728 db.commit().await.unwrap();
729 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_d.clone()));
730 assert_eq!(db.get(&key0).await.unwrap(), Some(make_value(10)));
731 assert_eq!(db.get(&key1).await.unwrap(), Some(make_value(11)));
732 assert_eq!(db.get(&key2).await.unwrap(), Some(value2_d.clone()));
733
734 drop(db);
735 let mut db = reopen_db(context.child("reopen_after_rewind_new_writes")).await;
736 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_d));
737 assert_eq!(db.get(&key0).await.unwrap(), Some(make_value(10)));
738 assert_eq!(db.get(&key1).await.unwrap(), Some(make_value(11)));
739 assert_eq!(db.get(&key2).await.unwrap(), Some(value2_d));
740
741 db.rewind_to_size(initial_size).await.unwrap();
743 assert_eq!(db.root(), initial_root);
744 assert_eq!(db.size(), initial_size);
745 assert_eq!(db.inactivity_floor_loc(), initial_floor);
746 assert_eq!(db.get_metadata().await.unwrap(), None);
747 assert_eq!(db.get(&key0).await.unwrap(), None);
748 assert_eq!(db.get(&key1).await.unwrap(), None);
749 assert_eq!(db.get(&key2).await.unwrap(), None);
750
751 db.commit().await.unwrap();
752 drop(db);
753 let db = reopen_db(context.child("reopen_initial_boundary")).await;
754 assert_eq!(db.root(), initial_root);
755 assert_eq!(db.size(), initial_size);
756 assert_eq!(db.inactivity_floor_loc(), initial_floor);
757 assert_eq!(db.get_metadata().await.unwrap(), None);
758 assert_eq!(db.get(&key0).await.unwrap(), None);
759 assert_eq!(db.get(&key1).await.unwrap(), None);
760 assert_eq!(db.get(&key2).await.unwrap(), None);
761
762 db.destroy().await.unwrap();
763 }
764
765 #[boxed]
767 pub(crate) async fn test_any_db_build_and_authenticate<D, V>(
768 context: Context,
769 mut db: D,
770 reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
771 make_value: impl Fn(u64) -> V,
772 ) where
773 D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + Provable<mmr::Family>,
774 V: CodecShared + Clone + Eq + std::hash::Hash + std::fmt::Debug,
775 <D as Provable<mmr::Family>>::Operation: Codec,
776 {
777 use crate::qmdb::verify_proof;
778
779 const ELEMENTS: u64 = 1000;
780
781 let mut map = HashMap::<Digest, V>::default();
782 {
783 let mut batch = db.new_batch();
784 for i in 0u64..ELEMENTS {
785 let k = Sha256::hash(&i.to_be_bytes());
786 let v = make_value(i * 1000);
787 batch = batch.write(k, Some(v.clone()));
788 map.insert(k, v);
789 }
790
791 for i in 0u64..ELEMENTS {
793 if i % 3 != 0 {
794 continue;
795 }
796 let k = Sha256::hash(&i.to_be_bytes());
797 let v = make_value((i + 1) * 10000);
798 batch = batch.write(k, Some(v.clone()));
799 map.insert(k, v);
800 }
801
802 for i in 0u64..ELEMENTS {
804 if i % 7 != 1 {
805 continue;
806 }
807 let k = Sha256::hash(&i.to_be_bytes());
808 batch = batch.write(k, None);
809 map.remove(&k);
810 }
811
812 let merkleized = batch.merkleize(&db, None).await.unwrap();
813 db.apply_batch(merkleized).await.unwrap();
814 }
815 db.sync().await.unwrap();
817 db.prune(db.sync_boundary()).await.unwrap();
818
819 let root = db.root();
821 db.sync().await.unwrap();
822 drop(db);
823 let db = reopen_db(context.child("reopened")).await;
824 assert_eq!(root, db.root());
825
826 for i in 0u64..ELEMENTS {
828 let k = Sha256::hash(&i.to_be_bytes());
829 if let Some(map_value) = map.get(&k) {
830 let Some(db_value) = db.get(&k).await.unwrap() else {
831 panic!("key not found in db: {k}");
832 };
833 assert_eq!(*map_value, db_value);
834 } else {
835 assert!(db.get(&k).await.unwrap().is_none());
836 }
837 }
838 let bounds = db.bounds();
839 let inactivity_floor = db.inactivity_floor_loc();
840 for loc in *inactivity_floor..*bounds.end {
841 let loc = Location::new(loc);
842 let (proof, ops) = db.proof(loc, NZU64!(10)).await.unwrap();
843 assert!(verify_proof::<Sha256, _, _>(&proof, loc, &ops, &root));
844 }
845
846 db.destroy().await.unwrap();
847 }
848
849 pub(crate) async fn test_any_db_log_replay<
851 F: Family,
852 D,
853 V: Clone + CodecShared + PartialEq + std::fmt::Debug,
854 >(
855 context: Context,
856 mut db: D,
857 reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
858 make_value: impl Fn(u64) -> V,
859 ) where
860 D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
861 {
862 const UPDATES: u64 = 100;
864 let k = Sha256::hash(&UPDATES.to_be_bytes());
865 let mut last_value = None;
866 {
867 let mut batch = db.new_batch();
868 for i in 0u64..UPDATES {
869 let v = make_value(i * 1000);
870 last_value = Some(v.clone());
871 batch = batch.write(k, Some(v));
872 }
873 let merkleized = batch.merkleize(&db, None).await.unwrap();
874 db.apply_batch(merkleized).await.unwrap();
875 }
876 db.commit().await.unwrap();
877 let root = db.root();
878
879 drop(db);
881 let db = reopen_db(context.child("reopened")).await;
882 assert_eq!(db.root(), root);
883 assert_eq!(db.get(&k).await.unwrap(), last_value);
884
885 db.destroy().await.unwrap();
886 }
887
888 pub(crate) async fn test_any_db_historical_proof_basic<D, V: Clone + CodecShared>(
890 _context: Context,
891 mut db: D,
892 make_value: impl Fn(u64) -> V,
893 ) where
894 D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + Provable<mmr::Family>,
895 <D as Provable<mmr::Family>>::Operation: Codec + PartialEq + std::fmt::Debug,
896 {
897 use crate::qmdb::verify_proof;
898 use commonware_utils::NZU64;
899
900 const OPS: u64 = 20;
902 {
903 let mut batch = db.new_batch();
904 for i in 0u64..OPS {
905 let k = Sha256::hash(&i.to_be_bytes());
906 let v = make_value(i * 1000);
907 batch = batch.write(k, Some(v));
908 }
909 let merkleized = batch.merkleize(&db, None).await.unwrap();
910 db.apply_batch(merkleized).await.unwrap();
911 }
912 let root_hash = db.root();
913 let original_op_count = db.size();
914
915 let max_ops = NZU64!(10);
917 let start_loc = Location::new(5);
918 let (historical_proof, historical_ops) = db
919 .historical_proof(original_op_count, start_loc, max_ops)
920 .await
921 .unwrap();
922 let (regular_proof, regular_ops) = db.proof(start_loc, max_ops).await.unwrap();
923
924 assert_eq!(historical_proof.leaves, regular_proof.leaves);
925 assert_eq!(historical_proof.digests, regular_proof.digests);
926 assert_eq!(historical_ops, regular_ops);
927 assert!(verify_proof::<Sha256, _, _>(
928 &historical_proof,
929 start_loc,
930 &historical_ops,
931 &root_hash,
932 ));
933
934 {
936 let mut batch = db.new_batch();
937 for i in OPS..(OPS + 5) {
938 let k = Sha256::hash(&(i + 1000).to_be_bytes()); let v = make_value(i * 1000);
940 batch = batch.write(k, Some(v));
941 }
942 let merkleized = batch.merkleize(&db, None).await.unwrap();
943 db.apply_batch(merkleized).await.unwrap();
944 }
945
946 let (historical_proof2, historical_ops2) = db
948 .historical_proof(original_op_count, start_loc, max_ops)
949 .await
950 .unwrap();
951 assert_eq!(historical_proof2.leaves, original_op_count);
952 assert_eq!(historical_proof2.digests, regular_proof.digests);
953 assert_eq!(historical_ops2, regular_ops);
954 assert!(verify_proof::<Sha256, _, _>(
955 &historical_proof2,
956 start_loc,
957 &historical_ops2,
958 &root_hash,
959 ));
960
961 db.destroy().await.unwrap();
962 }
963
964 pub(crate) async fn test_any_db_historical_proof_invalid<D, V: Clone + CodecShared>(
966 _context: Context,
967 mut db: D,
968 make_value: impl Fn(u64) -> V,
969 ) where
970 D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + Provable<mmr::Family>,
971 <D as Provable<mmr::Family>>::Operation: Codec + PartialEq + std::fmt::Debug + Clone,
972 {
973 use crate::qmdb::verify_proof;
974 use commonware_utils::NZU64;
975
976 let mut historical_op_count = Location::new(0);
980 for i in 0u64..2 {
981 let k = Sha256::hash(&i.to_be_bytes());
982 let v = make_value(i * 1000);
983 let merkleized = db
984 .new_batch()
985 .write(k, Some(v))
986 .merkleize(&db, None)
987 .await
988 .unwrap();
989 db.apply_batch(merkleized).await.unwrap();
990 if i == 0 {
991 historical_op_count = db.bounds().end;
992 }
993 }
994
995 let expected_ops_len = (*historical_op_count - 1) as usize;
996 let (proof, ops) = db
997 .historical_proof(historical_op_count, Location::new(1), NZU64!(10))
998 .await
999 .unwrap();
1000 assert_eq!(proof.leaves, historical_op_count);
1001 assert_eq!(ops.len(), expected_ops_len);
1002
1003 {
1005 let mut tampered_proof = proof.clone();
1006 tampered_proof.digests[0] = Sha256::hash(b"invalid");
1007 let root_hash = db.root();
1008 assert!(!verify_proof::<Sha256, _, _>(
1009 &tampered_proof,
1010 Location::new(1),
1011 &ops,
1012 &root_hash,
1013 ));
1014 }
1015
1016 {
1018 let mut tampered_proof = proof.clone();
1019 tampered_proof.digests.push(Sha256::hash(b"invalid"));
1020 let root_hash = db.root();
1021 assert!(!verify_proof::<Sha256, _, _>(
1022 &tampered_proof,
1023 Location::new(1),
1024 &ops,
1025 &root_hash,
1026 ));
1027 }
1028
1029 {
1031 let root_hash = db.root();
1032 let mut tampered_ops = ops.clone();
1033 if tampered_ops.len() >= 2 {
1035 tampered_ops.swap(0, 1);
1036 assert!(!verify_proof::<Sha256, _, _>(
1037 &proof,
1038 Location::new(1),
1039 &tampered_ops,
1040 &root_hash,
1041 ));
1042 }
1043 }
1044
1045 {
1047 let root_hash = db.root();
1048 let mut tampered_ops = ops.clone();
1049 tampered_ops.push(tampered_ops[0].clone());
1050 assert!(!verify_proof::<Sha256, _, _>(
1051 &proof,
1052 Location::new(1),
1053 &tampered_ops,
1054 &root_hash,
1055 ));
1056 }
1057
1058 {
1060 let root_hash = db.root();
1061 assert!(!verify_proof::<Sha256, _, _>(
1062 &proof,
1063 Location::new(2),
1064 &ops,
1065 &root_hash,
1066 ));
1067 }
1068
1069 {
1071 let invalid_root = Sha256::hash(b"invalid");
1072 assert!(!verify_proof::<Sha256, _, _>(
1073 &proof,
1074 Location::new(1),
1075 &ops,
1076 &invalid_root,
1077 ));
1078 }
1079
1080 {
1082 let mut tampered_proof = proof.clone();
1083 tampered_proof.leaves = Location::new(100);
1084 let root_hash = db.root();
1085 assert!(!verify_proof::<Sha256, _, _>(
1086 &tampered_proof,
1087 Location::new(1),
1088 &ops,
1089 &root_hash,
1090 ));
1091 }
1092
1093 db.destroy().await.unwrap();
1094 }
1095
1096 pub(crate) async fn test_any_db_historical_proof_edge_cases<D, V: Clone + CodecShared>(
1098 _context: Context,
1099 mut db: D,
1100 make_value: impl Fn(u64) -> V,
1101 ) where
1102 D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + Provable<mmr::Family>,
1103 <D as Provable<mmr::Family>>::Operation: Codec + PartialEq + std::fmt::Debug,
1104 {
1105 use commonware_utils::NZU64;
1106
1107 let initial_size = db.bounds().end;
1113 let mut boundaries = vec![initial_size];
1114 for i in 0u64..5 {
1115 let k = Sha256::hash(&i.to_be_bytes());
1116 let v = make_value(i * 1000);
1117 let merkleized = db
1118 .new_batch()
1119 .write(k, Some(v))
1120 .merkleize(&db, None)
1121 .await
1122 .unwrap();
1123 db.apply_batch(merkleized).await.unwrap();
1124 boundaries.push(db.bounds().end);
1125 }
1126
1127 let singleton_size = boundaries[0];
1129 let (single_proof, single_ops) = db
1130 .historical_proof(singleton_size, Location::new(0), NZU64!(1))
1131 .await
1132 .unwrap();
1133 assert_eq!(single_proof.leaves, singleton_size);
1134 assert_eq!(single_ops.len(), 1);
1135
1136 let limited_size = boundaries[2];
1140 let limited_start = boundaries[1];
1141 let expected_limited = (*limited_size - *limited_start) as usize;
1142 assert!(expected_limited > 0);
1143 let (_limited_proof, limited_ops) = db
1144 .historical_proof(limited_size, limited_start, NZU64!(20))
1145 .await
1146 .unwrap();
1147 assert_eq!(limited_ops.len(), expected_limited);
1148
1149 let min_size = boundaries[2];
1152 let max_ops = NZU64!(3);
1153 let expected_min = core::cmp::min(max_ops.get(), *min_size - 1) as usize;
1154 let (min_proof, min_ops) = db
1155 .historical_proof(min_size, Location::new(1), max_ops)
1156 .await
1157 .unwrap();
1158 assert_eq!(min_proof.leaves, min_size);
1159 assert_eq!(min_ops.len(), expected_min);
1160
1161 db.destroy().await.unwrap();
1162 }
1163
1164 pub(crate) async fn test_any_db_multiple_commits_delete_replayed<F: Family, D, V>(
1166 context: Context,
1167 mut db: D,
1168 reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
1169 make_value: impl Fn(u64) -> V,
1170 ) where
1171 D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
1172 V: Clone + CodecShared + Eq + std::fmt::Debug,
1173 {
1174 let mut map = HashMap::<Digest, V>::default();
1175 const ELEMENTS: u64 = 10;
1176 let metadata_value = make_value(42);
1177 let key_at = |j: u64, i: u64| Sha256::hash(&(j * 1000 + i).to_be_bytes());
1178 for j in 0u64..ELEMENTS {
1179 let mut batch = db.new_batch();
1180 for i in 0u64..ELEMENTS {
1181 let k = key_at(j, i);
1182 let v = make_value(i * 1000);
1183 batch = batch.write(k, Some(v.clone()));
1184 map.insert(k, v);
1185 }
1186 let merkleized = batch
1187 .merkleize(&db, Some(metadata_value.clone()))
1188 .await
1189 .unwrap();
1190 db.apply_batch(merkleized).await.unwrap();
1191 db.commit().await.unwrap();
1192 }
1193 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_value));
1194 let k = key_at(ELEMENTS - 1, ELEMENTS - 1);
1195
1196 let merkleized = db
1197 .new_batch()
1198 .write(k, None)
1199 .merkleize(&db, None)
1200 .await
1201 .unwrap();
1202 db.apply_batch(merkleized).await.unwrap();
1203 db.commit().await.unwrap();
1204 assert_eq!(db.get_metadata().await.unwrap(), None);
1205 assert!(db.get(&k).await.unwrap().is_none());
1206
1207 let root = db.root();
1208 drop(db);
1209 let db = reopen_db(context.child("reopened")).await;
1210 assert_eq!(root, db.root());
1211 assert_eq!(db.get_metadata().await.unwrap(), None);
1212 assert!(db.get(&k).await.unwrap().is_none());
1213
1214 db.destroy().await.unwrap();
1215 }
1216
1217 use crate::qmdb::any::{
1218 ordered::{fixed::Db as OrderedFixedDb, variable::Db as OrderedVariableDb},
1219 unordered::{fixed::Db as UnorderedFixedDb, variable::Db as UnorderedVariableDb},
1220 };
1221 use commonware_macros::{test_group, test_traced};
1222 use commonware_parallel::{Sequential, Strategy};
1223 use commonware_runtime::{deterministic, Runner as _};
1224
1225 type UnorderedFixed =
1227 UnorderedFixedDb<mmr::Family, Context, Digest, Digest, Sha256, OneCap, Sequential>;
1228 type UnorderedVariable =
1229 UnorderedVariableDb<mmr::Family, Context, Digest, Digest, Sha256, OneCap, Sequential>;
1230 type OrderedFixed =
1231 OrderedFixedDb<mmr::Family, Context, Digest, Digest, Sha256, OneCap, Sequential>;
1232 type OrderedVariable =
1233 OrderedVariableDb<mmr::Family, Context, Digest, Digest, Sha256, OneCap, Sequential>;
1234 type UnorderedFixedP1 = unordered::fixed::partitioned::Db<
1235 mmr::Family,
1236 Context,
1237 Digest,
1238 Digest,
1239 Sha256,
1240 OneCap,
1241 1,
1242 Sequential,
1243 >;
1244 type UnorderedVariableP1 = unordered::variable::partitioned::Db<
1245 mmr::Family,
1246 Context,
1247 Digest,
1248 Digest,
1249 Sha256,
1250 OneCap,
1251 1,
1252 Sequential,
1253 >;
1254 type OrderedFixedP1 = ordered::fixed::partitioned::Db<
1255 mmr::Family,
1256 Context,
1257 Digest,
1258 Digest,
1259 Sha256,
1260 OneCap,
1261 1,
1262 Sequential,
1263 >;
1264 type OrderedVariableP1 = ordered::variable::partitioned::Db<
1265 mmr::Family,
1266 Context,
1267 Digest,
1268 Digest,
1269 Sha256,
1270 OneCap,
1271 1,
1272 Sequential,
1273 >;
1274 type UnorderedFixedP2 = unordered::fixed::partitioned::Db<
1275 mmr::Family,
1276 Context,
1277 Digest,
1278 Digest,
1279 Sha256,
1280 OneCap,
1281 2,
1282 Sequential,
1283 >;
1284 type UnorderedVariableP2 = unordered::variable::partitioned::Db<
1285 mmr::Family,
1286 Context,
1287 Digest,
1288 Digest,
1289 Sha256,
1290 OneCap,
1291 2,
1292 Sequential,
1293 >;
1294 type OrderedFixedP2 = ordered::fixed::partitioned::Db<
1295 mmr::Family,
1296 Context,
1297 Digest,
1298 Digest,
1299 Sha256,
1300 OneCap,
1301 2,
1302 Sequential,
1303 >;
1304 type OrderedVariableP2 = ordered::variable::partitioned::Db<
1305 mmr::Family,
1306 Context,
1307 Digest,
1308 Digest,
1309 Sha256,
1310 OneCap,
1311 2,
1312 Sequential,
1313 >;
1314
1315 mod mmb_types {
1317 use super::*;
1318 use crate::{
1319 index::{ordered::Index as OrderedIndex, unordered::Index as UnorderedIndex},
1320 journal::contiguous::{fixed::Journal as FJournal, variable::Journal as VJournal},
1321 merkle::{mmb, Location},
1322 qmdb::any::{
1323 operation::{update, Operation},
1324 value::{FixedEncoding, VariableEncoding},
1325 },
1326 };
1327
1328 type MmbLocation = Location<mmb::Family>;
1329
1330 pub type MmbUnorderedFixed = super::super::db::Db<
1331 mmb::Family,
1332 Context,
1333 FJournal<
1334 Context,
1335 Operation<mmb::Family, update::Unordered<Digest, FixedEncoding<Digest>>>,
1336 >,
1337 UnorderedIndex<OneCap, MmbLocation>,
1338 Sha256,
1339 update::Unordered<Digest, FixedEncoding<Digest>>,
1340 { crate::qmdb::any::BITMAP_CHUNK_BYTES },
1341 Sequential,
1342 >;
1343
1344 pub type MmbUnorderedVariable = super::super::db::Db<
1345 mmb::Family,
1346 Context,
1347 VJournal<
1348 Context,
1349 Operation<mmb::Family, update::Unordered<Digest, VariableEncoding<Digest>>>,
1350 >,
1351 UnorderedIndex<OneCap, MmbLocation>,
1352 Sha256,
1353 update::Unordered<Digest, VariableEncoding<Digest>>,
1354 { crate::qmdb::any::BITMAP_CHUNK_BYTES },
1355 Sequential,
1356 >;
1357
1358 pub type MmbOrderedFixed = super::super::db::Db<
1359 mmb::Family,
1360 Context,
1361 FJournal<
1362 Context,
1363 Operation<mmb::Family, update::Ordered<Digest, FixedEncoding<Digest>>>,
1364 >,
1365 OrderedIndex<OneCap, MmbLocation>,
1366 Sha256,
1367 update::Ordered<Digest, FixedEncoding<Digest>>,
1368 { crate::qmdb::any::BITMAP_CHUNK_BYTES },
1369 Sequential,
1370 >;
1371
1372 pub type MmbOrderedVariable = super::super::db::Db<
1373 mmb::Family,
1374 Context,
1375 VJournal<
1376 Context,
1377 Operation<mmb::Family, update::Ordered<Digest, VariableEncoding<Digest>>>,
1378 >,
1379 OrderedIndex<OneCap, MmbLocation>,
1380 Sha256,
1381 update::Ordered<Digest, VariableEncoding<Digest>>,
1382 { crate::qmdb::any::BITMAP_CHUNK_BYTES },
1383 Sequential,
1384 >;
1385 }
1386 use mmb_types::*;
1387
1388 #[inline]
1389 fn to_digest(i: u64) -> Digest {
1390 Sha256::hash(&i.to_be_bytes())
1391 }
1392
1393 macro_rules! with_mmr_variants {
1395 ($cb:ident!($($args:tt)*)) => {
1396 $cb!($($args)*, uf, UnorderedFixed, mmr::Family, fixed_db_config);
1397 $cb!($($args)*, uv, UnorderedVariable, mmr::Family, variable_db_config);
1398 $cb!($($args)*, of, OrderedFixed, mmr::Family, fixed_db_config);
1399 $cb!($($args)*, ov, OrderedVariable, mmr::Family, variable_db_config);
1400 $cb!($($args)*, ufp1, UnorderedFixedP1, mmr::Family, fixed_db_config);
1401 $cb!($($args)*, uvp1, UnorderedVariableP1, mmr::Family, variable_db_config);
1402 $cb!($($args)*, ofp1, OrderedFixedP1, mmr::Family, fixed_db_config);
1403 $cb!($($args)*, ovp1, OrderedVariableP1, mmr::Family, variable_db_config);
1404 $cb!($($args)*, ufp2, UnorderedFixedP2, mmr::Family, fixed_db_config);
1405 $cb!($($args)*, uvp2, UnorderedVariableP2, mmr::Family, variable_db_config);
1406 $cb!($($args)*, ofp2, OrderedFixedP2, mmr::Family, fixed_db_config);
1407 $cb!($($args)*, ovp2, OrderedVariableP2, mmr::Family, variable_db_config);
1408 };
1409 }
1410
1411 macro_rules! with_all_variants {
1413 ($cb:ident!($($args:tt)*)) => {
1414 $cb!($($args)*, uf, UnorderedFixed, mmr::Family, fixed_db_config);
1415 $cb!($($args)*, uv, UnorderedVariable, mmr::Family, variable_db_config);
1416 $cb!($($args)*, of, OrderedFixed, mmr::Family, fixed_db_config);
1417 $cb!($($args)*, ov, OrderedVariable, mmr::Family, variable_db_config);
1418 $cb!($($args)*, ufp1, UnorderedFixedP1, mmr::Family, fixed_db_config);
1419 $cb!($($args)*, uvp1, UnorderedVariableP1, mmr::Family, variable_db_config);
1420 $cb!($($args)*, ofp1, OrderedFixedP1, mmr::Family, fixed_db_config);
1421 $cb!($($args)*, ovp1, OrderedVariableP1, mmr::Family, variable_db_config);
1422 $cb!($($args)*, ufp2, UnorderedFixedP2, mmr::Family, fixed_db_config);
1423 $cb!($($args)*, uvp2, UnorderedVariableP2, mmr::Family, variable_db_config);
1424 $cb!($($args)*, ofp2, OrderedFixedP2, mmr::Family, fixed_db_config);
1425 $cb!($($args)*, ovp2, OrderedVariableP2, mmr::Family, variable_db_config);
1426 $cb!($($args)*, uf_mmb, MmbUnorderedFixed, mmb::Family, fixed_db_config);
1427 $cb!($($args)*, uv_mmb, MmbUnorderedVariable, mmb::Family, variable_db_config);
1428 $cb!($($args)*, of_mmb, MmbOrderedFixed, mmb::Family, fixed_db_config);
1429 $cb!($($args)*, ov_mmb, MmbOrderedVariable, mmb::Family, variable_db_config);
1430 };
1431 }
1432
1433 macro_rules! test_for_variant {
1437 (with_reopen: $f:ident, $traced:literal, $l:ident, $db:ty, $family:ty, $cfg:ident) => {
1438 paste::paste! {
1439 #[test_group("slow")]
1440 #[test_traced($traced)]
1441 fn [<$f _ $l>]() {
1442 let executor = deterministic::Runner::default();
1443 executor.start(|context| async move {
1444 let ctx = context.child(stringify!($l));
1445 let db = <$db>::init(ctx.child("storage"), $cfg::<OneCap>("db", &ctx))
1446 .await
1447 .unwrap();
1448 $f(
1449 ctx,
1450 db,
1451 |ctx| {
1452 Box::pin(async move {
1453 <$db>::init(ctx.child("storage"), $cfg::<OneCap>("db", &ctx))
1454 .await
1455 .unwrap()
1456 })
1457 },
1458 to_digest,
1459 )
1460 .await;
1461 });
1462 }
1463 }
1464 };
1465 (with_make_value: $f:ident, $traced:literal, $l:ident, $db:ty, $family:ty, $cfg:ident) => {
1466 paste::paste! {
1467 #[test_group("slow")]
1468 #[test_traced($traced)]
1469 fn [<$f _ $l>]() {
1470 let executor = deterministic::Runner::default();
1471 executor.start(|context| async move {
1472 let ctx = context.child(stringify!($l));
1473 let db = <$db>::init(ctx.child("storage"), $cfg::<OneCap>("db", &ctx))
1474 .await
1475 .unwrap();
1476 $f(ctx, db, to_digest).await;
1477 });
1478 }
1479 }
1480 };
1481 }
1482
1483 macro_rules! test_for_all_variants {
1485 (with_reopen: $f:ident, $traced:literal) => {
1486 with_all_variants!(test_for_variant!(with_reopen: $f, $traced));
1487 };
1488 (with_make_value: $f:ident, $traced:literal) => {
1489 with_all_variants!(test_for_variant!(with_make_value: $f, $traced));
1490 };
1491 }
1492
1493 macro_rules! test_for_mmr_variants {
1497 (with_reopen: $f:ident, $traced:literal) => {
1498 with_mmr_variants!(test_for_variant!(with_reopen: $f, $traced));
1499 };
1500 (with_make_value: $f:ident, $traced:literal) => {
1501 with_mmr_variants!(test_for_variant!(with_make_value: $f, $traced));
1502 };
1503 }
1504
1505 test_for_all_variants!(with_reopen: test_any_db_log_replay, "WARN");
1506 test_for_mmr_variants!(with_reopen: test_any_db_build_and_authenticate, "WARN");
1507 test_for_mmr_variants!(with_make_value: test_any_db_historical_proof_basic, "WARN");
1508 test_for_mmr_variants!(with_make_value: test_any_db_historical_proof_invalid, "WARN");
1509 test_for_mmr_variants!(with_make_value: test_any_db_historical_proof_edge_cases, "WARN");
1510 test_for_all_variants!(with_reopen: test_any_db_multiple_commits_delete_replayed, "WARN");
1511 test_for_all_variants!(with_reopen: test_any_db_non_empty_recovery, "WARN");
1512 test_for_all_variants!(with_reopen: test_any_db_empty_recovery, "WARN");
1513 test_for_all_variants!(with_reopen: test_any_db_commit_after_sync_recovery, "WARN");
1514 test_for_all_variants!(with_reopen: test_any_db_prune_after_unsynced_floor_recovery, "WARN");
1515 test_for_mmr_variants!(with_reopen: test_any_db_rewind_recovery, "WARN");
1516
1517 fn key(i: u64) -> Digest {
1518 Sha256::hash(&i.to_be_bytes())
1519 }
1520
1521 fn val(i: u64) -> Digest {
1522 Sha256::hash(&(i + 10000).to_be_bytes())
1523 }
1524
1525 async fn commit_writes(
1527 db: &mut UnorderedVariable,
1528 writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
1529 metadata: Option<Digest>,
1530 ) -> std::ops::Range<crate::mmr::Location> {
1531 let mut batch = db.new_batch();
1532 for (k, v) in writes {
1533 batch = batch.write(k, v);
1534 }
1535 let merkleized = batch.merkleize(&*db, metadata).await.unwrap();
1536 let range = db.apply_batch(merkleized).await.unwrap();
1537 db.commit().await.unwrap();
1538 range
1539 }
1540
1541 #[test_traced("INFO")]
1543 fn test_any_batch_empty() {
1544 let executor = deterministic::Runner::default();
1545 executor.start(|context| async move {
1546 let ctx = context.child("db");
1547 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1548 ctx.child("storage"),
1549 variable_db_config::<OneCap>("e", &ctx),
1550 )
1551 .await
1552 .unwrap();
1553
1554 let root_before = db.root();
1555 let batch = db.new_batch();
1556 let merkleized = batch.merkleize(&db, None).await.unwrap();
1557 db.apply_batch(merkleized).await.unwrap();
1558
1559 assert_ne!(db.root(), root_before);
1561
1562 commit_writes(&mut db, [(key(0), Some(val(0)))], None).await;
1564 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
1565
1566 db.destroy().await.unwrap();
1567 });
1568 }
1569
1570 #[test_traced("INFO")]
1572 fn test_any_batch_metadata() {
1573 let executor = deterministic::Runner::default();
1574 executor.start(|context| async move {
1575 let ctx = context.child("db");
1576 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1577 ctx.child("storage"),
1578 variable_db_config::<OneCap>("m", &ctx),
1579 )
1580 .await
1581 .unwrap();
1582
1583 let metadata = val(42);
1584
1585 commit_writes(&mut db, [(key(0), Some(val(0)))], Some(metadata)).await;
1587 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
1588
1589 let batch = db.new_batch();
1591 let merkleized = batch.merkleize(&db, None).await.unwrap();
1592 db.apply_batch(merkleized).await.unwrap();
1593 assert_eq!(db.get_metadata().await.unwrap(), None);
1594
1595 db.destroy().await.unwrap();
1596 });
1597 }
1598
1599 #[test_traced("INFO")]
1602 fn test_any_batch_get_read_through() {
1603 let executor = deterministic::Runner::default();
1604 executor.start(|context| async move {
1605 let ctx = context.child("db");
1606 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1607 ctx.child("storage"),
1608 variable_db_config::<OneCap>("g", &ctx),
1609 )
1610 .await
1611 .unwrap();
1612
1613 let ka = key(0);
1615 let va = val(0);
1616 commit_writes(&mut db, [(ka, Some(va))], None).await;
1617
1618 let kb = key(1);
1619 let vb = val(1);
1620 let kc = key(2);
1621
1622 let mut batch = db.new_batch();
1623
1624 assert_eq!(batch.get(&ka, &db).await.unwrap(), Some(va));
1626
1627 batch = batch.write(kb, Some(vb));
1629 assert_eq!(batch.get(&kb, &db).await.unwrap(), Some(vb));
1630
1631 assert_eq!(batch.get(&kc, &db).await.unwrap(), None);
1633
1634 let va2 = val(100);
1636 batch = batch.write(ka, Some(va2));
1637 assert_eq!(batch.get(&ka, &db).await.unwrap(), Some(va2));
1638
1639 batch = batch.write(ka, None);
1641 assert_eq!(batch.get(&ka, &db).await.unwrap(), None);
1642
1643 db.destroy().await.unwrap();
1644 });
1645 }
1646
1647 #[test_traced("INFO")]
1649 fn test_any_batch_get_on_merkleized() {
1650 let executor = deterministic::Runner::default();
1651 executor.start(|context| async move {
1652 let ctx = context.child("db");
1653 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1654 ctx.child("storage"),
1655 variable_db_config::<OneCap>("mg", &ctx),
1656 )
1657 .await
1658 .unwrap();
1659
1660 let ka = key(0);
1661 let kb = key(1);
1662 let kc = key(2);
1663 let kd = key(3);
1664
1665 commit_writes(&mut db, [(ka, Some(val(0))), (kb, Some(val(1)))], None).await;
1667
1668 let va2 = val(100);
1670 let vc = val(2);
1671 let mut batch = db.new_batch();
1672 batch = batch.write(ka, Some(va2));
1673 batch = batch.write(kb, None);
1674 batch = batch.write(kc, Some(vc));
1675 let merkleized = batch.merkleize(&db, None).await.unwrap();
1676
1677 assert_eq!(merkleized.get(&ka, &db).await.unwrap(), Some(va2));
1678 assert_eq!(merkleized.get(&kb, &db).await.unwrap(), None);
1679 assert_eq!(merkleized.get(&kc, &db).await.unwrap(), Some(vc));
1680 assert_eq!(merkleized.get(&kd, &db).await.unwrap(), None);
1681
1682 db.destroy().await.unwrap();
1683 });
1684 }
1685
1686 #[test_traced("INFO")]
1688 fn test_any_batch_stacked_get() {
1689 let executor = deterministic::Runner::default();
1690 executor.start(|context| async move {
1691 let ctx = context.child("db");
1692 let db: UnorderedVariable = UnorderedVariableDb::init(
1693 ctx.child("storage"),
1694 variable_db_config::<OneCap>("sg", &ctx),
1695 )
1696 .await
1697 .unwrap();
1698
1699 let ka = key(0);
1700 let kb = key(1);
1701
1702 let mut batch = db.new_batch();
1704 batch = batch.write(ka, Some(val(0)));
1705 let merkleized = batch.merkleize(&db, None).await.unwrap();
1706
1707 let mut child = merkleized.new_batch::<Sha256>();
1709 assert_eq!(child.get(&ka, &db).await.unwrap(), Some(val(0)));
1710
1711 child = child.write(ka, Some(val(100)));
1713 assert_eq!(child.get(&ka, &db).await.unwrap(), Some(val(100)));
1714
1715 child = child.write(kb, Some(val(1)));
1717 assert_eq!(child.get(&kb, &db).await.unwrap(), Some(val(1)));
1718
1719 child = child.write(ka, None);
1721 assert_eq!(child.get(&ka, &db).await.unwrap(), None);
1722
1723 db.destroy().await.unwrap();
1724 });
1725 }
1726
1727 #[test_traced("INFO")]
1729 fn test_any_batch_stacked_delete_recreate() {
1730 let executor = deterministic::Runner::default();
1731 executor.start(|context| async move {
1732 let ctx = context.child("db");
1733 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1734 ctx.child("storage"),
1735 variable_db_config::<OneCap>("dr", &ctx),
1736 )
1737 .await
1738 .unwrap();
1739
1740 let ka = key(0);
1741
1742 commit_writes(&mut db, [(ka, Some(val(0)))], None).await;
1744
1745 let mut parent = db.new_batch();
1747 parent = parent.write(ka, None);
1748 let parent_m = parent.merkleize(&db, None).await.unwrap();
1749 assert_eq!(parent_m.get(&ka, &db).await.unwrap(), None);
1750
1751 let mut child = parent_m.new_batch::<Sha256>();
1753 child = child.write(ka, Some(val(200)));
1754 let child_m = child.merkleize(&db, None).await.unwrap();
1755 assert_eq!(child_m.get(&ka, &db).await.unwrap(), Some(val(200)));
1756
1757 db.apply_batch(child_m).await.unwrap();
1759 assert_eq!(db.get(&ka).await.unwrap(), Some(val(200)));
1760
1761 db.destroy().await.unwrap();
1762 });
1763 }
1764
1765 #[test_traced("INFO")]
1768 fn test_any_batch_floor_raise() {
1769 let executor = deterministic::Runner::default();
1770 executor.start(|context| async move {
1771 let ctx = context.child("db");
1772 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1773 ctx.child("storage"),
1774 variable_db_config::<OneCap>("fr", &ctx),
1775 )
1776 .await
1777 .unwrap();
1778
1779 let init: Vec<_> = (0..100).map(|i| (key(i), Some(val(i)))).collect();
1781 commit_writes(&mut db, init, None).await;
1782
1783 let floor_before = db.inactivity_floor_loc();
1784
1785 let updates: Vec<_> = (0..30).map(|i| (key(i), Some(val(i + 500)))).collect();
1787 commit_writes(&mut db, updates, None).await;
1788
1789 assert!(db.inactivity_floor_loc() > floor_before);
1791
1792 for i in 0..30 {
1794 assert_eq!(
1795 db.get(&key(i)).await.unwrap(),
1796 Some(val(i + 500)),
1797 "updated key {i} mismatch"
1798 );
1799 }
1800 for i in 30..100 {
1801 assert_eq!(
1802 db.get(&key(i)).await.unwrap(),
1803 Some(val(i)),
1804 "untouched key {i} mismatch"
1805 );
1806 }
1807
1808 db.destroy().await.unwrap();
1809 });
1810 }
1811
1812 #[test_traced("INFO")]
1814 fn test_any_batch_apply_returns_range() {
1815 let executor = deterministic::Runner::default();
1816 executor.start(|context| async move {
1817 let ctx = context.child("db");
1818 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1819 ctx.child("storage"),
1820 variable_db_config::<OneCap>("ar", &ctx),
1821 )
1822 .await
1823 .unwrap();
1824
1825 let writes: Vec<_> = (0..5).map(|i| (key(i), Some(val(i)))).collect();
1827 let range1 = commit_writes(&mut db, writes, None).await;
1828
1829 assert_eq!(range1.start, crate::mmr::Location::new(1));
1831 assert!(range1.end.saturating_sub(*range1.start) >= 6);
1833
1834 let writes: Vec<_> = (5..10).map(|i| (key(i), Some(val(i)))).collect();
1836 let range2 = commit_writes(&mut db, writes, None).await;
1837 assert_eq!(range2.start, range1.end);
1838
1839 db.destroy().await.unwrap();
1840 });
1841 }
1842
1843 #[test_traced("INFO")]
1845 fn test_any_batch_deep_chain() {
1846 let executor = deterministic::Runner::default();
1847 executor.start(|context| async move {
1848 let ctx = context.child("db");
1849 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1850 ctx.child("storage"),
1851 variable_db_config::<OneCap>("dc", &ctx),
1852 )
1853 .await
1854 .unwrap();
1855
1856 let init: Vec<_> = (0..5).map(|i| (key(i), Some(val(i)))).collect();
1858 commit_writes(&mut db, init, None).await;
1859
1860 let mut parent = db.new_batch();
1862 parent = parent.write(key(0), Some(val(100)));
1863 parent = parent.write(key(5), Some(val(5)));
1864 let parent_m = parent.merkleize(&db, None).await.unwrap();
1865
1866 let mut child = parent_m.new_batch::<Sha256>();
1868 child = child.write(key(1), Some(val(101)));
1869 child = child.write(key(6), Some(val(6)));
1870 let child_m = child.merkleize(&db, None).await.unwrap();
1871
1872 let mut grandchild = child_m.new_batch::<Sha256>();
1874 grandchild = grandchild.write(key(2), None);
1875 grandchild = grandchild.write(key(7), Some(val(7)));
1876 let grandchild_m = grandchild.merkleize(&db, None).await.unwrap();
1877
1878 assert_eq!(
1880 grandchild_m.get(&key(0), &db).await.unwrap(),
1881 Some(val(100))
1882 );
1883 assert_eq!(
1884 grandchild_m.get(&key(1), &db).await.unwrap(),
1885 Some(val(101))
1886 );
1887 assert_eq!(grandchild_m.get(&key(2), &db).await.unwrap(), None);
1888 assert_eq!(grandchild_m.get(&key(7), &db).await.unwrap(), Some(val(7)));
1889
1890 db.apply_batch(grandchild_m).await.unwrap();
1892
1893 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(100)));
1894 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(101)));
1895 assert_eq!(db.get(&key(2)).await.unwrap(), None);
1896 assert_eq!(db.get(&key(3)).await.unwrap(), Some(val(3)));
1897 assert_eq!(db.get(&key(4)).await.unwrap(), Some(val(4)));
1898 assert_eq!(db.get(&key(5)).await.unwrap(), Some(val(5)));
1899 assert_eq!(db.get(&key(6)).await.unwrap(), Some(val(6)));
1900 assert_eq!(db.get(&key(7)).await.unwrap(), Some(val(7)));
1901
1902 db.destroy().await.unwrap();
1903 });
1904 }
1905
1906 #[test_traced("INFO")]
1908 fn test_any_batch_chain_matches_sequential() {
1909 let executor = deterministic::Runner::default();
1910 executor.start(|context| async move {
1911 let ctx = context.child("db");
1912
1913 let ctx_a = ctx.child("a");
1915 let mut db_a: UnorderedVariable = UnorderedVariableDb::init(
1916 ctx_a.child("db"),
1917 variable_db_config::<OneCap>("cms-a", &ctx_a),
1918 )
1919 .await
1920 .unwrap();
1921
1922 let ctx_b = ctx.child("b");
1924 let mut db_b: UnorderedVariable = UnorderedVariableDb::init(
1925 ctx_b.child("db"),
1926 variable_db_config::<OneCap>("cms-b", &ctx_b),
1927 )
1928 .await
1929 .unwrap();
1930
1931 let writes1: Vec<_> = (0..5).map(|i| (key(i), Some(val(i)))).collect();
1933
1934 let writes2 = vec![
1936 (key(0), Some(val(100))),
1937 (key(1), None),
1938 (key(5), Some(val(5))),
1939 ];
1940
1941 commit_writes(&mut db_a, writes1.clone(), None).await;
1943 commit_writes(&mut db_a, writes2.clone(), None).await;
1944
1945 let mut parent = db_b.new_batch();
1947 for (k, v) in &writes1 {
1948 parent = parent.write(*k, *v);
1949 }
1950 let parent_m = parent.merkleize(&db_b, None).await.unwrap();
1951
1952 let mut child = parent_m.new_batch::<Sha256>();
1953 for (k, v) in &writes2 {
1954 child = child.write(*k, *v);
1955 }
1956 let child_m = child.merkleize(&db_b, None).await.unwrap();
1957 db_b.apply_batch(child_m).await.unwrap();
1958
1959 assert_eq!(db_a.root(), db_b.root());
1961 for i in 0..6 {
1962 assert_eq!(
1963 db_a.get(&key(i)).await.unwrap(),
1964 db_b.get(&key(i)).await.unwrap(),
1965 "key {i} mismatch"
1966 );
1967 }
1968
1969 db_a.destroy().await.unwrap();
1970 db_b.destroy().await.unwrap();
1971 });
1972 }
1973
1974 #[test_traced("INFO")]
1976 fn test_any_batch_create_then_delete_same_batch() {
1977 let executor = deterministic::Runner::default();
1978 executor.start(|context| async move {
1979 let ctx = context.child("db");
1980 let mut db: UnorderedVariable = UnorderedVariableDb::init(
1981 ctx.child("storage"),
1982 variable_db_config::<OneCap>("cd", &ctx),
1983 )
1984 .await
1985 .unwrap();
1986
1987 commit_writes(&mut db, [(key(0), Some(val(0)))], None).await;
1989
1990 let mut batch = db.new_batch();
1992 batch = batch.write(key(1), Some(val(1))); batch = batch.write(key(1), None); batch = batch.write(key(2), Some(val(2))); batch = batch.write(key(0), None); let merkleized = batch.merkleize(&db, None).await.unwrap();
1997 db.apply_batch(merkleized).await.unwrap();
1998
1999 assert_eq!(db.get(&key(0)).await.unwrap(), None);
2000 assert_eq!(db.get(&key(1)).await.unwrap(), None);
2001 assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
2002
2003 db.destroy().await.unwrap();
2004 });
2005 }
2006
2007 #[test_traced("INFO")]
2009 fn test_any_batch_delete_all_keys() {
2010 let executor = deterministic::Runner::default();
2011 executor.start(|context| async move {
2012 let ctx = context.child("db");
2013 let mut db: UnorderedVariable = UnorderedVariableDb::init(
2014 ctx.child("storage"),
2015 variable_db_config::<OneCap>("da", &ctx),
2016 )
2017 .await
2018 .unwrap();
2019
2020 let init: Vec<_> = (0..5).map(|i| (key(i), Some(val(i)))).collect();
2022 commit_writes(&mut db, init, None).await;
2023
2024 let deletes: Vec<_> = (0..5).map(|i| (key(i), None)).collect();
2026 commit_writes(&mut db, deletes, None).await;
2027
2028 for i in 0..5 {
2029 assert_eq!(db.get(&key(i)).await.unwrap(), None, "key {i} not deleted");
2030 }
2031
2032 commit_writes(&mut db, [(key(10), Some(val(10)))], None).await;
2034 assert_eq!(db.get(&key(10)).await.unwrap(), Some(val(10)));
2035
2036 db.destroy().await.unwrap();
2037 });
2038 }
2039
2040 #[test_traced("INFO")]
2042 fn test_any_batch_parallel_forks() {
2043 let executor = deterministic::Runner::default();
2044 executor.start(|context| async move {
2045 let ctx = context.child("db");
2046 let mut db: UnorderedVariable = UnorderedVariableDb::init(
2047 ctx.child("storage"),
2048 variable_db_config::<OneCap>("pf", &ctx),
2049 )
2050 .await
2051 .unwrap();
2052
2053 commit_writes(&mut db, [(key(0), Some(val(0)))], None).await;
2055 let root_before = db.root();
2056
2057 let fork_a_m = db
2059 .new_batch()
2060 .write(key(0), Some(val(100)))
2061 .write(key(1), Some(val(1)))
2062 .merkleize(&db, None)
2063 .await
2064 .unwrap();
2065
2066 let fork_b_m = db
2068 .new_batch()
2069 .write(key(0), None)
2070 .write(key(2), Some(val(2)))
2071 .merkleize(&db, None)
2072 .await
2073 .unwrap();
2074
2075 assert_ne!(fork_a_m.root(), fork_b_m.root());
2077
2078 assert_eq!(db.root(), root_before);
2080 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2081 assert_eq!(db.get(&key(1)).await.unwrap(), None);
2082
2083 db.apply_batch(fork_a_m).await.unwrap();
2085 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(100)));
2086 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2087 assert_eq!(db.get(&key(2)).await.unwrap(), None);
2088
2089 db.destroy().await.unwrap();
2090 });
2091 }
2092
2093 #[test_traced("INFO")]
2095 fn test_any_batch_floor_raise_chained() {
2096 let executor = deterministic::Runner::default();
2097 executor.start(|context| async move {
2098 let ctx = context.child("db");
2099 let mut db: UnorderedVariable = UnorderedVariableDb::init(
2100 ctx.child("storage"),
2101 variable_db_config::<OneCap>("frc", &ctx),
2102 )
2103 .await
2104 .unwrap();
2105
2106 let init: Vec<_> = (0..50).map(|i| (key(i), Some(val(i)))).collect();
2108 commit_writes(&mut db, init, None).await;
2109 let floor_before = db.inactivity_floor_loc();
2110
2111 let mut parent = db.new_batch();
2113 for i in 0..20 {
2114 parent = parent.write(key(i), Some(val(i + 500)));
2115 }
2116 let parent_m = parent.merkleize(&db, None).await.unwrap();
2117
2118 let mut child = parent_m.new_batch::<Sha256>();
2120 for i in 20..30 {
2121 child = child.write(key(i), Some(val(i + 500)));
2122 }
2123 let child_m = child.merkleize(&db, None).await.unwrap();
2124 db.apply_batch(child_m).await.unwrap();
2125
2126 assert!(db.inactivity_floor_loc() > floor_before);
2128
2129 for i in 0..30 {
2131 assert_eq!(
2132 db.get(&key(i)).await.unwrap(),
2133 Some(val(i + 500)),
2134 "updated key {i} mismatch"
2135 );
2136 }
2137 for i in 30..50 {
2138 assert_eq!(
2139 db.get(&key(i)).await.unwrap(),
2140 Some(val(i)),
2141 "untouched key {i} mismatch"
2142 );
2143 }
2144
2145 db.destroy().await.unwrap();
2146 });
2147 }
2148
2149 #[test_traced("INFO")]
2151 fn test_any_batch_abandoned() {
2152 let executor = deterministic::Runner::default();
2153 executor.start(|context| async move {
2154 let ctx = context.child("db");
2155 let mut db: UnorderedVariable = UnorderedVariableDb::init(
2156 ctx.child("storage"),
2157 variable_db_config::<OneCap>("ab", &ctx),
2158 )
2159 .await
2160 .unwrap();
2161
2162 commit_writes(&mut db, [(key(0), Some(val(0)))], None).await;
2163 let root_before = db.root();
2164
2165 {
2167 let mut batch = db.new_batch();
2168 batch = batch.write(key(0), Some(val(999)));
2169 batch = batch.write(key(1), Some(val(1)));
2170 let _merkleized = batch.merkleize(&db, None).await.unwrap();
2171 }
2173
2174 assert_eq!(db.root(), root_before);
2176 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2177 assert_eq!(db.get(&key(1)).await.unwrap(), None);
2178
2179 db.destroy().await.unwrap();
2180 });
2181 }
2182
2183 #[test_traced("INFO")]
2185 fn test_any_batch_apply_requires_commit_for_recovery() {
2186 let executor = deterministic::Runner::default();
2187 executor.start(|context| async move {
2188 let partition = "apply_requires_commit";
2189 let ctx = context.child("db");
2190 let mut db: UnorderedVariable = UnorderedVariableDb::init(
2191 ctx.child("storage"),
2192 variable_db_config::<OneCap>(partition, &ctx),
2193 )
2194 .await
2195 .unwrap();
2196
2197 let committed_root = db.root();
2198
2199 let merkleized = db
2200 .new_batch()
2201 .write(key(0), Some(val(0)))
2202 .merkleize(&db, None)
2203 .await
2204 .unwrap();
2205 db.apply_batch(merkleized).await.unwrap();
2206
2207 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2208
2209 drop(db);
2210
2211 let reopened: UnorderedVariable = UnorderedVariableDb::init(
2212 context.child("reopen"),
2213 variable_db_config::<OneCap>(partition, &context),
2214 )
2215 .await
2216 .unwrap();
2217 assert_eq!(reopened.root(), committed_root);
2218 assert_eq!(reopened.get(&key(0)).await.unwrap(), None);
2219
2220 reopened.destroy().await.unwrap();
2221 });
2222 }
2223
2224 #[test_traced("INFO")]
2226 fn test_any_rewind_pruned_target_errors() {
2227 let executor = deterministic::Runner::default();
2228 executor.start(|context| async move {
2229 const KEYS: u64 = 64;
2230
2231 let ctx = context.child("db");
2232 let mut db: UnorderedVariable = UnorderedVariableDb::init(
2233 ctx.child("storage"),
2234 variable_db_config::<OneCap>("rp", &ctx),
2235 )
2236 .await
2237 .unwrap();
2238
2239 let initial: Vec<_> = (0..KEYS).map(|i| (key(i), Some(val(i)))).collect();
2240 let first_range = commit_writes(&mut db, initial, None).await;
2241
2242 let mut round = 0u64;
2243 loop {
2244 round += 1;
2245 assert!(
2246 round <= 64,
2247 "failed to prune enough history for rewind test"
2248 );
2249
2250 let updates: Vec<_> = (0..KEYS)
2251 .map(|i| (key(i), Some(val(1000 + round * KEYS + i))))
2252 .collect();
2253 commit_writes(&mut db, updates, None).await;
2254
2255 db.prune(db.sync_boundary()).await.unwrap();
2256 let bounds = db.bounds();
2257 if bounds.start > first_range.start {
2258 break;
2259 }
2260 }
2261
2262 let oldest_retained = db.bounds().start;
2263 let boundary_err = db.rewind(oldest_retained).await.unwrap_err();
2264 assert!(
2265 matches!(
2266 boundary_err,
2267 crate::qmdb::Error::Journal(crate::journal::Error::ItemPruned(_))
2268 ),
2269 "unexpected rewind error at retained boundary: {boundary_err:?}"
2270 );
2271
2272 let err = db.rewind(first_range.start).await.unwrap_err();
2273 assert!(
2274 matches!(
2275 err,
2276 crate::qmdb::Error::Journal(crate::journal::Error::ItemPruned(_))
2277 ),
2278 "unexpected rewind error: {err:?}"
2279 );
2280
2281 db.destroy().await.unwrap();
2282 });
2283 }
2284
2285 #[test_traced("INFO")]
2287 fn test_any_rewind_invalid_target_errors() {
2288 let executor = deterministic::Runner::default();
2289 executor.start(|context| async move {
2290 let ctx = context.child("db");
2291 let mut db: UnorderedVariable = UnorderedVariableDb::init(
2292 ctx.child("storage"),
2293 variable_db_config::<OneCap>("ri", &ctx),
2294 )
2295 .await
2296 .unwrap();
2297
2298 let root_before = db.root();
2299 let size_before = db.size();
2300 db.rewind(size_before).await.unwrap();
2301 assert_eq!(db.root(), root_before);
2302 assert_eq!(db.size(), size_before);
2303
2304 let zero_err = db.rewind(Location::new(0)).await.unwrap_err();
2305 assert!(
2306 matches!(
2307 zero_err,
2308 crate::qmdb::Error::Journal(crate::journal::Error::InvalidRewind(0))
2309 ),
2310 "unexpected rewind error: {zero_err:?}"
2311 );
2312 assert_eq!(db.root(), root_before);
2313 assert_eq!(db.size(), size_before);
2314
2315 let too_large_target = Location::new(*size_before + 1);
2316 let too_large_err = db.rewind(too_large_target).await.unwrap_err();
2317 assert!(
2318 matches!(
2319 too_large_err,
2320 crate::qmdb::Error::Journal(crate::journal::Error::InvalidRewind(size))
2321 if size == *too_large_target
2322 ),
2323 "unexpected rewind error: {too_large_err:?}"
2324 );
2325 assert_eq!(db.root(), root_before);
2326 assert_eq!(db.size(), size_before);
2327
2328 db.destroy().await.unwrap();
2329 });
2330 }
2331
2332 #[test_traced("INFO")]
2335 fn test_any_rewind_rejects_target_with_pruned_floor() {
2336 let executor = deterministic::Runner::default();
2337 executor.start(|context| async move {
2338 const KEYS: u64 = 64;
2339
2340 let ctx = context.child("db");
2341 let mut db: UnorderedVariable =
2342 UnorderedVariableDb::init(ctx.child("storage"), variable_db_config::<OneCap>("rf", &ctx))
2343 .await
2344 .unwrap();
2345
2346 commit_writes(&mut db, (0..KEYS).map(|i| (key(i), Some(val(i)))), None).await;
2347 commit_writes(
2348 &mut db,
2349 (0..KEYS).map(|i| (key(i), Some(val(1_000 + i)))),
2350 None,
2351 )
2352 .await;
2353
2354 let rewind_target = db.size();
2355 let target_floor = db.inactivity_floor_loc();
2356 let prune_loc = Location::new(*target_floor + (KEYS / 2));
2357 assert!(
2358 rewind_target > *prune_loc,
2359 "test setup expected target size > prune_loc; target={rewind_target:?}, floor={target_floor:?}"
2360 );
2361
2362 let mut round = 0u64;
2363 while db.inactivity_floor_loc() < prune_loc {
2364 round += 1;
2365 assert!(
2366 round <= 8,
2367 "failed to advance inactivity floor enough for floor-pruned rewind test"
2368 );
2369 commit_writes(
2370 &mut db,
2371 (0..KEYS).map(|i| (key(i), Some(val(10_000 + round * KEYS + i)))),
2372 None,
2373 )
2374 .await;
2375 }
2376
2377 db.prune(prune_loc).await.unwrap();
2378 let bounds = db.bounds();
2379 assert!(
2380 bounds.start > *target_floor,
2381 "test setup expected pruned start beyond target floor; bounds={bounds:?}, target_floor={target_floor:?}"
2382 );
2383 assert!(
2384 rewind_target > bounds.start,
2385 "test setup expected target commit retained; target={rewind_target:?}, bounds={bounds:?}"
2386 );
2387
2388 let err = db.rewind(rewind_target).await.unwrap_err();
2389 assert!(
2390 matches!(
2391 err,
2392 crate::qmdb::Error::Journal(crate::journal::Error::ItemPruned(_))
2393 ),
2394 "unexpected rewind error: {err:?}"
2395 );
2396
2397 db.destroy().await.unwrap();
2398 });
2399 }
2400
2401 #[test_traced("INFO")]
2407 fn test_any_prune_keeps_bitmap_aligned_with_journal() {
2408 let executor = deterministic::Runner::default();
2409 executor.start(|context| async move {
2410 const BITMAP_CHUNK_BITS: u64 =
2413 commonware_utils::bitmap::Prunable::<BITMAP_CHUNK_BYTES>::CHUNK_SIZE_BITS;
2414 const ITEMS_PER_SECTION: u64 = 2048;
2418 const { assert!(ITEMS_PER_SECTION > BITMAP_CHUNK_BITS) };
2419
2420 let ctx = context.child("db");
2421 let mut cfg = variable_db_config::<OneCap>("rg", &ctx);
2422 cfg.journal_config.items_per_section = NZU64!(ITEMS_PER_SECTION);
2423
2424 let mut db: UnorderedVariable =
2425 UnorderedVariableDb::init(ctx.child("storage"), cfg).await.unwrap();
2426
2427 commit_writes(&mut db, (0..100).map(|i| (key(i), Some(val(i)))), None).await;
2428 let rewind_target = db.size();
2429 assert!(
2432 *rewind_target < BITMAP_CHUNK_BITS,
2433 "rewind_target {rewind_target:?} must be < {BITMAP_CHUNK_BITS} for the bug to manifest"
2434 );
2435 let root_at_target = db.root();
2436
2437 commit_writes(
2438 &mut db,
2439 (0..700).map(|i| (key(i), Some(val(1_000 + i)))),
2440 None,
2441 )
2442 .await;
2443 commit_writes(
2444 &mut db,
2445 (0..700).map(|i| (key(i), Some(val(10_000 + i)))),
2446 None,
2447 )
2448 .await;
2449
2450 let pre_prune_size = db.size();
2453 assert!(pre_prune_size > rewind_target);
2454
2455 let prune_loc = Location::new(600);
2456 assert!(
2459 *prune_loc > BITMAP_CHUNK_BITS,
2460 "prune_loc {prune_loc:?} must exceed one bitmap chunk ({BITMAP_CHUNK_BITS} bits)"
2461 );
2462 assert!(
2465 *prune_loc < ITEMS_PER_SECTION,
2466 "prune_loc {prune_loc:?} must be < {ITEMS_PER_SECTION} so the journal retains section 0"
2467 );
2468 assert!(db.inactivity_floor_loc() >= prune_loc);
2469
2470 db.prune(prune_loc).await.unwrap();
2471
2472 let bounds = db.bounds();
2475 assert_eq!(bounds.start, Location::new(0));
2476 assert_eq!(
2477 db.bitmap.pruned_bits(),
2478 0,
2479 "bitmap pruned past journal retained start"
2480 );
2481
2482 db.rewind(rewind_target).await.unwrap();
2485 assert_eq!(db.size(), rewind_target);
2486 assert_eq!(db.root(), root_at_target);
2487
2488 db.destroy().await.unwrap();
2489 });
2490 }
2491
2492 type MmbVariable = super::db::Db<
2499 crate::merkle::mmb::Family,
2500 Context,
2501 crate::journal::contiguous::variable::Journal<
2502 Context,
2503 super::operation::Operation<
2504 crate::merkle::mmb::Family,
2505 super::operation::update::Unordered<Digest, super::value::VariableEncoding<Digest>>,
2506 >,
2507 >,
2508 crate::index::unordered::Index<OneCap, crate::merkle::Location<crate::merkle::mmb::Family>>,
2509 Sha256,
2510 super::operation::update::Unordered<Digest, super::value::VariableEncoding<Digest>>,
2511 { crate::qmdb::any::BITMAP_CHUNK_BYTES },
2512 Sequential,
2513 >;
2514
2515 async fn open_mmb_db(context: Context, suffix: &str) -> MmbVariable {
2516 let cfg = variable_db_config::<OneCap>(suffix, &context);
2517 super::init(context, cfg).await.unwrap()
2518 }
2519
2520 async fn commit_writes_mmb(
2521 db: &mut MmbVariable,
2522 writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
2523 metadata: Option<Digest>,
2524 ) {
2525 let mut batch = db.new_batch();
2526 for (k, v) in writes {
2527 batch = batch.write(k, v);
2528 }
2529 let merkleized = batch.merkleize(db, metadata).await.unwrap();
2530 db.apply_batch(merkleized).await.unwrap();
2531 db.commit().await.unwrap();
2532 }
2533
2534 #[test_traced("INFO")]
2535 fn test_mmb_batch_crud() {
2536 let executor = deterministic::Runner::default();
2537 executor.start(|context| async move {
2538 let mut db = open_mmb_db(context.child("db"), "crud").await;
2539
2540 commit_writes_mmb(&mut db, [(key(0), Some(val(0)))], None).await;
2542 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2543
2544 commit_writes_mmb(&mut db, [(key(0), Some(val(1)))], None).await;
2546 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(1)));
2547
2548 commit_writes_mmb(&mut db, [(key(0), None)], None).await;
2550 assert!(db.get(&key(0)).await.unwrap().is_none());
2551
2552 commit_writes_mmb(
2554 &mut db,
2555 [(key(1), Some(val(1))), (key(2), Some(val(2)))],
2556 None,
2557 )
2558 .await;
2559 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2560 assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
2561
2562 db.destroy().await.unwrap();
2563 });
2564 }
2565
2566 #[test_traced("INFO")]
2567 fn test_mmb_batch_empty() {
2568 let executor = deterministic::Runner::default();
2569 executor.start(|context| async move {
2570 let mut db = open_mmb_db(context.child("db"), "empty").await;
2571 let root_before = db.root();
2572
2573 let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
2574 db.apply_batch(merkleized).await.unwrap();
2575 assert_ne!(db.root(), root_before);
2576
2577 commit_writes_mmb(&mut db, [(key(0), Some(val(0)))], None).await;
2578 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2579
2580 db.destroy().await.unwrap();
2581 });
2582 }
2583
2584 #[test_traced("INFO")]
2585 fn test_mmb_batch_metadata() {
2586 let executor = deterministic::Runner::default();
2587 executor.start(|context| async move {
2588 let mut db = open_mmb_db(context.child("db"), "meta").await;
2589
2590 let metadata = val(42);
2591 commit_writes_mmb(&mut db, [(key(0), Some(val(0)))], Some(metadata)).await;
2592 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
2593
2594 let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
2595 db.apply_batch(merkleized).await.unwrap();
2596 assert_eq!(db.get_metadata().await.unwrap(), None);
2597
2598 db.destroy().await.unwrap();
2599 });
2600 }
2601
2602 #[test_traced("WARN")]
2603 fn test_mmb_recovery() {
2604 let executor = deterministic::Runner::default();
2605 executor.start(|context| async move {
2606 let mut db =
2607 open_mmb_db(context.child("db").with_attribute("index", 0), "recovery").await;
2608
2609 commit_writes_mmb(&mut db, [(key(0), Some(val(0)))], Some(val(99))).await;
2610 commit_writes_mmb(&mut db, [(key(1), Some(val(1)))], None).await;
2611
2612 let root = db.root();
2613 let bounds = db.bounds();
2614 db.sync().await.unwrap();
2615 drop(db);
2616
2617 let db = open_mmb_db(context.child("db").with_attribute("index", 1), "recovery").await;
2619 assert_eq!(db.root(), root);
2620 assert_eq!(db.bounds(), bounds);
2621 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2622 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2623 assert_eq!(db.get_metadata().await.unwrap(), None);
2624
2625 db.destroy().await.unwrap();
2626 });
2627 }
2628
2629 #[test_traced("INFO")]
2630 fn test_mmb_prune() {
2631 let executor = deterministic::Runner::default();
2632 executor.start(|context| async move {
2633 let mut db = open_mmb_db(context.child("db"), "prune").await;
2634
2635 for i in 0u64..20 {
2636 commit_writes_mmb(&mut db, [(key(i), Some(val(i)))], None).await;
2637 }
2638
2639 let floor = db.inactivity_floor_loc();
2640 db.prune(floor).await.unwrap();
2641
2642 for i in 0u64..20 {
2644 assert_eq!(db.get(&key(i)).await.unwrap(), Some(val(i)));
2645 }
2646
2647 db.destroy().await.unwrap();
2648 });
2649 }
2650
2651 #[test_traced("INFO")]
2653 fn test_any_batch_single_stage_pipeline() {
2654 let executor = deterministic::Runner::default();
2655 executor.start(|context| async move {
2656 let ctx = context.child("db");
2657 let mut db: UnorderedVariable = UnorderedVariableDb::init(
2658 ctx.child("storage"),
2659 variable_db_config::<OneCap>("pipe", &ctx),
2660 )
2661 .await
2662 .unwrap();
2663
2664 {
2665 let mut batch = db.new_batch();
2666 batch = batch.write(key(0), Some(val(0)));
2667 let merkleized = batch.merkleize(&db, None).await.unwrap();
2668 db.apply_batch(merkleized).await.unwrap();
2669 }
2670
2671 let child_merkleized = {
2672 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2673 let mut child = db.new_batch();
2674 child = child.write(key(1), Some(val(1)));
2675 child.merkleize(&db, None).await.unwrap()
2676 };
2677 db.commit().await.unwrap();
2678
2679 db.apply_batch(child_merkleized).await.unwrap();
2680 db.commit().await.unwrap();
2681
2682 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2683 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2684
2685 db.destroy().await.unwrap();
2686 });
2687 }
2688}
2689
2690#[cfg(test)]
2691mod bitmap_tests {
2692 use crate::{
2696 merkle::Location,
2697 qmdb::any::unordered::variable::test::{create_test_config, AnyTest},
2698 };
2699 use commonware_cryptography::{Hasher as _, Sha256};
2700 use commonware_macros::{boxed, test_traced};
2701 use commonware_runtime::{
2702 deterministic::{self, Context},
2703 Runner as _, Supervisor as _,
2704 };
2705 use commonware_utils::bitmap::Readable as _;
2706
2707 async fn open_db(context: Context) -> AnyTest {
2709 let cfg = create_test_config(0, &context);
2710 AnyTest::init(context, cfg).await.unwrap()
2711 }
2712
2713 fn bitmap_active_locs(db: &AnyTest) -> Vec<u64> {
2715 let b = &db.bitmap;
2716 (b.pruned_bits()..b.len())
2717 .filter(|loc| b.get_bit(*loc))
2718 .collect()
2719 }
2720
2721 #[boxed]
2723 async fn assert_oracle_round_trip(mut db: AnyTest, context: Context, label: &str) -> AnyTest {
2724 let pre_active = bitmap_active_locs(&db);
2725 let pre_len = db.bitmap.len();
2726 let pre_pruned = db.bitmap.pruned_bits();
2727
2728 db.commit().await.unwrap();
2729 drop(db);
2730
2731 let db = open_db(context.child("reopen").with_attribute("case", label)).await;
2732
2733 assert_eq!(
2734 db.bitmap.pruned_bits(),
2735 pre_pruned,
2736 "pruned_bits diverged on reopen",
2737 );
2738 assert_eq!(db.bitmap.len(), pre_len, "bitmap len diverged on reopen");
2739 assert_eq!(
2740 bitmap_active_locs(&db),
2741 pre_active,
2742 "active locations diverged on reopen",
2743 );
2744 db
2745 }
2746
2747 #[test_traced]
2753 fn current_commit_floor_bit_is_one_others_zero() {
2754 deterministic::Runner::default().start(|context| async move {
2755 let mut db = open_db(context.child("db")).await;
2756
2757 let mut commit_locs = Vec::new();
2759 for i in 0..3u64 {
2760 let key = Sha256::hash(&i.to_be_bytes());
2761 let batch = db
2762 .new_batch()
2763 .write(key, Some(vec![i as u8]))
2764 .merkleize(&db, None)
2765 .await
2766 .unwrap();
2767 commit_locs.push(Location::<crate::merkle::mmr::Family>::new(
2768 batch.bounds.total_size - 1,
2769 ));
2770 db.apply_batch(batch).await.unwrap();
2771 }
2772 db.commit().await.unwrap();
2773
2774 assert_eq!(commit_locs.len(), 3);
2776 assert!(*commit_locs[0] < *commit_locs[1]);
2777 assert!(*commit_locs[1] < *commit_locs[2]);
2778 assert!(*commit_locs[2] < db.bitmap.len());
2779
2780 assert!(!db.bitmap.get_bit(*commit_locs[0]));
2782 assert!(!db.bitmap.get_bit(*commit_locs[1]));
2783 assert!(db.bitmap.get_bit(*commit_locs[2]));
2785
2786 let db = assert_oracle_round_trip(db, context, "commit_floor").await;
2787 db.destroy().await.unwrap();
2788 });
2789 }
2790
2791 #[test_traced]
2802 fn rewind_restores_bitmap_to_target_commit() {
2803 deterministic::Runner::default().start(|context| async move {
2804 let mut db = open_db(context.child("db")).await;
2805 let k1 = Sha256::hash(&[1]);
2806 let k2 = Sha256::hash(&[2]);
2807
2808 let b1 = db
2810 .new_batch()
2811 .write(k1, Some(vec![10]))
2812 .merkleize(&db, None)
2813 .await
2814 .unwrap();
2815 db.apply_batch(b1).await.unwrap();
2816 db.commit().await.unwrap();
2817 let size_after_first = Location::new(*db.last_commit_loc + 1);
2818
2819 let b2 = db
2820 .new_batch()
2821 .write(k2, Some(vec![20]))
2822 .merkleize(&db, None)
2823 .await
2824 .unwrap();
2825 db.apply_batch(b2).await.unwrap();
2826
2827 assert_eq!(db.get(&k1).await.unwrap(), Some(vec![10]));
2829 assert_eq!(db.get(&k2).await.unwrap(), Some(vec![20]));
2830 assert!(*db.last_commit_loc + 1 > *size_after_first);
2831
2832 db.rewind(size_after_first).await.unwrap();
2834
2835 assert_eq!(db.get(&k1).await.unwrap(), Some(vec![10]));
2837 assert!(db.get(&k2).await.unwrap().is_none());
2838
2839 let db = assert_oracle_round_trip(db, context, "rewind").await;
2840 db.destroy().await.unwrap();
2841 });
2842 }
2843
2844 #[test_traced]
2864 fn floor_scan_falls_through_to_uncommitted_tail() {
2865 deterministic::Runner::default().start(|context| async move {
2866 let mut db = open_db(context.child("db")).await;
2867 let anchor = Sha256::hash(&[0xAA]);
2868
2869 let b = db
2871 .new_batch()
2872 .write(anchor, Some(vec![1]))
2873 .merkleize(&db, None)
2874 .await
2875 .unwrap();
2876 db.apply_batch(b).await.unwrap();
2877
2878 assert_eq!(db.get(&anchor).await.unwrap(), Some(vec![1]));
2880 let committed_bitmap_len = db.bitmap.len();
2881
2882 let parent = db
2884 .new_batch()
2885 .write(anchor, Some(vec![2]))
2886 .merkleize(&db, None)
2887 .await
2888 .unwrap();
2889 assert!(
2890 parent.bounds.total_size > committed_bitmap_len,
2891 "parent must extend past committed bitmap to exercise the tail path",
2892 );
2893
2894 let mut child_batch = parent.new_batch::<Sha256>();
2898 child_batch = child_batch.write(anchor, Some(vec![3]));
2899 for i in 0..16u64 {
2900 let k = Sha256::hash(&(1000 + i).to_be_bytes());
2901 child_batch = child_batch.write(k, Some(vec![i as u8]));
2902 }
2903 let child = child_batch.merkleize(&db, None).await.unwrap();
2904 assert!(
2905 child.bounds.total_size > committed_bitmap_len,
2906 "child must include an uncommitted tail beyond committed bitmap",
2907 );
2908 let expected_root = child.root();
2909
2910 db.apply_batch(child).await.unwrap();
2913 assert_eq!(db.root(), expected_root);
2914 assert_eq!(db.get(&anchor).await.unwrap(), Some(vec![3]));
2915
2916 let db = assert_oracle_round_trip(db, context, "tail").await;
2917 db.destroy().await.unwrap();
2918 });
2919 }
2920}