1pub use super::db::KeyValueProof;
10use crate::{
11 bitmap::MerkleizedBitMap,
12 journal::contiguous::variable::Journal,
13 mmr::{Location, StandardHasher},
14 qmdb::{
15 any::{
16 ordered::variable::{Db as AnyDb, Operation},
17 value::VariableEncoding,
18 VariableValue,
19 },
20 current::{
21 db::{merkleize_grafted_bitmap, root, Merkleized},
22 VariableConfig as Config,
23 },
24 Durable, Error,
25 },
26 translator::Translator,
27};
28use commonware_codec::{FixedSize, Read};
29use commonware_cryptography::{DigestOf, Hasher};
30use commonware_runtime::{Clock, Metrics, Storage as RStorage};
31use commonware_utils::Array;
32
33pub type Db<E, K, V, H, T, const N: usize, S = Merkleized<DigestOf<H>>, D = Durable> =
34 super::db::Db<E, Journal<E, Operation<K, V>>, K, VariableEncoding<V>, H, T, N, S, D>;
35
36impl<
38 E: RStorage + Clock + Metrics,
39 K: Array,
40 V: VariableValue,
41 H: Hasher,
42 T: Translator,
43 const N: usize,
44 > Db<E, K, V, H, T, N, Merkleized<DigestOf<H>>, Durable>
45where
46 Operation<K, V>: Read,
47{
48 pub async fn init(
51 context: E,
52 config: Config<T, <Operation<K, V> as Read>::Cfg>,
53 ) -> Result<Self, Error> {
54 const {
56 assert!(
60 N.is_multiple_of(H::Digest::SIZE),
61 "chunk size must be some multiple of the digest size",
62 );
63 assert!(N.is_power_of_two(), "chunk size must be a power of 2");
66 }
67
68 let thread_pool = config.thread_pool.clone();
69 let bitmap_metadata_partition = config.bitmap_metadata_partition.clone();
70
71 let mut hasher = StandardHasher::<H>::new();
72 let mut status = MerkleizedBitMap::init(
73 context.with_label("bitmap"),
74 &bitmap_metadata_partition,
75 thread_pool,
76 &mut hasher,
77 )
78 .await?
79 .into_dirty();
80
81 let last_known_inactivity_floor = Location::new_unchecked(status.len());
83 let any = AnyDb::init_with_callback(
84 context.with_label("any"),
85 config.into(),
86 Some(last_known_inactivity_floor),
87 |append: bool, loc: Option<Location>| {
88 status.push(append);
89 if let Some(loc) = loc {
90 status.set_bit(*loc, false);
91 }
92 },
93 )
94 .await?;
95
96 let status = merkleize_grafted_bitmap(&mut hasher, status, &any.log.mmr).await?;
97
98 let root = root(&mut hasher, &status, &any.log.mmr).await?;
100
101 Ok(Self {
102 any,
103 status,
104 state: Merkleized { root },
105 })
106 }
107}
108
109#[cfg(test)]
110mod test {
111 use crate::{
112 bitmap::MerkleizedBitMap,
113 kv::tests::{assert_batchable, assert_deletable, assert_gettable, assert_send},
114 mmr::{hasher::Hasher as _, Location, StandardHasher},
115 qmdb::{
116 any::ordered::variable::Operation,
117 current::{
118 db::{Merkleized, Unmerkleized},
119 ordered::{db::KeyValueProof, variable::Db},
120 proof::{OperationProof, RangeProof},
121 tests::{self, apply_random_ops},
122 VariableConfig as Config,
123 },
124 store::{
125 batch_tests,
126 tests::{assert_log_store, assert_merkleized_store, assert_prunable_store},
127 LogStore,
128 },
129 Durable, Error, NonDurable,
130 },
131 translator::OneCap,
132 };
133 use commonware_cryptography::{sha256::Digest, Hasher as _, Sha256};
134 use commonware_macros::test_traced;
135 use commonware_runtime::{buffer::paged::CacheRef, deterministic, Runner as _};
136 use commonware_utils::{NZUsize, NZU16, NZU64};
137 use rand::RngCore;
138 use std::num::{NonZeroU16, NonZeroUsize};
139
140 const PAGE_SIZE: NonZeroU16 = NZU16!(88);
141 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(8);
142
143 fn current_db_config(partition_prefix: &str) -> Config<OneCap, ()> {
144 Config {
145 mmr_journal_partition: format!("{partition_prefix}_journal_partition"),
146 mmr_metadata_partition: format!("{partition_prefix}_metadata_partition"),
147 mmr_items_per_blob: NZU64!(11),
148 mmr_write_buffer: NZUsize!(1024),
149 log_partition: format!("{partition_prefix}_log_partition"),
150 log_items_per_blob: NZU64!(7),
151 log_write_buffer: NZUsize!(1024),
152 log_compression: None,
153 log_codec_config: (),
154 bitmap_metadata_partition: format!("{partition_prefix}_bitmap_metadata_partition"),
155 translator: OneCap,
156 thread_pool: None,
157 page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
158 }
159 }
160
161 type CleanCurrentTest =
163 Db<deterministic::Context, Digest, Digest, Sha256, OneCap, 32, Merkleized<Digest>, Durable>;
164
165 type MutableCurrentTest =
167 Db<deterministic::Context, Digest, Digest, Sha256, OneCap, 32, Unmerkleized, NonDurable>;
168
169 async fn open_db(
171 context: deterministic::Context,
172 partition_prefix: String,
173 ) -> CleanCurrentTest {
174 CleanCurrentTest::init(context, current_db_config(&partition_prefix))
175 .await
176 .unwrap()
177 }
178
179 #[test_traced("DEBUG")]
180 pub fn test_current_db_build_small_close_reopen() {
181 super::super::tests::test_build_small_close_reopen::<CleanCurrentTest, _, _>(open_db);
182 }
183
184 #[test_traced("WARN")]
185 fn test_current_db_build_big() {
186 tests::test_current_db_build_big::<CleanCurrentTest, _, _>(open_db, 4241, 3383);
188 }
189
190 #[test_traced("DEBUG")]
192 fn test_current_ordered_variable_db_steps_not_reset() {
193 let executor = deterministic::Runner::default();
194 executor.start(|context| async move {
195 let db = open_db(context, "steps_test".to_string()).await;
196 crate::qmdb::any::test::test_any_db_steps_not_reset(db).await;
197 });
198 }
199
200 #[test_traced("DEBUG")]
204 pub fn test_current_db_verify_proof_over_bits_in_uncommitted_chunk() {
205 let executor = deterministic::Runner::default();
206 executor.start(|context| async move {
207 let mut hasher = StandardHasher::<Sha256>::new();
208 let partition = "build_small".to_string();
209 let mut db = open_db(context, partition).await.into_mutable();
210
211 let k = Sha256::fill(0x01);
213 let v1 = Sha256::fill(0xA1);
214 db.update(k, v1).await.unwrap();
215 let (db, _) = db.commit(None).await.unwrap();
216 let db = db.into_merkleized().await.unwrap();
217
218 let (_, op_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
219 let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
220
221 let root = db.root();
223 assert!(CleanCurrentTest::verify_key_value_proof(
224 hasher.inner(),
225 k,
226 v1,
227 &proof,
228 &root,
229 ));
230
231 let v2 = Sha256::fill(0xA2);
232 assert!(!CleanCurrentTest::verify_key_value_proof(
234 hasher.inner(),
235 k,
236 v2,
237 &proof,
238 &root,
239 ));
240 let mut mangled_proof = proof.clone();
242 mangled_proof.next_key = Sha256::fill(0xFF);
243 assert!(!CleanCurrentTest::verify_key_value_proof(
244 hasher.inner(),
245 k,
246 v1,
247 &mangled_proof,
248 &root,
249 ));
250
251 let mut db = db.into_mutable();
253 db.update(k, v2).await.unwrap();
254 let (db, _) = db.commit(None).await.unwrap();
255 let db = db.into_merkleized().await.unwrap();
256 let root = db.root();
257
258 assert!(!CleanCurrentTest::verify_key_value_proof(
260 hasher.inner(),
261 k,
262 v2,
263 &proof,
264 &root,
265 ));
266
267 let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
269 assert!(CleanCurrentTest::verify_key_value_proof(
270 hasher.inner(),
271 k,
272 v2,
273 &proof,
274 &root,
275 ));
276 assert!(!CleanCurrentTest::verify_key_value_proof(
278 hasher.inner(),
279 k,
280 v1,
281 &proof,
282 &root,
283 ));
284
285 let (p, _, chunks) = db
288 .range_proof(hasher.inner(), op_loc, NZU64!(1))
289 .await
290 .unwrap();
291 let proof_inactive = KeyValueProof {
292 proof: OperationProof {
293 loc: op_loc,
294 chunk: chunks[0],
295 range_proof: p,
296 },
297 next_key: k,
298 };
299 let op = Operation::Update(crate::qmdb::any::ordered::Update {
302 key: k,
303 value: v1,
304 next_key: k,
305 });
306 assert!(CleanCurrentTest::verify_range_proof(
307 hasher.inner(),
308 &proof_inactive.proof.range_proof,
309 proof_inactive.proof.loc,
310 &[op],
311 &[proof_inactive.proof.chunk],
312 &root,
313 ));
314 assert!(!CleanCurrentTest::verify_key_value_proof(
317 hasher.inner(),
318 k,
319 v1,
320 &proof_inactive,
321 &root,
322 ));
323
324 let (_, active_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
328 assert_ne!(active_loc, proof_inactive.proof.loc);
330 assert_eq!(
331 MerkleizedBitMap::<deterministic::Context, Digest, 32>::leaf_pos(*active_loc),
332 MerkleizedBitMap::<deterministic::Context, Digest, 32>::leaf_pos(
333 *proof_inactive.proof.loc
334 )
335 );
336 let mut fake_proof = proof_inactive.clone();
337 fake_proof.proof.loc = active_loc;
338 assert!(!CleanCurrentTest::verify_key_value_proof(
339 hasher.inner(),
340 k,
341 v1,
342 &fake_proof,
343 &root,
344 ));
345
346 let mut modified_chunk = proof_inactive.proof.chunk;
351 let bit_pos = *proof_inactive.proof.loc;
352 let byte_idx = bit_pos / 8;
353 let bit_idx = bit_pos % 8;
354 modified_chunk[byte_idx as usize] |= 1 << bit_idx;
355
356 let mut fake_proof = proof_inactive.clone();
357 fake_proof.proof.chunk = modified_chunk;
358 assert!(!CleanCurrentTest::verify_key_value_proof(
359 hasher.inner(),
360 k,
361 v1,
362 &fake_proof,
363 &root,
364 ));
365
366 db.destroy().await.unwrap();
367 });
368 }
369
370 #[test_traced("DEBUG")]
371 pub fn test_current_db_range_proofs() {
372 let executor = deterministic::Runner::default();
373 executor.start(|mut context| async move {
374 let partition = "range_proofs".to_string();
375 let mut hasher = StandardHasher::<Sha256>::new();
376 let db = open_db(context.clone(), partition).await;
377 let root = db.root();
378
379 let proof = RangeProof {
382 proof: crate::mmr::Proof::default(),
383 partial_chunk_digest: None,
384 };
385 assert!(!CleanCurrentTest::verify_range_proof(
386 hasher.inner(),
387 &proof,
388 Location::new_unchecked(0),
389 &[],
390 &[],
391 &root,
392 ));
393
394 let rng_seed = context.next_u64();
395 let db = apply_random_ops::<CleanCurrentTest>(200, true, rng_seed, db.into_mutable())
396 .await
397 .unwrap();
398 let (db, _) = db.commit(None).await.unwrap();
399 let db = db.into_merkleized().await.unwrap();
400 let root = db.root();
401
402 let max_ops = 4;
405 let end_loc = db.size();
406 let start_loc = db.any.inactivity_floor_loc();
407
408 for loc in *start_loc..*end_loc {
409 let loc = Location::new_unchecked(loc);
410 let (proof, ops, chunks) = db
411 .range_proof(hasher.inner(), loc, NZU64!(max_ops))
412 .await
413 .unwrap();
414 assert!(
415 CleanCurrentTest::verify_range_proof(
416 hasher.inner(),
417 &proof,
418 loc,
419 &ops,
420 &chunks,
421 &root
422 ),
423 "failed to verify range at start_loc {start_loc}",
424 );
425 let mut chunks_with_extra = chunks.clone();
427 chunks_with_extra.push(chunks[chunks.len() - 1]);
428 assert!(!CleanCurrentTest::verify_range_proof(
429 hasher.inner(),
430 &proof,
431 loc,
432 &ops,
433 &chunks_with_extra,
434 &root,
435 ));
436 }
437
438 db.destroy().await.unwrap();
439 });
440 }
441
442 #[test_traced("DEBUG")]
443 pub fn test_current_db_key_value_proof() {
444 let executor = deterministic::Runner::default();
445 executor.start(|mut context| async move {
446 let partition = "range_proofs".to_string();
447 let mut hasher = StandardHasher::<Sha256>::new();
448 let db = open_db(context.clone(), partition.clone())
449 .await
450 .into_mutable();
451 let db = apply_random_ops::<CleanCurrentTest>(500, true, context.next_u64(), db)
452 .await
453 .unwrap();
454 let (db, _) = db.commit(None).await.unwrap();
455 let db = db.into_merkleized().await.unwrap();
456 let root = db.root();
457
458 let bad_key = Sha256::fill(0xAA);
460 let res = db.key_value_proof(hasher.inner(), bad_key).await;
461 assert!(matches!(res, Err(Error::KeyNotFound)));
462
463 let start = *db.inactivity_floor_loc();
464 for i in start..db.status.len() {
465 if !db.status.get_bit(i) {
466 continue;
467 }
468 let op = db.any.log.read(Location::new_unchecked(i)).await.unwrap();
471 let (key, value) = match op {
472 Operation::Update(key_data) => (key_data.key, key_data.value),
473 Operation::CommitFloor(_, _) => continue,
474 _ => unreachable!("expected update or commit floor operation"),
475 };
476 let proof = db.key_value_proof(hasher.inner(), key).await.unwrap();
477
478 assert!(CleanCurrentTest::verify_key_value_proof(
480 hasher.inner(),
481 key,
482 value,
483 &proof,
484 &root
485 ));
486 let wrong_val = Sha256::hash(&[0xFF]);
490 assert!(!CleanCurrentTest::verify_key_value_proof(
491 hasher.inner(),
492 key,
493 wrong_val,
494 &proof,
495 &root
496 ));
497 let wrong_key = Sha256::hash(&[0xEE]);
499 assert!(!CleanCurrentTest::verify_key_value_proof(
500 hasher.inner(),
501 wrong_key,
502 value,
503 &proof,
504 &root
505 ));
506 let wrong_root = Sha256::hash(&[0xDD]);
508 assert!(!CleanCurrentTest::verify_key_value_proof(
509 hasher.inner(),
510 key,
511 value,
512 &proof,
513 &wrong_root,
514 ));
515 let mut bad_proof = proof.clone();
517 bad_proof.next_key = wrong_key;
518 assert!(!CleanCurrentTest::verify_key_value_proof(
519 hasher.inner(),
520 key,
521 value,
522 &bad_proof,
523 &root,
524 ));
525 }
526
527 db.destroy().await.unwrap();
528 });
529 }
530
531 #[test_traced("WARN")]
534 pub fn test_current_db_build_random_close_reopen() {
535 crate::qmdb::current::tests::test_build_random_close_reopen(open_db);
536 }
537
538 #[test_traced("WARN")]
541 pub fn test_current_db_proving_repeated_updates() {
542 let executor = deterministic::Runner::default();
543 executor.start(|context| async move {
544 let mut hasher = StandardHasher::<Sha256>::new();
545 let partition = "build_small".to_string();
546 let mut db = open_db(context, partition).await;
547
548 let k = Sha256::fill(0x00);
550 let mut old_val = Sha256::fill(0x00);
551 for i in 1u8..=255 {
552 let v = Sha256::fill(i);
553 let mut dirty_db = db.into_mutable();
554 dirty_db.update(k, v).await.unwrap();
555 assert_eq!(dirty_db.get(&k).await.unwrap().unwrap(), v);
556 let (dirty_db, _) = dirty_db.commit(None).await.unwrap();
557 let clean_db = dirty_db.into_merkleized().await.unwrap();
558 db = clean_db;
559 let root = db.root();
560
561 let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
563 assert!(
564 CleanCurrentTest::verify_key_value_proof(hasher.inner(), k, v, &proof, &root),
565 "proof of update {i} failed to verify"
566 );
567 assert!(
569 !CleanCurrentTest::verify_key_value_proof(
570 hasher.inner(),
571 k,
572 old_val,
573 &proof,
574 &root
575 ),
576 "proof of update {i} verified when it should not have"
577 );
578 old_val = v;
579 }
580
581 db.destroy().await.unwrap();
582 });
583 }
584
585 #[test_traced("WARN")]
588 pub fn test_current_db_simulate_write_failures() {
589 crate::qmdb::current::tests::test_simulate_write_failures(open_db);
590 }
591
592 #[test_traced("WARN")]
593 pub fn test_current_db_different_pruning_delays_same_root() {
594 tests::test_different_pruning_delays_same_root::<CleanCurrentTest, _, _>(open_db);
595 }
596
597 #[test_traced("WARN")]
598 pub fn test_current_db_sync_persists_bitmap_pruning_boundary() {
599 tests::test_sync_persists_bitmap_pruning_boundary::<CleanCurrentTest, _, _>(open_db);
600 }
601
602 #[test_traced("DEBUG")]
603 fn test_batch() {
604 batch_tests::test_batch(|mut ctx| async move {
605 let seed = ctx.next_u64();
606 let prefix = format!("current_ordered_variable_batch_{seed}");
607 open_db(ctx, prefix).await.into_mutable()
608 });
609 }
610
611 #[test_traced("DEBUG")]
613 pub fn test_current_db_exclusion_proofs() {
614 let executor = deterministic::Runner::default();
615 executor.start(|context| async move {
616 let mut hasher = StandardHasher::<Sha256>::new();
617 let partition = "exclusion_proofs".to_string();
618 let db = open_db(context, partition).await;
619
620 let key_exists_1 = Sha256::fill(0x10);
621
622 let empty_root = db.root();
624 let empty_proof = db
625 .exclusion_proof(hasher.inner(), &key_exists_1)
626 .await
627 .unwrap();
628 assert!(CleanCurrentTest::verify_exclusion_proof(
629 hasher.inner(),
630 &key_exists_1,
631 &empty_proof,
632 &empty_root,
633 ));
634
635 let v1 = Sha256::fill(0xA1);
637 let mut db = db.into_mutable();
638 db.update(key_exists_1, v1).await.unwrap();
639 let (db, _) = db.commit(None).await.unwrap();
640 let db = db.into_merkleized().await.unwrap();
641 let root = db.root();
642
643 let result = db.exclusion_proof(hasher.inner(), &key_exists_1).await;
645 assert!(matches!(result, Err(Error::KeyExists)));
646
647 let greater_key = Sha256::fill(0xFF);
649 let lesser_key = Sha256::fill(0x00);
650 let proof = db
651 .exclusion_proof(hasher.inner(), &greater_key)
652 .await
653 .unwrap();
654 let proof2 = db
655 .exclusion_proof(hasher.inner(), &lesser_key)
656 .await
657 .unwrap();
658
659 assert_eq!(proof, proof2);
662 assert!(CleanCurrentTest::verify_exclusion_proof(
664 hasher.inner(),
665 &greater_key,
666 &proof,
667 &root,
668 ));
669 assert!(CleanCurrentTest::verify_exclusion_proof(
670 hasher.inner(),
671 &lesser_key,
672 &proof,
673 &root,
674 ));
675 assert!(!CleanCurrentTest::verify_exclusion_proof(
677 hasher.inner(),
678 &key_exists_1,
679 &proof,
680 &root,
681 ));
682
683 let key_exists_2 = Sha256::fill(0x30);
685 let v2 = Sha256::fill(0xB2);
686
687 let mut db = db.into_mutable();
688 db.update(key_exists_2, v2).await.unwrap();
689 let (db, _) = db.commit(None).await.unwrap();
690 let db = db.into_merkleized().await.unwrap();
691 let root = db.root();
692
693 let lesser_key = Sha256::fill(0x0F); let greater_key = Sha256::fill(0x31); let middle_key = Sha256::fill(0x20); let proof = db
699 .exclusion_proof(hasher.inner(), &greater_key)
700 .await
701 .unwrap();
702 assert!(CleanCurrentTest::verify_exclusion_proof(
705 hasher.inner(),
706 &greater_key,
707 &proof,
708 &root,
709 ));
710 assert!(CleanCurrentTest::verify_exclusion_proof(
711 hasher.inner(),
712 &lesser_key,
713 &proof,
714 &root,
715 ));
716 assert!(!CleanCurrentTest::verify_exclusion_proof(
717 hasher.inner(),
718 &middle_key,
719 &proof,
720 &root,
721 ));
722
723 let new_proof = db
725 .exclusion_proof(hasher.inner(), &lesser_key)
726 .await
727 .unwrap();
728 assert_eq!(proof, new_proof);
729
730 let proof = db
732 .exclusion_proof(hasher.inner(), &middle_key)
733 .await
734 .unwrap();
735 assert!(!CleanCurrentTest::verify_exclusion_proof(
737 hasher.inner(),
738 &key_exists_1,
739 &proof,
740 &root,
741 ));
742 assert!(CleanCurrentTest::verify_exclusion_proof(
744 hasher.inner(),
745 &middle_key,
746 &proof,
747 &root,
748 ));
749 assert!(!CleanCurrentTest::verify_exclusion_proof(
750 hasher.inner(),
751 &key_exists_2,
752 &proof,
753 &root,
754 ));
755
756 let conflicting_middle_key = Sha256::fill(0x11); assert!(CleanCurrentTest::verify_exclusion_proof(
758 hasher.inner(),
759 &conflicting_middle_key,
760 &proof,
761 &root,
762 ));
763
764 assert!(!CleanCurrentTest::verify_exclusion_proof(
766 hasher.inner(),
767 &greater_key,
768 &proof,
769 &root,
770 ));
771 assert!(!CleanCurrentTest::verify_exclusion_proof(
772 hasher.inner(),
773 &lesser_key,
774 &proof,
775 &root,
776 ));
777
778 let mut db = db.into_mutable();
781 db.delete(key_exists_1).await.unwrap();
782 db.delete(key_exists_2).await.unwrap();
783 let (db, _) = db.commit(None).await.unwrap();
784 let mut db = db.into_merkleized().await.unwrap();
785 db.sync().await.unwrap();
786 let root = db.root();
787 assert!(db.is_empty());
790 assert_ne!(db.bounds().end, 0);
791 assert_ne!(root, empty_root);
792
793 let proof = db
794 .exclusion_proof(hasher.inner(), &key_exists_1)
795 .await
796 .unwrap();
797 assert!(CleanCurrentTest::verify_exclusion_proof(
798 hasher.inner(),
799 &key_exists_1,
800 &proof,
801 &root,
802 ));
803 assert!(CleanCurrentTest::verify_exclusion_proof(
804 hasher.inner(),
805 &key_exists_2,
806 &proof,
807 &root,
808 ));
809
810 assert!(!CleanCurrentTest::verify_exclusion_proof(
812 hasher.inner(),
813 &key_exists_1,
814 &empty_proof, &root,
816 ));
817 assert!(!CleanCurrentTest::verify_exclusion_proof(
818 hasher.inner(),
819 &key_exists_1,
820 &proof,
821 &empty_root, ));
823 });
824 }
825
826 #[allow(dead_code)]
827 fn assert_merkleized_db_futures_are_send(
828 db: &mut CleanCurrentTest,
829 key: Digest,
830 loc: Location,
831 ) {
832 assert_gettable(db, &key);
833 assert_log_store(db);
834 assert_prunable_store(db, loc);
835 assert_merkleized_store(db, loc);
836 assert_send(db.sync());
837 }
838
839 #[allow(dead_code)]
840 fn assert_mutable_db_futures_are_send(db: &mut MutableCurrentTest, key: Digest, value: Digest) {
841 assert_gettable(db, &key);
842 assert_log_store(db);
843 assert_send(db.update(key, value));
844 assert_send(db.create(key, value));
845 assert_deletable(db, key);
846 assert_batchable(db, key, value);
847 }
848
849 #[allow(dead_code)]
850 fn assert_mutable_db_commit_is_send(db: MutableCurrentTest) {
851 assert_send(db.commit(None));
852 }
853}