1pub use super::db::KeyValueProof;
10use crate::{
11 index::unordered::Index,
12 journal::contiguous::variable::Journal,
13 mmr::Location,
14 qmdb::{
15 any::{unordered::variable::Operation, value::VariableEncoding, VariableValue},
16 current::VariableConfig as Config,
17 Error,
18 },
19 translator::Translator,
20};
21use commonware_codec::Read;
22use commonware_cryptography::Hasher;
23use commonware_runtime::{Clock, Metrics, Storage as RStorage};
24use commonware_utils::Array;
25
26pub type Db<E, K, V, H, T, const N: usize> =
27 super::db::Db<E, Journal<E, Operation<K, V>>, K, VariableEncoding<V>, Index<T, Location>, H, N>;
28
29impl<
30 E: RStorage + Clock + Metrics,
31 K: Array,
32 V: VariableValue,
33 H: Hasher,
34 T: Translator,
35 const N: usize,
36 > Db<E, K, V, H, T, N>
37where
38 Operation<K, V>: Read,
39{
40 pub async fn init(
43 context: E,
44 config: Config<T, <Operation<K, V> as Read>::Cfg>,
45 ) -> Result<Self, Error> {
46 crate::qmdb::current::init_variable(context, config, |ctx, t| Index::new(ctx, t)).await
47 }
48}
49
50pub mod partitioned {
51 pub use super::KeyValueProof;
54 use crate::{
55 index::partitioned::unordered::Index,
56 journal::contiguous::variable::Journal,
57 mmr::Location,
58 qmdb::{
59 any::{
60 unordered::variable::partitioned::Operation, value::VariableEncoding, VariableValue,
61 },
62 current::VariableConfig as Config,
63 Error,
64 },
65 translator::Translator,
66 };
67 use commonware_codec::Read;
68 use commonware_cryptography::Hasher;
69 use commonware_runtime::{Clock, Metrics, Storage as RStorage};
70 use commonware_utils::Array;
71
72 pub type Db<E, K, V, H, T, const P: usize, const N: usize> =
79 crate::qmdb::current::unordered::db::Db<
80 E,
81 Journal<E, Operation<K, V>>,
82 K,
83 VariableEncoding<V>,
84 Index<T, Location, P>,
85 H,
86 N,
87 >;
88
89 impl<
90 E: RStorage + Clock + Metrics,
91 K: Array,
92 V: VariableValue,
93 H: Hasher,
94 T: Translator,
95 const P: usize,
96 const N: usize,
97 > Db<E, K, V, H, T, P, N>
98 where
99 Operation<K, V>: Read,
100 {
101 pub async fn init(
104 context: E,
105 config: Config<T, <Operation<K, V> as Read>::Cfg>,
106 ) -> Result<Self, Error> {
107 crate::qmdb::current::init_variable(context, config, |ctx, t| Index::new(ctx, t)).await
108 }
109 }
110}
111
112#[cfg(test)]
113mod test {
114 use crate::{
115 kv::tests::{assert_gettable, assert_send},
116 mmr::{hasher::Hasher as _, Location, Proof, StandardHasher},
117 qmdb::{
118 any::unordered::variable::Operation,
119 current::{
120 proof::RangeProof,
121 tests::{apply_random_ops, variable_config},
122 unordered::{db::KeyValueProof, variable::Db},
123 },
124 store::{
125 tests::{assert_log_store, assert_merkleized_store, assert_prunable_store},
126 LogStore as _,
127 },
128 Error,
129 },
130 translator::TwoCap,
131 };
132 use commonware_cryptography::{sha256::Digest, Digest as _, Hasher as _, Sha256};
133 use commonware_macros::test_traced;
134 use commonware_runtime::{deterministic, Metrics as _, Runner as _};
135 use commonware_utils::{bitmap::Prunable as BitMap, NZU64};
136 use rand::RngCore;
137
138 type CurrentTest = Db<deterministic::Context, Digest, Digest, Sha256, TwoCap, 32>;
140
141 async fn open_db(context: deterministic::Context, partition_prefix: String) -> CurrentTest {
143 let cfg = variable_config::<TwoCap>(&partition_prefix, &context);
144 CurrentTest::init(context, cfg).await.unwrap()
145 }
146
147 #[test_traced("DEBUG")]
151 pub fn test_current_db_verify_proof_over_bits_in_uncommitted_chunk() {
152 let executor = deterministic::Runner::default();
153 executor.start(|context| async move {
154 let mut hasher = StandardHasher::<Sha256>::new();
155 let partition = "build-small".to_string();
156 let mut db = open_db(context.with_label("uncommitted_chunk"), partition.clone()).await;
157
158 let k = Sha256::fill(0x01);
160 let v1 = Sha256::fill(0xA1);
161 let finalized = {
162 let mut batch = db.new_batch();
163 batch.write(k, Some(v1));
164 batch.merkleize(None).await.unwrap().finalize()
165 };
166 db.apply_batch(finalized).await.unwrap();
167
168 let (_, op_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
169 let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
170
171 let root = db.root();
173 assert!(CurrentTest::verify_key_value_proof(
174 hasher.inner(),
175 k,
176 v1,
177 &proof,
178 &root
179 ));
180
181 let v2 = Sha256::fill(0xA2);
182 assert!(!CurrentTest::verify_key_value_proof(
184 hasher.inner(),
185 k,
186 v2,
187 &proof,
188 &root,
189 ));
190
191 let finalized = {
193 let mut batch = db.new_batch();
194 batch.write(k, Some(v2));
195 batch.merkleize(None).await.unwrap().finalize()
196 };
197 db.apply_batch(finalized).await.unwrap();
198 let root = db.root();
199
200 assert!(!CurrentTest::verify_key_value_proof(
202 hasher.inner(),
203 k,
204 v2,
205 &proof,
206 &root,
207 ));
208
209 let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
211 assert!(CurrentTest::verify_key_value_proof(
212 hasher.inner(),
213 k,
214 v2,
215 &proof,
216 &root,
217 ));
218
219 assert!(!CurrentTest::verify_key_value_proof(
221 hasher.inner(),
222 k,
223 v1,
224 &proof,
225 &root,
226 ));
227
228 let (range_proof, _, chunks) = db
231 .range_proof(hasher.inner(), op_loc, NZU64!(1))
232 .await
233 .unwrap();
234 let proof_inactive = KeyValueProof {
235 loc: op_loc,
236 chunk: chunks[0],
237 range_proof,
238 };
239 let op = Operation::Update(crate::qmdb::any::operation::update::Unordered(k, v1));
242 assert!(CurrentTest::verify_range_proof(
243 hasher.inner(),
244 &proof_inactive.range_proof,
245 proof_inactive.loc,
246 &[op],
247 &[proof_inactive.chunk],
248 &root,
249 ));
250
251 assert!(!CurrentTest::verify_key_value_proof(
254 hasher.inner(),
255 k,
256 v1,
257 &proof_inactive,
258 &root,
259 ));
260
261 let (_, active_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
265 assert_ne!(active_loc, proof_inactive.loc);
267 assert_eq!(
268 BitMap::<32>::to_chunk_index(*active_loc),
269 BitMap::<32>::to_chunk_index(*proof_inactive.loc)
270 );
271 let mut fake_proof = proof_inactive.clone();
272 fake_proof.loc = active_loc;
273 assert!(!CurrentTest::verify_key_value_proof(
274 hasher.inner(),
275 k,
276 v1,
277 &fake_proof,
278 &root,
279 ));
280
281 let mut modified_chunk = proof_inactive.chunk;
286 let bit_pos = *proof_inactive.loc;
287 let byte_idx = bit_pos / 8;
288 let bit_idx = bit_pos % 8;
289 modified_chunk[byte_idx as usize] |= 1 << bit_idx;
290
291 let mut fake_proof = proof_inactive.clone();
292 fake_proof.chunk = modified_chunk;
293 assert!(!CurrentTest::verify_key_value_proof(
294 hasher.inner(),
295 k,
296 v1,
297 &fake_proof,
298 &root,
299 ));
300
301 db.destroy().await.unwrap();
302 });
303 }
304
305 #[test_traced("DEBUG")]
306 pub fn test_current_db_range_proofs() {
307 let executor = deterministic::Runner::default();
308 executor.start(|mut context| async move {
309 let partition = "range-proofs".to_string();
310 let mut hasher = StandardHasher::<Sha256>::new();
311 let db = open_db(context.with_label("first"), partition.clone()).await;
312 let root = db.root();
313
314 let proof = RangeProof {
317 proof: Proof::default(),
318 partial_chunk_digest: None,
319 ops_root: Digest::EMPTY,
320 };
321 assert!(!CurrentTest::verify_range_proof(
322 hasher.inner(),
323 &proof,
324 Location::new(0),
325 &[],
326 &[],
327 &root,
328 ));
329
330 let mut db = apply_random_ops::<CurrentTest>(200, true, context.next_u64(), db)
331 .await
332 .unwrap();
333 let finalized = db.new_batch().merkleize(None).await.unwrap().finalize();
334 db.apply_batch(finalized).await.unwrap();
335 let root = db.root();
336
337 let max_ops = 4;
340 let end_loc = db.size().await;
341 let start_loc = db.any.inactivity_floor_loc();
342
343 for loc in *start_loc..*end_loc {
344 let loc = Location::new(loc);
345 let (proof, ops, chunks) = db
346 .range_proof(hasher.inner(), loc, NZU64!(max_ops))
347 .await
348 .unwrap();
349 assert!(
350 CurrentTest::verify_range_proof(
351 hasher.inner(),
352 &proof,
353 loc,
354 &ops,
355 &chunks,
356 &root
357 ),
358 "failed to verify range at start_loc {start_loc}",
359 );
360 let mut chunks_with_extra = chunks.clone();
362 chunks_with_extra.push(chunks[chunks.len() - 1]);
363 assert!(!CurrentTest::verify_range_proof(
364 hasher.inner(),
365 &proof,
366 loc,
367 &ops,
368 &chunks_with_extra,
369 &root,
370 ));
371 }
372
373 db.destroy().await.unwrap();
374 });
375 }
376
377 #[test_traced("DEBUG")]
378 pub fn test_current_db_key_value_proof() {
379 let executor = deterministic::Runner::default();
380 executor.start(|mut context| async move {
381 let partition = "range-proofs".to_string();
382 let mut hasher = StandardHasher::<Sha256>::new();
383 let db = open_db(context.clone(), partition.clone()).await;
384 let mut db = apply_random_ops::<CurrentTest>(500, true, context.next_u64(), db)
385 .await
386 .unwrap();
387 let finalized = db.new_batch().merkleize(None).await.unwrap().finalize();
388 db.apply_batch(finalized).await.unwrap();
389 let root = db.root();
390
391 let bad_key = Sha256::fill(0xAA);
393 let res = db.key_value_proof(hasher.inner(), bad_key).await;
394 assert!(matches!(res, Err(Error::KeyNotFound)));
395
396 let start = *db.inactivity_floor_loc();
397 for i in start..db.status.len() {
398 if !db.status.get_bit(i) {
399 continue;
400 }
401 let (key, value) = match db.any.log.read(Location::new(i)).await.unwrap() {
404 Operation::Update(crate::qmdb::any::operation::update::Unordered(
405 key,
406 value,
407 )) => (key, value),
408 Operation::CommitFloor(_, _) => continue,
409 Operation::Delete(_) => {
410 unreachable!("location does not reference update/commit operation")
411 }
412 };
413
414 let proof = db.key_value_proof(hasher.inner(), key).await.unwrap();
415 assert!(CurrentTest::verify_key_value_proof(
417 hasher.inner(),
418 key,
419 value,
420 &proof,
421 &root
422 ));
423 let wrong_val = Sha256::hash(&[0xFF]);
427 assert!(!CurrentTest::verify_key_value_proof(
428 hasher.inner(),
429 key,
430 wrong_val,
431 &proof,
432 &root
433 ));
434 let wrong_key = Sha256::hash(&[0xEE]);
436 assert!(!CurrentTest::verify_key_value_proof(
437 hasher.inner(),
438 wrong_key,
439 value,
440 &proof,
441 &root
442 ));
443 let wrong_root = Sha256::hash(&[0xDD]);
445 assert!(!CurrentTest::verify_key_value_proof(
446 hasher.inner(),
447 key,
448 value,
449 &proof,
450 &wrong_root,
451 ));
452 }
453
454 db.destroy().await.unwrap();
455 });
456 }
457
458 #[test_traced("WARN")]
461 pub fn test_current_db_proving_repeated_updates() {
462 let executor = deterministic::Runner::default();
463 executor.start(|context| async move {
464 let mut hasher = StandardHasher::<Sha256>::new();
465 let partition = "build-small".to_string();
466 let mut db = open_db(context.clone(), partition.clone()).await;
467
468 let k = Sha256::fill(0x00);
470 let mut old_val = Sha256::fill(0x00);
471 for i in 1u8..=255 {
472 let v = Sha256::fill(i);
473 let finalized = {
474 let mut batch = db.new_batch();
475 batch.write(k, Some(v));
476 batch.merkleize(None).await.unwrap().finalize()
477 };
478 db.apply_batch(finalized).await.unwrap();
479 assert_eq!(db.get(&k).await.unwrap().unwrap(), v);
480 let root = db.root();
481
482 let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
484 assert!(
485 CurrentTest::verify_key_value_proof(hasher.inner(), k, v, &proof, &root),
486 "proof of update {i} failed to verify"
487 );
488 assert!(
490 !CurrentTest::verify_key_value_proof(hasher.inner(), k, old_val, &proof, &root),
491 "proof of update {i} verified when it should not have"
492 );
493 old_val = v;
494 }
495
496 db.destroy().await.unwrap();
497 });
498 }
499
500 #[allow(dead_code)]
501 fn assert_db_futures_are_send(db: &mut CurrentTest, key: Digest, loc: Location) {
502 assert_gettable(db, &key);
503 assert_log_store(db);
504 assert_prunable_store(db, loc);
505 assert_merkleized_store(db, loc);
506 assert_send(db.sync());
507 }
508}