1pub use super::db::KeyValueProof;
10use crate::{
11 index::unordered::Index,
12 journal::contiguous::fixed::Journal,
13 merkle::{Graftable, Location},
14 qmdb::{
15 any::{unordered::fixed::Operation, value::FixedEncoding, FixedValue},
16 current::FixedConfig as Config,
17 Error,
18 },
19 translator::Translator,
20 Context,
21};
22use commonware_cryptography::Hasher;
23use commonware_parallel::Strategy;
24use commonware_utils::Array;
25
26pub type Db<F, E, K, V, H, T, const N: usize, S> = super::db::Db<
28 F,
29 E,
30 Journal<E, Operation<F, K, V>>,
31 K,
32 FixedEncoding<V>,
33 Index<T, Location<F>>,
34 H,
35 N,
36 S,
37>;
38
39impl<
40 F: Graftable,
41 E: Context,
42 K: Array,
43 V: FixedValue,
44 H: Hasher,
45 T: Translator,
46 const N: usize,
47 S: Strategy,
48 > Db<F, E, K, V, H, T, N, S>
49{
50 pub async fn init(context: E, config: Config<T, S>) -> Result<Self, Error<F>> {
53 crate::qmdb::current::init(context, config).await
54 }
55}
56
57pub mod partitioned {
58 use super::*;
64 use crate::index::partitioned::unordered::Index;
65
66 pub type Db<F, E, K, V, H, T, const P: usize, const N: usize, S> =
73 crate::qmdb::current::unordered::db::Db<
74 F,
75 E,
76 Journal<E, Operation<F, K, V>>,
77 K,
78 FixedEncoding<V>,
79 Index<T, Location<F>, P>,
80 H,
81 N,
82 S,
83 >;
84
85 impl<
86 F: Graftable,
87 E: Context,
88 K: Array,
89 V: FixedValue,
90 H: Hasher,
91 T: Translator,
92 const P: usize,
93 const N: usize,
94 S: Strategy,
95 > Db<F, E, K, V, H, T, P, N, S>
96 {
97 pub async fn init(context: E, config: Config<T, S>) -> Result<Self, Error<F>> {
100 crate::qmdb::current::init(context, config).await
101 }
102 }
103}
104
105#[cfg(test)]
106pub mod test {
107 use super::*;
108 use crate::{
109 mmr,
110 qmdb::current::{tests::fixed_config, unordered::tests as shared},
111 translator::TwoCap,
112 };
113 use commonware_cryptography::{sha256::Digest, Sha256};
114 use commonware_macros::test_traced;
115 use commonware_runtime::{deterministic, Metrics, Runner as _, Supervisor as _};
116 use commonware_utils::TestRng;
117 use rand::Rng as _;
118 use std::collections::HashMap;
119
120 type CurrentTest = Db<
122 mmr::Family,
123 deterministic::Context,
124 Digest,
125 Digest,
126 Sha256,
127 TwoCap,
128 32,
129 commonware_parallel::Sequential,
130 >;
131
132 async fn open_db(context: deterministic::Context, partition_prefix: String) -> CurrentTest {
134 let cfg = fixed_config::<TwoCap>(&partition_prefix, &context);
135 CurrentTest::init(context, cfg).await.unwrap()
136 }
137
138 #[test_traced("INFO")]
139 pub fn test_current_unordered_fixed_metrics() {
140 deterministic::Runner::default().start(|ctx| async move {
141 let mut db = open_db(ctx.child("current"), "metrics".to_string()).await;
142 let key = Sha256::fill(1u8);
143 let value = Sha256::fill(2u8);
144 let batch = db
145 .new_batch()
146 .write(key, Some(value))
147 .merkleize(&db, None)
148 .await
149 .unwrap();
150 db.apply_batch(batch).await.unwrap();
151 assert_eq!(db.get(&key).await.unwrap(), Some(value));
152 db.sync().await.unwrap();
153 db.prune(db.sync_boundary()).await.unwrap();
154
155 let metrics = ctx.encode();
156 for expected in [
157 "current_apply_batch_calls_total 1",
158 "current_sync_calls_total 1",
159 "current_prune_calls_total 1",
160 "current_pruned_chunks 0",
161 "current_sync_boundary 0",
162 "current_apply_batch_duration_count 1",
163 "current_sync_duration_count 1",
164 "current_prune_duration_count 1",
165 "current_any_get_calls_total 1",
166 "current_any_apply_batch_calls_total 1",
167 ] {
168 assert!(metrics.contains(expected), "missing {expected}\n{metrics}");
169 }
170 assert!(!metrics.contains("current_get_calls_total"));
171 });
172 }
173
174 #[test_traced("WARN")]
177 pub fn test_current_unordered_fixed_read_merkleize_parity() {
178 fn key(i: u64) -> Digest {
179 Sha256::hash(&i.to_be_bytes())
180 }
181 fn val(i: u64) -> Digest {
182 Sha256::hash(&(i + 10000).to_be_bytes())
183 }
184
185 deterministic::Runner::default().start(|ctx| async move {
186 let mut db = open_db(ctx.child("current"), "fused-parity".to_string()).await;
187
188 let mut seed = db.new_batch();
189 for i in 0..2000u64 {
190 seed = seed.write(key(i), Some(val(i)));
191 }
192 let seed = seed.merkleize(&db, None).await.unwrap();
193 db.apply_batch(seed).await.unwrap();
194 db.commit().await.unwrap();
195
196 let make = |salt: u64| -> Vec<(Digest, Option<Digest>)> {
197 let mut rng = TestRng::new(salt);
198 let mut out = Vec::new();
199 for _ in 0..600 {
200 let r = rng.next_u32() % 100;
201 if r < 60 {
202 out.push((key(rng.next_u64() % 2000), Some(val(rng.next_u64()))));
203 } else if r < 80 {
204 out.push((key(rng.next_u64() % 2000), None));
205 } else {
206 out.push((key(2000 + rng.next_u64() % 2000), Some(val(rng.next_u64()))));
207 }
208 }
209 let mut m: HashMap<Digest, Option<Digest>> = HashMap::new();
210 for (k, v) in out {
211 m.insert(k, v);
212 }
213 m.into_iter().collect()
214 };
215
216 for depth in [0u8, 1u8] {
217 let parent = if depth == 1 {
218 let mut p = db.new_batch();
219 for (k, v) in make(900) {
220 p = p.write(k, v);
221 }
222 Some(p.merkleize(&db, None).await.unwrap())
223 } else {
224 None
225 };
226
227 let muts = make(depth as u64 + 1);
228 let new_batch = || {
229 parent
230 .as_ref()
231 .map_or_else(|| db.new_batch(), |p| p.new_batch::<Sha256>())
232 };
233
234 let mut nb = new_batch();
235 for (k, v) in &muts {
236 nb = nb.write(*k, *v);
237 }
238 let normal_root = nb.merkleize(&db, None).await.unwrap().root();
239
240 let keys: Vec<&Digest> = muts.iter().map(|(k, _)| k).collect();
241 let mut fb = new_batch();
242 let values = fb.get_many(&keys, &db).await.unwrap();
243 let plain = new_batch().get_many(&keys, &db).await.unwrap();
244 assert_eq!(values, plain, "value mismatch at depth={depth}");
245 for (k, v) in &muts {
246 fb = fb.write(*k, *v);
247 }
248 let fused_root = fb.merkleize(&db, None).await.unwrap().root();
249 assert_eq!(normal_root, fused_root, "root mismatch at depth={depth}");
250 }
251 });
252 }
253
254 crate::qmdb::current::tests::staged_merkleize_parity_test!(
255 test_current_unordered_fixed_staged_merkleize_parity,
256 open_db
257 );
258
259 #[test_traced("WARN")]
266 pub fn test_current_unordered_fixed_staged_ancestor_commit_before_merkleize() {
267 fn key(i: u64) -> Digest {
268 Sha256::hash(&i.to_be_bytes())
269 }
270 fn val(i: u64) -> Digest {
271 Sha256::hash(&(i + 10000).to_be_bytes())
272 }
273
274 deterministic::Runner::default().start(|ctx| async move {
275 let mut db = open_db(ctx.child("current"), "staged-ancestor".to_string()).await;
276
277 let mut seed = db.new_batch();
280 for i in 0..8u64 {
281 seed = seed.write(key(i), Some(val(i)));
282 }
283 let seed = seed.merkleize(&db, None).await.unwrap();
284 db.apply_batch(seed).await.unwrap();
285 db.commit().await.unwrap();
286
287 let grandparent = db
290 .new_batch()
291 .write(key(0), Some(val(1_000)))
292 .write(key(100), Some(val(1_001)))
293 .merkleize(&db, None)
294 .await
295 .unwrap();
296 let parent = grandparent
297 .new_batch::<Sha256>()
298 .write(key(1), Some(val(1_002)))
299 .merkleize(&db, None)
300 .await
301 .unwrap();
302
303 let read_keys = [key(0), key(100)];
304 let keys: Vec<&Digest> = read_keys.iter().collect();
305 let (values, staged) = parent
306 .new_batch::<Sha256>()
307 .stage(&keys, &db)
308 .await
309 .unwrap();
310 assert_eq!(values, vec![Some(val(1_000)), Some(val(1_001))]);
311
312 db.apply_batch(grandparent).await.unwrap();
315
316 let updates = vec![(0, Some(val(2_000))), (1, Some(val(2_001)))];
317 let staged = staged
318 .merkleize(updates, Vec::new(), None, &db)
319 .await
320 .unwrap();
321
322 let explicit_root = parent
324 .new_batch::<Sha256>()
325 .write(key(0), Some(val(2_000)))
326 .write(key(100), Some(val(2_001)))
327 .merkleize(&db, None)
328 .await
329 .unwrap()
330 .root();
331 assert_eq!(staged.root(), explicit_root);
332
333 db.apply_batch(parent).await.unwrap();
334 db.apply_batch(staged).await.unwrap();
335 db.commit().await.unwrap();
336
337 assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(2_000)));
338 assert_eq!(db.get(&key(100)).await.unwrap(), Some(val(2_001)));
339 assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1_002)));
340 });
341 }
342
343 #[test_traced("INFO")]
349 pub fn test_merkleized_batch_sync_boundary_matches_db() {
350 deterministic::Runner::default().start(|ctx| async move {
351 let partition = "batch-boundary-match".to_string();
352 let mut db = open_db(ctx.child("current"), partition.clone()).await;
353
354 let key = Sha256::fill(1u8);
355 let mut last_batch_boundary = mmr::Location::new(0);
356 for i in 0..300u64 {
357 let value = Sha256::hash(&i.to_be_bytes());
358 let batch = db
359 .new_batch()
360 .write(key, Some(value))
361 .merkleize(&db, None)
362 .await
363 .unwrap();
364 last_batch_boundary = batch.sync_boundary();
365 db.apply_batch(batch).await.unwrap();
366 }
367 db.sync().await.unwrap();
368
369 let db_boundary = db.sync_boundary();
372 assert!(
373 *db_boundary > 0,
374 "inactivity floor never crossed a chunk; add more commits"
375 );
376
377 assert_eq!(
380 last_batch_boundary, db_boundary,
381 "batch boundary diverged from applied db boundary"
382 );
383
384 drop(db);
386 let reopened = open_db(ctx.child("reopen"), partition).await;
387 assert_eq!(
388 reopened.sync_boundary(),
389 last_batch_boundary,
390 "reopened db boundary disagrees with the boundary recorded from the last merkleized batch"
391 );
392 reopened.destroy().await.unwrap();
393 });
394 }
395
396 #[test_traced("DEBUG")]
397 pub fn test_current_db_verify_proof_over_bits_in_uncommitted_chunk() {
398 shared::test_verify_proof_over_bits_in_uncommitted_chunk(open_db);
399 }
400
401 #[test_traced("DEBUG")]
402 pub fn test_current_db_range_proofs() {
403 shared::test_range_proofs(open_db);
404 }
405
406 #[test_traced("DEBUG")]
407 pub fn test_current_db_key_value_proof() {
408 shared::test_key_value_proof(open_db);
409 }
410
411 #[test_traced("WARN")]
412 pub fn test_current_db_proving_repeated_updates() {
413 shared::test_proving_repeated_updates(open_db);
414 }
415}