use crate::{
Context,
merkle::{Family, Location, full},
qmdb::sync::{Journal, Target},
translator::Translator,
};
use commonware_cryptography::{Digest, Hasher};
use commonware_parallel::Strategy;
use commonware_utils::range::NonEmptyRange;
use std::{future::Future, num::NonZeroU64};
pub trait Config {
type JournalConfig;
fn journal_config(&self) -> Self::JournalConfig;
}
impl<T: Translator, J: Clone, S: Strategy> Config for crate::qmdb::any::Config<T, J, S> {
type JournalConfig = J;
fn journal_config(&self) -> Self::JournalConfig {
self.journal_config.clone()
}
}
impl<T: Translator, C: Clone, S: Strategy> Config for crate::qmdb::immutable::Config<T, C, S> {
type JournalConfig = C;
fn journal_config(&self) -> Self::JournalConfig {
self.log.clone()
}
}
impl<J: Clone, S: Strategy> Config for crate::qmdb::keyless::Config<J, S> {
type JournalConfig = J;
fn journal_config(&self) -> Self::JournalConfig {
self.log.clone()
}
}
impl<C: Clone + Send + Sync + 'static, S: Strategy> Config for crate::qmdb::compact::Config<C, S> {
type JournalConfig = ();
fn journal_config(&self) -> Self::JournalConfig {}
}
pub trait Database: Sized + Send {
type Family: Family;
type Op: Send + Sync;
type Journal: Journal<Self::Family, Context = Self::Context, Op = Self::Op>;
type Config: Config<JournalConfig = <Self::Journal as Journal<Self::Family>>::Config>;
type Digest: Digest;
type Context: commonware_runtime::Storage
+ commonware_runtime::Clock
+ commonware_runtime::Metrics;
type Hasher: commonware_cryptography::Hasher<Digest = Self::Digest>;
fn from_sync_result(
context: Self::Context,
config: Self::Config,
journal: Self::Journal,
pinned_nodes: Option<Vec<Self::Digest>>,
range: NonEmptyRange<Location<Self::Family>>,
apply_batch_size: NonZeroU64,
) -> impl Future<Output = Result<Self, crate::qmdb::Error<Self::Family>>> + Send;
fn persist_sync_result(
self,
) -> impl Future<Output = Result<Self, crate::qmdb::Error<Self::Family>>> + Send;
fn local_pinned_nodes(
context: Self::Context,
config: &Self::Config,
target: &crate::qmdb::sync::Target<Self::Family, Self::Digest>,
journal: &Self::Journal,
) -> impl Future<Output = Result<Option<Vec<Self::Digest>>, crate::qmdb::Error<Self::Family>>> + Send;
fn root(&self) -> Self::Digest;
}
pub(crate) fn journal_covers_range<F: Family>(
bounds: std::ops::Range<u64>,
range: &NonEmptyRange<Location<F>>,
) -> bool {
Location::new(bounds.start) <= range.start() && Location::new(bounds.end) == range.end()
}
pub(crate) async fn local_pinned_nodes<F, E, H, S>(
context: E,
config: full::Config<S>,
target: &Target<F, H::Digest>,
inactivity_floor: Location<F>,
) -> Result<Option<Vec<H::Digest>>, crate::qmdb::Error<F>>
where
F: Family,
E: Context,
H: Hasher,
S: Strategy,
{
let hasher = crate::qmdb::hasher::<H>();
let merkle = match full::Merkle::<F, _, _, S>::init(context, &hasher, config).await {
Ok(merkle) => merkle,
Err(crate::merkle::Error::MissingNode(_)) => return Ok(None),
Err(err) => return Err(err.into()),
};
let bounds = merkle.bounds();
if bounds.start > target.range.start() || bounds.end != target.range.end() {
return Ok(None);
}
let inactive_peaks = F::inactive_peaks(target.range.end(), inactivity_floor);
if merkle.root(&hasher, inactive_peaks)? != target.root {
return Ok(None);
}
merkle
.pinned_nodes_at(target.range.start())
.await
.map(Some)
.map_err(Into::into)
}
#[cfg(test)]
mod tests {
use super::{journal_covers_range, local_pinned_nodes};
use crate::{
journal::contiguous::fixed,
merkle::{Location, Position, full, mmr::Family as MmrFamily},
qmdb::sync::Target,
};
use commonware_cryptography::{Sha256, sha256::Digest};
use commonware_parallel::Sequential;
use commonware_runtime::{
BufferPooler, Runner as _, Supervisor as _, buffer::paged::CacheRef, deterministic,
};
use commonware_utils::{NZU16, NZU64, NZUsize, non_empty_range, range::NonEmptyRange};
fn merkle_config(pooler: &impl BufferPooler) -> full::Config<Sequential> {
full::Config {
journal_partition: "local-pins-journal".into(),
metadata_partition: "local-pins-metadata".into(),
items_per_blob: NZU64!(7),
write_buffer: NZUsize!(1024),
replay_buffer: NZUsize!(1024),
strategy: Sequential,
page_cache: CacheRef::from_pooler(pooler, NZU16!(111), NZUsize!(5)),
}
}
#[test]
fn test_journal_covers_range() {
let range: NonEmptyRange<Location<MmrFamily>> =
non_empty_range!(Location::new(10), Location::new(20));
assert!(journal_covers_range(10..20, &range));
assert!(journal_covers_range(5..20, &range));
assert!(!journal_covers_range(11..20, &range));
assert!(!journal_covers_range(10..19, &range));
assert!(!journal_covers_range(10..21, &range));
assert!(!journal_covers_range(0..0, &range));
}
#[test]
fn local_pinned_nodes_treats_interrupted_reset_as_unavailable() {
deterministic::Runner::default().start(|context| async move {
let hasher = crate::qmdb::hasher::<Sha256>();
let config = merkle_config(&context);
let mut merkle = full::Merkle::<MmrFamily, _, Digest, Sequential>::init(
context.child("init"),
&hasher,
config.clone(),
)
.await
.unwrap();
let mut batch = merkle.new_batch();
for i in 0u64..50 {
batch = batch.add(&hasher, &i.to_be_bytes());
}
let batch = merkle.with_mem(|mem| batch.merkleize(mem, &hasher));
merkle = merkle.apply_batch(&batch).unwrap();
let merkle = merkle.sync().await.unwrap();
let merkle = merkle.prune(Location::new(30)).await.unwrap();
let merkle = merkle.sync().await.unwrap();
drop(merkle);
let restart = Location::new(7);
let journal_config = fixed::Config {
partition: config.journal_partition.clone(),
items_per_blob: config.items_per_blob,
page_cache: config.page_cache.clone(),
write_buffer: config.write_buffer,
replay_buffer: config.replay_buffer,
};
let journal = fixed::Journal::<_, Digest>::init(
context.child("interrupted_reset"),
journal_config,
)
.await
.unwrap();
let reset_pos = Position::<MmrFamily>::try_from(restart).unwrap();
let journal = journal.clear_to_size(*reset_pos).await.unwrap();
drop(journal);
let target = Target {
root: Digest::from([0; 32]),
range: non_empty_range!(restart, Location::new(20)),
};
let pinned = local_pinned_nodes::<MmrFamily, _, Sha256, Sequential>(
context.child("local_pins"),
config,
&target,
restart,
)
.await
.unwrap();
assert!(pinned.is_none());
});
}
}