use crate::{
Context,
index::ordered::Index,
journal::contiguous::variable::Journal,
merkle::{Family, Location},
qmdb::{
Error,
any::{VariableConfig, VariableValue, ordered, value::VariableEncoding},
operation::Key,
},
translator::OrderedTranslator,
};
use commonware_codec::Read;
use commonware_cryptography::Hasher;
use commonware_parallel::Strategy;
use commonware_runtime::Spawner;
pub type Update<K, V> = ordered::Update<K, VariableEncoding<V>>;
pub type Operation<F, K, V> = ordered::Operation<F, K, VariableEncoding<V>>;
pub type Db<F, E, K, V, H, T, S> = super::Db<
F,
E,
Journal<E, Operation<F, K, V>>,
Index<T, Location<F>>,
H,
Update<K, V>,
{ crate::qmdb::any::BITMAP_CHUNK_BYTES },
S,
>;
impl<
F: Family,
E: Context + Spawner,
K: Key,
V: VariableValue,
H: Hasher,
T: OrderedTranslator,
S: Strategy,
> Db<F, E, K, V, H, T, S>
{
pub async fn init(
context: E,
cfg: VariableConfig<T, <Operation<F, K, V> as Read>::Cfg, S>,
max_size: Option<Location<F>>,
) -> Result<Self, Error<F>> {
crate::qmdb::any::init(context, cfg, max_size).await
}
}
pub mod partitioned {
pub use super::{Operation, Update};
use crate::{
Context,
index::partitioned::ordered::Index,
journal::contiguous::variable::Journal,
merkle::{Family, Location},
qmdb::{
Error,
any::{VariableConfig, VariableValue},
operation::Key,
},
translator::OrderedTranslator,
};
use commonware_codec::Read;
use commonware_cryptography::Hasher;
use commonware_parallel::Strategy;
use commonware_runtime::Spawner;
pub type Db<F, E, K, V, H, T, const P: usize, S> = crate::qmdb::any::ordered::Db<
F,
E,
Journal<E, Operation<F, K, V>>,
Index<T, Location<F>, P>,
H,
Update<K, V>,
{ crate::qmdb::any::BITMAP_CHUNK_BYTES },
S,
>;
impl<
F: Family,
E: Context + Spawner,
K: Key,
V: VariableValue,
H: Hasher,
T: OrderedTranslator,
const P: usize,
S: Strategy,
> Db<F, E, K, V, H, T, P, S>
{
pub async fn init(
context: E,
cfg: VariableConfig<T, <Operation<F, K, V> as Read>::Cfg, S, core::num::NonZeroUsize>,
max_size: Option<Location<F>>,
) -> Result<Self, Error<F>> {
crate::qmdb::any::init(context, cfg, max_size).await
}
}
pub mod p256 {
pub type Db<F, E, K, V, H, T, S> = super::Db<F, E, K, V, H, T, 1, S>;
}
pub mod p64k {
pub type Db<F, E, K, V, H, T, S> = super::Db<F, E, K, V, H, T, 2, S>;
}
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
use crate::{
mmr,
qmdb::{
any::{
batch::{DiffEntry, Slot, StagedLoc, lookup_sorted},
ordered::test::{
test_ordered_any_db_basic, test_ordered_any_db_empty,
test_ordered_any_update_collision_edge_case,
},
test::variable_db_config,
},
floor::{Bounded, Proportional},
},
translator::TwoCap,
};
use commonware_cryptography::{Sha256, sha256::Digest};
use commonware_macros::test_traced;
use commonware_math::algebra::Random;
use commonware_parallel::Sequential;
use commonware_runtime::{
Runner as _, Supervisor as _,
buffer::paged::CacheRef,
deterministic::{self, Context},
};
use commonware_utils::{NZU16, NZU64, NZUsize, TestRng, sequence::FixedBytes};
use rand::Rng;
const PAGE_SIZE: u16 = 103;
const PAGE_CACHE_SIZE: usize = 13;
pub(crate) type VarConfig<B = ()> =
VariableConfig<TwoCap, ((), (commonware_codec::RangeCfg<usize>, ())), Sequential, B>;
pub(crate) type AnyTest =
Db<mmr::Family, deterministic::Context, Digest, Vec<u8>, Sha256, TwoCap, Sequential>;
pub(crate) fn create_test_config<B>(seed: u64, init_concurrency: B) -> VarConfig<B> {
let page_cache = CacheRef::new(NZU16!(PAGE_SIZE), NZUsize!(PAGE_CACHE_SIZE));
VariableConfig {
merkle_config: crate::mmr::full::Config {
journal_partition: format!("mmr-journal-{seed}"),
metadata_partition: format!("mmr-metadata-{seed}"),
items_per_blob: NZU64!(12), write_buffer: NZUsize!(64),
replay_buffer: NZUsize!(64),
strategy: Sequential,
page_cache: page_cache.clone(),
},
journal_config: crate::journal::contiguous::variable::Config {
partition: format!("log-journal-{seed}"),
items_per_section: NZU64!(14), write_buffer: NZUsize!(64),
replay_buffer: NZUsize!(64),
compression: None,
codec_config: ((), ((0..=10000).into(), ())),
page_cache,
},
translator: TwoCap,
init_cache: Some(NZUsize!(1024)),
init_buffer: NZUsize!(1 << 21),
init_concurrency,
}
}
pub(crate) async fn create_test_db(mut context: Context) -> AnyTest {
let seed = context.next_u64();
let config = create_test_config(seed, ());
AnyTest::init(context, config, None).await.unwrap()
}
#[test_traced]
fn test_foreign_db_merkleize_rejected() {
deterministic::Runner::default().start(|context| async move {
let db_a = create_test_db(context.child("a")).await;
let db_b = create_test_db(context.child("b")).await;
let seed_a = db_a
.new_batch()
.write(Sha256::hash(&[b"a"]), Some(vec![1]))
.merkleize(&db_a, None, &mut Proportional)
.await
.unwrap();
let db_a = db_a.apply_batch(seed_a).await.unwrap();
let seed_b = db_b
.new_batch()
.write(Sha256::hash(&[b"b"]), Some(vec![2]))
.merkleize(&db_b, None, &mut Proportional)
.await
.unwrap();
let db_b = db_b.apply_batch(seed_b).await.unwrap();
assert_eq!(db_a.bounds().end, db_b.bounds().end);
assert_ne!(db_a.root(), db_b.root());
let batch = db_a.new_batch().write(Sha256::hash(&[b"a"]), Some(vec![3]));
assert!(matches!(
batch.merkleize(&db_b, None, &mut Proportional).await,
Err(Error::StaleBatch)
));
});
}
#[test_traced("WARN")]
fn test_ordered_partitioned_variable_parallel_init_equivalence() {
deterministic::Runner::default().start(|context| async move {
type PartDb<S> = partitioned::Db<
mmr::Family,
deterministic::Context,
Digest,
Vec<u8>,
Sha256,
TwoCap,
1,
S,
>;
fn expected_value(i: u64) -> Option<Vec<u8>> {
if i % 7 == 1 {
None
} else if i.is_multiple_of(3) {
Some(vec![0xAB; (i % 17 + 1) as usize])
} else {
Some(vec![(i % 251) as u8; (i % 40 + 1) as usize])
}
}
let cfg = create_test_config(77, NZUsize!(1));
let db = PartDb::<Sequential>::init(context.child("populate"), cfg, None)
.await
.unwrap();
let mut batch = db.new_batch();
for i in 0u64..500 {
let k = Sha256::hash(&[&i.to_be_bytes()]);
let v = vec![(i % 251) as u8; (i % 40 + 1) as usize];
batch = batch.write(k, Some(v));
}
let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
let db = db.apply_batch(merkleized).await.unwrap();
let db = db.commit().await.unwrap();
let mut batch = db.new_batch();
for i in (0u64..500).step_by(3) {
let k = Sha256::hash(&[&i.to_be_bytes()]);
batch = batch.write(k, Some(vec![0xAB; (i % 17 + 1) as usize]));
}
for i in (1u64..500).step_by(7) {
let k = Sha256::hash(&[&i.to_be_bytes()]);
batch = batch.write(k, None);
}
let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
let db = db.apply_batch(merkleized).await.unwrap();
let db = db.commit().await.unwrap();
let db = db.sync().await.unwrap();
let root = db.root();
drop(db);
for concurrency in [1usize, 4] {
let cfg =
create_test_config(77, core::num::NonZeroUsize::new(concurrency).unwrap());
let ctx = context
.child("reopen")
.with_attribute("concurrency", concurrency);
let db = PartDb::<Sequential>::init(ctx, cfg, None).await.unwrap();
assert_eq!(
db.root(),
root,
"root mismatch at concurrency={concurrency}"
);
for i in 0u64..500 {
let k = Sha256::hash(&[&i.to_be_bytes()]);
assert_eq!(
db.get(&k).await.unwrap(),
expected_value(i),
"value mismatch for key {i}"
);
}
drop(db);
}
});
}
fn to_bytes(i: u64) -> Vec<u8> {
let len = ((i % 13) + 7) as usize;
vec![(i % 255) as u8; len]
}
pub(crate) fn create_test_ops(n: usize) -> Vec<Operation<mmr::Family, Digest, Vec<u8>>> {
create_test_ops_seeded(n, 0)
}
pub(crate) fn create_test_ops_seeded(
n: usize,
seed: u64,
) -> Vec<Operation<mmr::Family, Digest, Vec<u8>>> {
let mut rng = TestRng::new(seed);
let mut prev_key = Digest::random(&mut rng);
let mut ops = Vec::new();
for i in 0..n {
if i % 10 == 0 && i > 0 {
ops.push(Operation::Delete(prev_key));
} else {
let key = Digest::random(&mut rng);
let next_key = Digest::random(&mut rng);
let value = to_bytes(rng.next_u64());
ops.push(Operation::Update(ordered::Update {
key,
value,
next_key,
}));
prev_key = key;
}
}
ops
}
pub(crate) async fn apply_ops(
db: AnyTest,
ops: Vec<Operation<mmr::Family, Digest, Vec<u8>>>,
) -> AnyTest {
let mut batch = db.new_batch();
for op in ops {
match op {
Operation::Update(data) => {
batch = batch.write(data.key, Some(data.value));
}
Operation::Delete(key) => {
batch = batch.write(key, None);
}
Operation::CommitFloor(_, _) => {
panic!("CommitFloor not supported in apply_ops");
}
}
}
let merkleized = batch.merkleize(&db, None, &mut Proportional).await.unwrap();
db.apply_batch(merkleized).await.unwrap()
}
type VariableDb = Db<mmr::Family, Context, FixedBytes<4>, Digest, Sha256, TwoCap, Sequential>;
async fn open_variable_db(context: Context) -> VariableDb {
let cfg = variable_db_config("fixed-bytes-var-partition");
VariableDb::init(context, cfg, None).await.unwrap()
}
#[test_traced("WARN")]
fn test_ordered_any_variable_db_empty() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let db = open_variable_db(context.child("initial")).await;
test_ordered_any_db_empty(context, db, |ctx| Box::pin(open_variable_db(ctx))).await;
});
}
#[test_traced("WARN")]
fn test_ordered_any_variable_db_basic() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let db = open_variable_db(context.child("initial")).await;
test_ordered_any_db_basic(context, db, |ctx| Box::pin(open_variable_db(ctx))).await;
});
}
#[test_traced("WARN")]
fn test_ordered_any_update_collision_edge_case_variable() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let db = open_variable_db(context.child("storage")).await;
test_ordered_any_update_collision_edge_case(db).await;
});
}
#[test_traced("WARN")]
fn test_ordered_any_update_batch_create_between_collisions() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let db = open_variable_db(context.child("storage")).await;
let key1 = FixedBytes::from([0xFFu8, 0xFFu8, 5u8, 5u8]);
let key2 = FixedBytes::from([0xFFu8, 0xFFu8, 6u8, 6u8]);
let key3 = FixedBytes::from([0xFFu8, 0xFFu8, 7u8, 0u8]);
let val = Sha256::fill(1u8);
let merkleized = db
.new_batch()
.write(key1.clone(), Some(val))
.write(key3.clone(), Some(val))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(merkleized).await.unwrap();
assert_eq!(db.get(&key1).await.unwrap().unwrap(), val);
assert!(db.get(&key2).await.unwrap().is_none());
assert_eq!(db.get(&key3).await.unwrap().unwrap(), val);
let merkleized = db
.new_batch()
.write(key2.clone(), Some(val))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(merkleized).await.unwrap();
assert_eq!(db.get(&key1).await.unwrap().unwrap(), val);
assert_eq!(db.get(&key2).await.unwrap().unwrap(), val);
assert_eq!(db.get(&key3).await.unwrap().unwrap(), val);
let span1 = db.get_span(&key1).await.unwrap().unwrap();
assert_eq!(span1.1.next_key, key2);
let span2 = db.get_span(&key2).await.unwrap().unwrap();
assert_eq!(span2.1.next_key, key3);
let span3 = db.get_span(&key3).await.unwrap().unwrap();
assert_eq!(span3.1.next_key, key1);
db.destroy().await.unwrap();
});
}
#[test_traced("WARN")]
fn test_ordered_any_batch_create_delete_prev_links() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let key1 = FixedBytes::from([0x10u8, 0x00, 0x00, 0x00]);
let key2 = FixedBytes::from([0x20u8, 0x00, 0x00, 0x00]);
let key3 = FixedBytes::from([0x30u8, 0x00, 0x00, 0x00]);
let val1 = Sha256::fill(1u8);
let val2 = Sha256::fill(2u8);
let val3 = Sha256::fill(3u8);
let db = open_variable_db(context.child("first")).await;
let merkleized = db
.new_batch()
.write(key1.clone(), Some(val1))
.write(key3.clone(), Some(val3))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(merkleized).await.unwrap();
let merkleized = db
.new_batch()
.write(key1.clone(), None)
.write(key2.clone(), Some(val2))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(merkleized).await.unwrap();
assert!(db.get(&key1).await.unwrap().is_none());
assert_eq!(db.get(&key2).await.unwrap(), Some(val2));
assert_eq!(db.get(&key3).await.unwrap(), Some(val3));
let span2 = db.get_span(&key2).await.unwrap().unwrap();
assert_eq!(span2.1.next_key, key3);
let span3 = db.get_span(&key3).await.unwrap().unwrap();
assert_eq!(span3.1.next_key, key2);
db.destroy().await.unwrap();
let db = open_variable_db(context.child("second")).await;
let merkleized = db
.new_batch()
.write(key1.clone(), Some(val1))
.write(key3.clone(), Some(val3))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(merkleized).await.unwrap();
let merkleized = db
.new_batch()
.write(key2.clone(), Some(val2))
.write(key3.clone(), None)
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(merkleized).await.unwrap();
assert_eq!(db.get(&key1).await.unwrap(), Some(val1));
assert_eq!(db.get(&key2).await.unwrap(), Some(val2));
assert!(db.get(&key3).await.unwrap().is_none());
let span1 = db.get_span(&key1).await.unwrap().unwrap();
assert_eq!(span1.1.next_key, key2);
let span2 = db.get_span(&key2).await.unwrap().unwrap();
assert_eq!(span2.1.next_key, key1);
db.destroy().await.unwrap();
});
}
#[test_traced("WARN")]
fn ordered_variable_staged_ancestor_applied_before_merkleize() {
deterministic::Runner::default().start(|context| async move {
let db = create_test_db(context.child("staged_ancestor")).await;
let key = |i: u64| Sha256::hash(&[&i.to_be_bytes()]);
let mut seed = db.new_batch();
for i in 0..8u64 {
seed = seed.write(key(i), Some(to_bytes(i)));
}
let seed = seed.merkleize(&db, None, &mut Proportional).await.unwrap();
let db = db.apply_batch(seed).await.unwrap().commit().await.unwrap();
let grandparent = db
.new_batch()
.write(key(0), Some(to_bytes(1_000)))
.write(key(100), Some(to_bytes(1_001)))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let parent = grandparent
.new_batch::<Sha256>()
.write(key(1), Some(to_bytes(1_002)))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let read_keys = [key(0), key(100)];
let keys: Vec<&Digest> = read_keys.iter().collect();
let (values, staged) = parent
.new_batch::<Sha256>()
.stage(&keys, &db)
.await
.unwrap();
assert_eq!(values, vec![Some(to_bytes(1_000)), Some(to_bytes(1_001))]);
for (resolved, key) in staged.resolved_locs().into_iter().zip(&read_keys) {
let written =
lookup_sorted(grandparent.diff.as_slice(), key).and_then(DiffEntry::loc);
assert!(
matches!(
resolved,
Some(StagedLoc::Ancestor { loc, .. }) if Some(loc) == written
),
"a staged read must resolve in the grandparent's diff"
);
}
let db = db.apply_batch(grandparent).await.unwrap();
let slots = vec![Slot::Put(to_bytes(2_000)), Slot::Put(to_bytes(2_001))];
let staged = staged
.merkleize(slots, Vec::new(), None, &db, &mut Proportional)
.await
.unwrap();
let explicit_root = parent
.new_batch::<Sha256>()
.write(key(0), Some(to_bytes(2_000)))
.write(key(100), Some(to_bytes(2_001)))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap()
.root();
assert_eq!(staged.root(), explicit_root);
let db = db.apply_batch(parent).await.unwrap();
let db = db.apply_batch(staged).await.unwrap();
let db = db.commit().await.unwrap();
assert_eq!(db.get(&key(0)).await.unwrap(), Some(to_bytes(2_000)));
assert_eq!(db.get(&key(100)).await.unwrap(), Some(to_bytes(2_001)));
assert_eq!(db.get(&key(1)).await.unwrap(), Some(to_bytes(1_002)));
db.destroy().await.unwrap();
});
}
#[test_traced("WARN")]
fn ordered_variable_staged_ancestor_alive_through_apply() {
deterministic::Runner::default().start(|context| async move {
for apply_ancestors_first in [false, true] {
let label = if apply_ancestors_first {
"staged_ancestor_alive_separate"
} else {
"staged_ancestor_alive_combined"
};
let db = create_test_db(context.child(label)).await;
let key = |i: u64| Sha256::hash(&[&i.to_be_bytes()]);
let mut seed = db.new_batch();
for i in 0..64u64 {
seed = seed.write(key(i), Some(to_bytes(i)));
}
let seed = seed.merkleize(&db, None, &mut Proportional).await.unwrap();
let db = db.apply_batch(seed).await.unwrap().commit().await.unwrap();
let grandparent = db
.new_batch()
.write(key(0), Some(to_bytes(1_000)))
.write(key(100), Some(to_bytes(1_001)))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let parent = grandparent
.new_batch::<Sha256>()
.write(key(1), Some(to_bytes(1_002)))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let read_keys = [key(0), key(100), key(1), key(3)];
let keys: Vec<&Digest> = read_keys.iter().collect();
let (values, staged) = parent
.new_batch::<Sha256>()
.stage(&keys, &db)
.await
.unwrap();
assert_eq!(
values,
vec![
Some(to_bytes(1_000)),
Some(to_bytes(1_001)),
Some(to_bytes(1_002)),
Some(to_bytes(3)),
]
);
let resolved = staged.resolved_locs();
for (slot, written) in [
lookup_sorted(grandparent.diff.as_slice(), &key(0)),
lookup_sorted(grandparent.diff.as_slice(), &key(100)),
lookup_sorted(parent.diff.as_slice(), &key(1)),
]
.into_iter()
.enumerate()
{
let written = written.and_then(DiffEntry::loc);
assert!(
matches!(
resolved[slot],
Some(StagedLoc::Ancestor { loc, .. }) if Some(loc) == written
),
"slot {slot} must resolve in the diff of the ancestor that wrote it"
);
}
assert!(
matches!(resolved[3], Some(StagedLoc::Committed(_))),
"a key no ancestor wrote must resolve in the database"
);
let slots = vec![
Slot::Put(to_bytes(2_000)),
Slot::Put(to_bytes(2_001)),
Slot::Delete,
Slot::Put(to_bytes(2_002)),
];
let staged = staged
.merkleize(slots, Vec::new(), None, &db, &mut Proportional)
.await
.unwrap();
let explicit_root = parent
.new_batch::<Sha256>()
.write(key(0), Some(to_bytes(2_000)))
.write(key(100), Some(to_bytes(2_001)))
.write(key(1), None)
.write(key(3), Some(to_bytes(2_002)))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap()
.root();
assert_eq!(staged.root(), explicit_root);
let db = if apply_ancestors_first {
let db = db.apply_batch(grandparent).await.unwrap();
db.apply_batch(parent).await.unwrap()
} else {
db
};
let db = db.apply_batch(staged).await.unwrap();
let db = db.commit().await.unwrap();
assert_eq!(db.get(&key(0)).await.unwrap(), Some(to_bytes(2_000)));
assert_eq!(db.get(&key(100)).await.unwrap(), Some(to_bytes(2_001)));
assert_eq!(db.get(&key(1)).await.unwrap(), None);
assert_eq!(db.get(&key(3)).await.unwrap(), Some(to_bytes(2_002)));
db.destroy().await.unwrap();
}
});
}
fn is_send<T: Send>(_: T) {}
#[allow(dead_code)]
fn assert_non_trait_futures_are_send(db: &mut AnyTest, key: Digest) {
is_send(db.get_all(&key));
is_send(db.get_with_loc(&key));
is_send(db.get_span(&key));
let mut policy = Bounded {
entries: 1,
skips: 1,
};
is_send(db.new_batch().merkleize(db, None, &mut policy));
is_send(async move {
let (_, staged) = db.new_batch().stage(&[&key], db).await?;
staged
.merkleize(vec![Slot::Keep], Vec::new(), None, db, &mut policy)
.await
});
}
#[test_traced("WARN")]
fn test_ordered_sequential_commit_basic() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let db = create_test_db(context).await;
let db = apply_ops(db, create_test_ops(10)).await;
let db = db.commit().await.unwrap();
let base = db.to_batch();
let key_a = Digest::random(TestRng::new(800));
let val_a = vec![1u8; 10];
let parent_batch = base
.new_batch::<Sha256>()
.write(key_a, Some(val_a.clone()))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let key_b = Digest::random(TestRng::new(801));
let val_b = vec![2u8; 10];
let child_batch = parent_batch
.new_batch::<Sha256>()
.write(key_b, Some(val_b.clone()))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(parent_batch).await.unwrap();
let db = db.commit().await.unwrap();
let db = db.apply_batch(child_batch).await.unwrap();
let db = db.commit().await.unwrap();
assert_eq!(db.get(&key_a).await.unwrap().unwrap(), val_a);
assert_eq!(db.get(&key_b).await.unwrap().unwrap(), val_b);
db.destroy().await.unwrap();
});
}
#[test_traced("WARN")]
fn test_ordered_sequential_commit_delete_after_insert() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let db = create_test_db(context).await;
let db = apply_ops(db, create_test_ops(5)).await;
let db = db.commit().await.unwrap();
let base = db.to_batch();
let key_x = Digest::random(TestRng::new(810));
let val_x = vec![10u8; 8];
let parent_batch = base
.new_batch::<Sha256>()
.write(key_x, Some(val_x.clone()))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let child_batch = parent_batch
.new_batch::<Sha256>()
.write(key_x, None)
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(parent_batch).await.unwrap();
let db = db.commit().await.unwrap();
assert_eq!(db.get(&key_x).await.unwrap().unwrap(), val_x);
let db = db.apply_batch(child_batch).await.unwrap();
let db = db.commit().await.unwrap();
assert!(db.get(&key_x).await.unwrap().is_none());
db.destroy().await.unwrap();
});
}
#[test_traced("WARN")]
fn test_ordered_sequential_commit_overlapping_keys() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let db = create_test_db(context).await;
let db = apply_ops(db, create_test_ops(5)).await;
let db = db.commit().await.unwrap();
let base = db.to_batch();
let key_x = Digest::random(TestRng::new(820));
let val_a = vec![10u8; 8];
let parent_batch = base
.new_batch::<Sha256>()
.write(key_x, Some(val_a.clone()))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let val_b = vec![20u8; 8];
let child_batch = parent_batch
.new_batch::<Sha256>()
.write(key_x, Some(val_b.clone()))
.merkleize(&db, None, &mut Proportional)
.await
.unwrap();
let db = db.apply_batch(parent_batch).await.unwrap();
let db = db.commit().await.unwrap();
assert_eq!(db.get(&key_x).await.unwrap().unwrap(), val_a);
let db = db.apply_batch(child_batch).await.unwrap();
let db = db.commit().await.unwrap();
assert_eq!(db.get(&key_x).await.unwrap().unwrap(), val_b);
db.destroy().await.unwrap();
});
}
mod from_sync_testable {
use super::*;
use crate::{
merkle::{Family, Location, full::Merkle},
qmdb::any::sync::tests::FromSyncTestable,
};
use futures::future::join_all;
impl<F: Family> FromSyncTestable
for Db<F, deterministic::Context, Digest, Vec<u8>, Sha256, TwoCap, Sequential>
{
type Merkle = Merkle<F, deterministic::Context, Digest, Sequential>;
fn into_log_components(self) -> (Self::Merkle, Self::Journal) {
(self.log.merkle, self.log.journal)
}
async fn pinned_nodes_at(&self, loc: Location<F>) -> Vec<Digest> {
join_all(F::nodes_to_pin(loc).map(|p| self.log.merkle.get_node(p)))
.await
.into_iter()
.map(|n| n.unwrap().unwrap())
.collect()
}
}
}
}