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