1pub use super::db::KeyValueProof;
10use crate::{
11 Context,
12 index::unordered::Index,
13 journal::contiguous::fixed::Journal,
14 merkle::{Graftable, Location},
15 qmdb::{
16 Error,
17 any::{FixedValue, unordered::fixed::Operation, value::FixedEncoding},
18 current::FixedConfig as Config,
19 },
20 translator::Translator,
21};
22use commonware_cryptography::Hasher;
23use commonware_parallel::Strategy;
24use commonware_runtime::Spawner;
25use commonware_utils::Array;
26
27pub type Db<F, E, K, V, H, T, const N: usize, S> = super::db::Db<
29 F,
30 E,
31 Journal<E, Operation<F, K, V>>,
32 K,
33 FixedEncoding<V>,
34 Index<T, Location<F>>,
35 H,
36 N,
37 S,
38>;
39
40impl<
41 F: Graftable,
42 E: Context + Spawner,
43 K: Array,
44 V: FixedValue,
45 H: Hasher,
46 T: Translator,
47 const N: usize,
48 S: Strategy,
49> Db<F, E, K, V, H, T, N, S>
50{
51 pub async fn init(context: E, config: Config<T, S>) -> Result<Self, Error<F>> {
54 crate::qmdb::current::init(context, config).await
55 }
56}
57
58pub mod partitioned {
59 use super::*;
65 use crate::index::partitioned::unordered::Index;
66
67 pub type Db<F, E, K, V, H, T, const P: usize, const N: usize, S> =
74 crate::qmdb::current::unordered::db::Db<
75 F,
76 E,
77 Journal<E, Operation<F, K, V>>,
78 K,
79 FixedEncoding<V>,
80 Index<T, Location<F>, P>,
81 H,
82 N,
83 S,
84 >;
85
86 impl<
87 F: Graftable,
88 E: Context + Spawner,
89 K: Array,
90 V: FixedValue,
91 H: Hasher,
92 T: Translator,
93 const P: usize,
94 const N: usize,
95 S: Strategy,
96 > Db<F, E, K, V, H, T, P, N, S>
97 {
98 pub async fn init(
101 context: E,
102 config: Config<T, S, core::num::NonZeroUsize>,
103 ) -> Result<Self, Error<F>> {
104 crate::qmdb::current::init(context, config).await
105 }
106 }
107}
108
109#[cfg(test)]
110pub mod test {
111 use super::*;
112 use crate::{
113 mmr,
114 qmdb::current::{
115 tests::{fixed_config, fixed_config_partitioned},
116 unordered::tests as shared,
117 },
118 translator::{OneCap, TwoCap},
119 };
120 use commonware_cryptography::{Sha256, sha256::Digest};
121 use commonware_macros::test_traced;
122 use commonware_parallel::Sequential;
123 use commonware_runtime::{Metrics, Runner as _, Supervisor as _, deterministic};
124 use commonware_utils::TestRng;
125 use rand::Rng as _;
126 use std::collections::HashMap;
127
128 type CurrentTest = Db<
130 mmr::Family,
131 deterministic::Context,
132 Digest,
133 Digest,
134 Sha256,
135 TwoCap,
136 32,
137 commonware_parallel::Sequential,
138 >;
139
140 async fn open_db(context: deterministic::Context, partition_prefix: String) -> CurrentTest {
142 let cfg = fixed_config::<TwoCap>(&partition_prefix, &context);
143 CurrentTest::init(context, cfg).await.unwrap()
144 }
145
146 #[test_traced("INFO")]
147 pub fn test_current_unordered_fixed_metrics() {
148 deterministic::Runner::default().start(|ctx| async move {
149 let db = open_db(ctx.child("current"), "metrics".to_string()).await;
150 let key = Sha256::fill(1u8);
151 let value = Sha256::fill(2u8);
152 let batch = db
153 .new_batch()
154 .write(key, Some(value))
155 .merkleize(&db, None)
156 .await
157 .unwrap();
158 let (db, _) = db.apply_batch(batch).await.unwrap();
159 assert_eq!(db.get(&key).await.unwrap(), Some(value));
160 let db = db.sync().await.unwrap();
161 let boundary = db.sync_boundary();
162 let _db = db.prune(boundary).await.unwrap();
163
164 let metrics = ctx.encode();
165 for expected in [
166 "current_apply_batch_calls_total 1",
167 "current_sync_calls_total 1",
168 "current_prune_calls_total 1",
169 "current_pruned_chunks 0",
170 "current_sync_boundary 0",
171 "current_apply_batch_duration_count 1",
172 "current_sync_duration_count 1",
173 "current_prune_duration_count 1",
174 "current_any_get_calls_total 1",
175 "current_any_apply_batch_calls_total 1",
176 ] {
177 assert!(metrics.contains(expected), "missing {expected}\n{metrics}");
178 }
179 assert!(!metrics.contains("current_get_calls_total"));
180 });
181 }
182
183 #[test_traced("WARN")]
186 pub fn test_current_unordered_fixed_read_merkleize_parity() {
187 fn key(i: u64) -> Digest {
188 Sha256::hash(&[&i.to_be_bytes()])
189 }
190 fn val(i: u64) -> Digest {
191 Sha256::hash(&[&(i + 10000).to_be_bytes()])
192 }
193
194 deterministic::Runner::default().start(|ctx| async move {
195 let db = open_db(ctx.child("current"), "fused-parity".to_string()).await;
196
197 let mut seed = db.new_batch();
198 for i in 0..2000u64 {
199 seed = seed.write(key(i), Some(val(i)));
200 }
201 let seed = seed.merkleize(&db, None).await.unwrap();
202 let (db, _) = db.apply_batch(seed).await.unwrap();
203 let db = db.commit().await.unwrap();
204
205 let make = |salt: u64| -> Vec<(Digest, Option<Digest>)> {
206 let mut rng = TestRng::new(salt);
207 let mut out = Vec::new();
208 for _ in 0..600 {
209 let r = rng.next_u32() % 100;
210 if r < 60 {
211 out.push((key(rng.next_u64() % 2000), Some(val(rng.next_u64()))));
212 } else if r < 80 {
213 out.push((key(rng.next_u64() % 2000), None));
214 } else {
215 out.push((key(2000 + rng.next_u64() % 2000), Some(val(rng.next_u64()))));
216 }
217 }
218 let mut m: HashMap<Digest, Option<Digest>> = HashMap::new();
219 for (k, v) in out {
220 m.insert(k, v);
221 }
222 m.into_iter().collect()
223 };
224
225 for depth in [0u8, 1u8] {
226 let parent = if depth == 1 {
227 let mut p = db.new_batch();
228 for (k, v) in make(900) {
229 p = p.write(k, v);
230 }
231 Some(p.merkleize(&db, None).await.unwrap())
232 } else {
233 None
234 };
235
236 let muts = make(depth as u64 + 1);
237 let new_batch = || {
238 parent
239 .as_ref()
240 .map_or_else(|| db.new_batch(), |p| p.new_batch::<Sha256>())
241 };
242
243 let mut nb = new_batch();
244 for (k, v) in &muts {
245 nb = nb.write(*k, *v);
246 }
247 let normal_root = nb.merkleize(&db, None).await.unwrap().root();
248
249 let keys: Vec<&Digest> = muts.iter().map(|(k, _)| k).collect();
250 let mut fb = new_batch();
251 let values = fb.get_many(&keys, &db).await.unwrap();
252 let plain = new_batch().get_many(&keys, &db).await.unwrap();
253 assert_eq!(values, plain, "value mismatch at depth={depth}");
254 for (k, v) in &muts {
255 fb = fb.write(*k, *v);
256 }
257 let fused_root = fb.merkleize(&db, None).await.unwrap().root();
258 assert_eq!(normal_root, fused_root, "root mismatch at depth={depth}");
259 }
260 });
261 }
262
263 crate::qmdb::current::tests::staged_merkleize_parity_test!(
264 test_current_unordered_fixed_staged_merkleize_parity,
265 open_db
266 );
267
268 #[test_traced("WARN")]
275 pub fn test_current_unordered_fixed_staged_ancestor_commit_before_merkleize() {
276 fn key(i: u64) -> Digest {
277 Sha256::hash(&[&i.to_be_bytes()])
278 }
279 fn val(i: u64) -> Digest {
280 Sha256::hash(&[&(i + 10000).to_be_bytes()])
281 }
282
283 deterministic::Runner::default().start(|ctx| async move {
284 let db = open_db(ctx.child("current"), "staged-ancestor".to_string()).await;
285
286 let mut seed = db.new_batch();
289 for i in 0..8u64 {
290 seed = seed.write(key(i), Some(val(i)));
291 }
292 let seed = seed.merkleize(&db, None).await.unwrap();
293 let (db, _) = db.apply_batch(seed).await.unwrap();
294 let db = db.commit().await.unwrap();
295
296 let grandparent = db
299 .new_batch()
300 .write(key(0), Some(val(1_000)))
301 .write(key(100), Some(val(1_001)))
302 .merkleize(&db, None)
303 .await
304 .unwrap();
305 let parent = grandparent
306 .new_batch::<Sha256>()
307 .write(key(1), Some(val(1_002)))
308 .merkleize(&db, None)
309 .await
310 .unwrap();
311
312 let read_keys = [key(0), key(100)];
313 let keys: Vec<&Digest> = read_keys.iter().collect();
314 let (values, staged) = parent
315 .new_batch::<Sha256>()
316 .stage(&keys, &db)
317 .await
318 .unwrap();
319 assert_eq!(values, vec![Some(val(1_000)), Some(val(1_001))]);
320
321 let (db, _) = db.apply_batch(grandparent).await.unwrap();
324
325 let updates = vec![(0, Some(val(2_000))), (1, Some(val(2_001)))];
326 let staged = staged
327 .merkleize(updates, Vec::new(), None, &db)
328 .await
329 .unwrap();
330
331 let explicit_root = parent
333 .new_batch::<Sha256>()
334 .write(key(0), Some(val(2_000)))
335 .write(key(100), Some(val(2_001)))
336 .merkleize(&db, None)
337 .await
338 .unwrap()
339 .root();
340 assert_eq!(staged.root(), explicit_root);
341
342 let (db, _) = db.apply_batch(parent).await.unwrap();
343 let (db, _) = db.apply_batch(staged).await.unwrap();
344 let db = db.commit().await.unwrap();
345
346 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(2_000)));
347 assert_eq!(db.get(&key(100)).await.unwrap(), Some(val(2_001)));
348 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1_002)));
349 });
350 }
351
352 #[test_traced("INFO")]
358 pub fn test_merkleized_batch_sync_boundary_matches_db() {
359 deterministic::Runner::default().start(|ctx| async move {
360 let partition = "batch-boundary-match".to_string();
361 let mut db = open_db(ctx.child("current"), partition.clone()).await;
362
363 let key = Sha256::fill(1u8);
364 let mut last_batch_boundary = mmr::Location::new(0);
365 for i in 0..300u64 {
366 let value = Sha256::hash(&[&i.to_be_bytes()]);
367 let batch = db
368 .new_batch()
369 .write(key, Some(value))
370 .merkleize(&db, None)
371 .await
372 .unwrap();
373 last_batch_boundary = batch.sync_boundary();
374 (db, _) = db.apply_batch(batch).await.unwrap();
375 }
376 let db = db.sync().await.unwrap();
377
378 let db_boundary = db.sync_boundary();
381 assert!(
382 *db_boundary > 0,
383 "inactivity floor never crossed a chunk; add more commits"
384 );
385
386 assert_eq!(
389 last_batch_boundary, db_boundary,
390 "batch boundary diverged from applied db boundary"
391 );
392
393 drop(db);
395 let reopened = open_db(ctx.child("reopen"), partition).await;
396 assert_eq!(
397 reopened.sync_boundary(),
398 last_batch_boundary,
399 "reopened db boundary disagrees with the boundary recorded from the last merkleized batch"
400 );
401 reopened.destroy().await.unwrap();
402 });
403 }
404
405 #[test_traced("DEBUG")]
406 pub fn test_current_db_verify_proof_over_bits_in_uncommitted_chunk() {
407 shared::test_verify_proof_over_bits_in_uncommitted_chunk(open_db);
408 }
409
410 #[test_traced("DEBUG")]
411 pub fn test_current_db_range_proofs() {
412 shared::test_range_proofs(open_db);
413 }
414
415 #[test_traced("DEBUG")]
416 pub fn test_current_db_key_value_proof() {
417 shared::test_key_value_proof(open_db);
418 }
419
420 #[test_traced("WARN")]
421 pub fn test_current_db_proving_repeated_updates() {
422 shared::test_proving_repeated_updates(open_db);
423 }
424
425 #[commonware_macros::boxed]
432 async fn check_current_parallel_init_equivalence<const P: usize>(
433 context: deterministic::Context,
434 partition: &'static str,
435 concurrency_sweep: &[usize],
436 ) {
437 type PartDb<const P: usize, S> = partitioned::Db<
438 mmr::Family,
439 deterministic::Context,
440 Digest,
441 Digest,
442 Sha256,
443 OneCap,
444 P,
445 32,
446 S,
447 >;
448
449 fn expected_value(i: u64) -> Option<Digest> {
451 if i % 7 == 1 {
452 None
453 } else if i.is_multiple_of(3) {
454 Some(Sha256::hash(&[&((i + 1) * 11).to_be_bytes()]))
455 } else {
456 Some(Sha256::hash(&[&(i * 7).to_be_bytes()]))
457 }
458 }
459
460 let cfg = fixed_config_partitioned::<OneCap>(partition, &context);
461 let db = PartDb::<P, Sequential>::init(context.child("populate"), cfg)
462 .await
463 .unwrap();
464
465 let mut batch = db.new_batch();
467 for i in 0u64..2000 {
468 let k = Sha256::hash(&[&i.to_be_bytes()]);
469 let v = Sha256::hash(&[&(i * 7).to_be_bytes()]);
470 batch = batch.write(k, Some(v));
471 }
472 let merkleized = batch.merkleize(&db, None).await.unwrap();
473 let (db, _) = db.apply_batch(merkleized).await.unwrap();
474 let db = db.commit().await.unwrap();
475
476 let mut batch = db.new_batch();
478 for i in (0u64..2000).step_by(3) {
479 let k = Sha256::hash(&[&i.to_be_bytes()]);
480 let v = Sha256::hash(&[&((i + 1) * 11).to_be_bytes()]);
481 batch = batch.write(k, Some(v));
482 }
483 for i in (1u64..2000).step_by(7) {
484 let k = Sha256::hash(&[&i.to_be_bytes()]);
485 batch = batch.write(k, None);
486 }
487 let merkleized = batch.merkleize(&db, None).await.unwrap();
488 let (db, _) = db.apply_batch(merkleized).await.unwrap();
489 let db = db.commit().await.unwrap();
490
491 let boundary = db.sync_boundary();
493 let db = db.prune(boundary).await.unwrap();
494 let db = db.sync().await.unwrap();
495 let root = db.root();
496 drop(db);
497
498 for &concurrency in concurrency_sweep {
501 let mut cfg = fixed_config_partitioned::<OneCap>(partition, &context);
502 cfg.init_concurrency = core::num::NonZeroUsize::new(concurrency).unwrap();
503 let ctx = context
504 .child("reopen")
505 .with_attribute("concurrency", concurrency);
506 let db = PartDb::<P, Sequential>::init(ctx, cfg).await.unwrap();
507 assert_eq!(
508 db.root(),
509 root,
510 "current root mismatch at P={P} concurrency={concurrency}"
511 );
512 for i in 0u64..2000 {
513 let k = Sha256::hash(&[&i.to_be_bytes()]);
514 assert_eq!(
515 db.get(&k).await.unwrap(),
516 expected_value(i),
517 "value mismatch for key {i}"
518 );
519 }
520 drop(db);
521 }
522 }
523
524 #[test_traced("WARN")]
528 fn test_current_unordered_partitioned_p1_parallel_init_equivalence() {
529 deterministic::Runner::default().start(|context| async move {
530 check_current_parallel_init_equivalence::<1>(
531 context,
532 "current_unordered_parallel_equiv_p1",
533 &[1, 2, 3, 5],
534 )
535 .await;
536 });
537 }
538}