pub mod db;
pub mod fixed;
#[cfg(any(test, feature = "test-traits"))]
mod test_trait_impls;
pub mod variable;
#[cfg(test)]
pub mod tests {
use super::db;
use crate::{
index::unordered::Index,
journal::contiguous::{Contiguous as _, Mutable},
merkle::{Graftable, Location, Proof},
qmdb::{
Error,
any::{
ValueEncoding,
operation::update::Unordered as UnorderedUpdate,
traits::{DbAny, UnmerkleizedBatch as _},
unordered::Operation,
},
current::{BitmapPrunedBits, proof::RangeProof, tests::apply_random_ops},
store::tests::{TestKey, TestValue},
},
translator::TwoCap,
};
use commonware_codec::Codec;
use commonware_cryptography::{Digest as _, Hasher as _, Sha256, sha256::Digest};
use commonware_runtime::{
Runner as _, Supervisor as _,
deterministic::{self, Context},
};
use commonware_utils::{
NZU64,
bitmap::{Prunable as BitMap, Readable as _},
};
use core::future::Future;
use rand::Rng;
type TestDb<F, C, V> = db::Db<
F,
deterministic::Context,
C,
Digest,
V,
Index<TwoCap, Location<F>>,
Sha256,
32,
commonware_parallel::Sequential,
>;
pub async fn test_build_small_close_reopen<F, C, Fn, Fut>(context: Context, mut open_db: Fn)
where
F: Graftable,
C: DbAny<F> + BitmapPrunedBits,
C::Key: TestKey,
<C as DbAny<F>>::Value: TestValue,
Fn: FnMut(Context, String) -> Fut,
Fut: Future<Output = C>,
{
let partition = "build-small".to_string();
let db: C = open_db(context.child("first"), partition.clone()).await;
assert_eq!(db.inactivity_floor_loc(), Location::<F>::new(0));
assert_eq!(db.oldest_retained(), 0);
let root0 = db.root();
drop(db);
let db: C = open_db(context.child("second"), partition.clone()).await;
assert!(db.get_metadata().await.unwrap().is_none());
assert_eq!(db.root(), root0);
let k1: C::Key = TestKey::from_seed(0);
let v1: <C as DbAny<F>>::Value = TestValue::from_seed(10);
assert!(db.get(&k1).await.unwrap().is_none());
let merkleized = db
.new_batch()
.write(k1, Some(v1.clone()))
.merkleize(&db, None)
.await
.unwrap();
let (db, _) = db.apply_batch(merkleized).await.unwrap();
let db = db.commit().await.unwrap();
assert_eq!(db.get(&k1).await.unwrap().unwrap(), v1);
assert!(db.get_metadata().await.unwrap().is_none());
let root1 = db.root();
assert_ne!(root1, root0);
drop(db);
let db: C = open_db(context.child("third"), partition.clone()).await;
assert!(db.get_metadata().await.unwrap().is_none());
assert_eq!(db.root(), root1);
assert!(db.get(&k1).await.unwrap().is_some());
assert!(db.get(&k1).await.unwrap().is_some());
let metadata: <C as DbAny<F>>::Value = TestValue::from_seed(1);
let merkleized = db
.new_batch()
.write(k1, None)
.merkleize(&db, Some(metadata.clone()))
.await
.unwrap();
let (db, _) = db.apply_batch(merkleized).await.unwrap();
let db = db.commit().await.unwrap();
assert_eq!(db.get_metadata().await.unwrap().unwrap(), metadata);
let root2 = db.root();
assert!(db.get(&k1).await.unwrap().is_none());
let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
let (db, _) = db.apply_batch(merkleized).await.unwrap();
let db = db.sync().await.unwrap();
let root3 = db.root();
assert_ne!(root3, root2);
drop(db);
let db: C = open_db(context.child("fourth"), partition.clone()).await;
assert!(db.get_metadata().await.unwrap().is_none());
assert_eq!(db.root(), root3);
let bounds = db.bounds();
for i in 0..*bounds.end - 1 {
assert!(!db.get_bit(i));
}
assert!(db.get_bit(*bounds.end - 1));
let merkleized = db
.new_batch()
.write(k1, Some(v1))
.merkleize(&db, None)
.await
.unwrap();
let (db, _) = db.apply_batch(merkleized).await.unwrap();
assert_ne!(db.root(), root3);
db.destroy().await.unwrap();
}
pub(super) fn test_verify_proof_over_bits_in_uncommitted_chunk<F, C, V, Fn, Fut>(
mut open_db: Fn,
) where
F: Graftable,
C: Mutable<Item = Operation<F, Digest, V>> + 'static,
V: ValueEncoding<Value = Digest> + 'static,
Operation<F, Digest, V>: Codec,
TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
Fn: FnMut(Context, String) -> Fut + 'static,
Fut: Future<Output = TestDb<F, C, V>>,
{
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let partition = "build-small".to_string();
let db = open_db(context.child("db"), partition.clone()).await;
let k = Sha256::fill(0x01);
let v1 = Sha256::fill(0xA1);
let merkleized = db
.new_batch()
.write(k, Some(v1))
.merkleize(&db, None)
.await
.unwrap();
let (db, _) = db.apply_batch(merkleized).await.unwrap();
let (_, op_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
let proof = db.key_value_proof(k).await.unwrap();
let root = db.root();
assert!(TestDb::<F, C, V>::verify_key_value_proof(
k, v1, &proof, &root
));
let v2 = Sha256::fill(0xA2);
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
k, v2, &proof, &root,
));
let merkleized = db
.new_batch()
.write(k, Some(v2))
.merkleize(&db, None)
.await
.unwrap();
let (db, _) = db.apply_batch(merkleized).await.unwrap();
let root = db.root();
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
k, v2, &proof, &root,
));
let proof = db.key_value_proof(k).await.unwrap();
assert!(TestDb::<F, C, V>::verify_key_value_proof(
k, v2, &proof, &root,
));
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
k, v1, &proof, &root,
));
let (range_proof, _, chunks) = db.range_proof(op_loc, NZU64!(1)).await.unwrap();
let proof_inactive = db::KeyValueProof {
loc: op_loc,
chunk: chunks[0],
range_proof,
};
let op = Operation::Update(UnorderedUpdate(k, v1));
assert!(TestDb::<F, C, V>::verify_range_proof(
&proof_inactive.range_proof,
proof_inactive.loc,
&[op],
&[proof_inactive.chunk],
&root,
));
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
k,
v1,
&proof_inactive,
&root,
));
let (_, active_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
assert_ne!(active_loc, proof_inactive.loc);
assert_eq!(
BitMap::<32>::to_chunk_index(*active_loc),
BitMap::<32>::to_chunk_index(*proof_inactive.loc)
);
let mut fake_proof = proof_inactive.clone();
fake_proof.loc = active_loc;
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
k,
v1,
&fake_proof,
&root,
));
let mut modified_chunk = proof_inactive.chunk;
let bit_pos = *proof_inactive.loc;
let byte_idx = bit_pos / 8;
let bit_idx = bit_pos % 8;
modified_chunk[byte_idx as usize] |= 1 << bit_idx;
let mut fake_proof = proof_inactive.clone();
fake_proof.chunk = modified_chunk;
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
k,
v1,
&fake_proof,
&root,
));
db.destroy().await.unwrap();
});
}
pub(super) fn test_range_proofs<F, C, V, Fn, Fut>(mut open_db: Fn)
where
F: Graftable,
C: Mutable<Item = Operation<F, Digest, V>> + 'static,
V: ValueEncoding<Value = Digest> + 'static,
Operation<F, Digest, V>: Codec,
TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
Fn: FnMut(Context, String) -> Fut + 'static,
Fut: Future<Output = TestDb<F, C, V>>,
{
let executor = deterministic::Runner::default();
executor.start(|mut context| async move {
let partition = "range-proofs".to_string();
let db = open_db(context.child("db"), partition.clone()).await;
let root = db.root();
let proof = RangeProof {
proof: Proof::default(),
pending_chunk_digest: None.try_into().unwrap(),
partial_chunk_digest: None,
ops_root: Digest::EMPTY,
};
assert!(!TestDb::<F, C, V>::verify_range_proof(
&proof,
Location::<F>::new(0),
&[],
&[],
&root,
));
let db = apply_random_ops::<F, TestDb<F, C, V>>(200, true, context.next_u64(), db)
.await
.unwrap();
let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
let (db, _) = db.apply_batch(merkleized).await.unwrap();
let root = db.root();
let max_ops = 4;
let end_loc = db.bounds().end;
let start_loc = db.any.inactivity_floor_loc();
for loc in *start_loc..*end_loc {
let loc = Location::<F>::new(loc);
let (proof, ops, chunks) = db.range_proof(loc, NZU64!(max_ops)).await.unwrap();
assert!(
TestDb::<F, C, V>::verify_range_proof(&proof, loc, &ops, &chunks, &root),
"failed to verify range at start_loc {start_loc}",
);
let mut chunks_with_extra = chunks.clone();
chunks_with_extra.push(chunks[chunks.len() - 1]);
assert!(!TestDb::<F, C, V>::verify_range_proof(
&proof,
loc,
&ops,
&chunks_with_extra,
&root,
));
}
db.destroy().await.unwrap();
});
}
pub(super) fn test_key_value_proof<F, C, V, Fn, Fut>(mut open_db: Fn)
where
F: Graftable,
C: Mutable<Item = Operation<F, Digest, V>> + 'static,
V: ValueEncoding<Value = Digest> + 'static,
Operation<F, Digest, V>: Codec,
TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
Fn: FnMut(Context, String) -> Fut + 'static,
Fut: Future<Output = TestDb<F, C, V>>,
{
let executor = deterministic::Runner::default();
executor.start(|mut context| async move {
let partition = "range-proofs".to_string();
let db = open_db(context.child("db"), partition.clone()).await;
let db = apply_random_ops::<F, TestDb<F, C, V>>(500, true, context.next_u64(), db)
.await
.unwrap();
let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
let (db, _) = db.apply_batch(merkleized).await.unwrap();
let root = db.root();
let bad_key = Sha256::fill(0xAA);
let res = db.key_value_proof(bad_key).await;
assert!(matches!(res, Err(Error::KeyNotFound)));
let start = *db.inactivity_floor_loc();
for i in start..db.any.bitmap.len() {
if !db.any.bitmap.get_bit(i) {
continue;
}
let (key, value) = match db.any.log.read(*Location::<F>::new(i)).await.unwrap() {
Operation::Update(UnorderedUpdate(key, value)) => (key, value),
Operation::CommitFloor(_, _) => continue,
Operation::Delete(_) => {
unreachable!("location does not reference update/commit operation")
}
};
let proof = db.key_value_proof(key).await.unwrap();
assert!(TestDb::<F, C, V>::verify_key_value_proof(
key, value, &proof, &root
));
let wrong_val = Sha256::hash(&[&[0xFF]]);
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
key, wrong_val, &proof, &root
));
let wrong_key = Sha256::hash(&[&[0xEE]]);
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
wrong_key, value, &proof, &root
));
let wrong_root = Sha256::hash(&[&[0xDD]]);
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
key,
value,
&proof,
&wrong_root,
));
}
db.destroy().await.unwrap();
});
}
pub(super) fn test_proving_repeated_updates<F, C, V, Fn, Fut>(mut open_db: Fn)
where
F: Graftable,
C: Mutable<Item = Operation<F, Digest, V>> + 'static,
V: ValueEncoding<Value = Digest> + 'static,
Operation<F, Digest, V>: Codec,
TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
Fn: FnMut(Context, String) -> Fut + 'static,
Fut: Future<Output = TestDb<F, C, V>>,
{
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let partition = "build-small".to_string();
let mut db = open_db(context.child("db"), partition.clone()).await;
let k = Sha256::fill(0x00);
let mut old_val = Sha256::fill(0x00);
for i in 1u8..=255 {
let v = Sha256::fill(i);
let merkleized = db
.new_batch()
.write(k, Some(v))
.merkleize(&db, None)
.await
.unwrap();
(db, _) = db.apply_batch(merkleized).await.unwrap();
assert_eq!(db.get(&k).await.unwrap().unwrap(), v);
let root = db.root();
let proof = db.key_value_proof(k).await.unwrap();
assert!(
TestDb::<F, C, V>::verify_key_value_proof(k, v, &proof, &root),
"proof of update {i} failed to verify"
);
assert!(
!TestDb::<F, C, V>::verify_key_value_proof(k, old_val, &proof, &root,),
"proof of update {i} verified when it should not have"
);
old_val = v;
}
db.destroy().await.unwrap();
});
}
}