use crate::{
journal::contiguous::{Contiguous, Many},
merkle::{Family, Location},
};
use commonware_utils::range::NonEmptyRange;
use std::future::Future;
pub trait Journal<F: Family>: Sized + Send {
type Context;
type Config: Sync;
type Op: Send + Sync;
type Error: std::error::Error + Send + 'static + Into<crate::qmdb::Error<F>>;
fn new(
context: Self::Context,
config: Self::Config,
range: NonEmptyRange<Location<F>>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send;
fn resize(self, start: Location<F>) -> impl Future<Output = Result<Self, Self::Error>> + Send;
fn sync(self) -> impl Future<Output = Result<Self, Self::Error>> + Send;
fn size(&self) -> u64;
fn append(self, ops: &[Self::Op]) -> impl Future<Output = Result<Self, Self::Error>> + Send;
}
impl<F, E, V> Journal<F> for crate::journal::contiguous::variable::Journal<E, V>
where
F: Family,
E: crate::Context,
V: commonware_codec::CodecShared,
{
type Context = E;
type Config = crate::journal::contiguous::variable::Config<V::Cfg>;
type Op = V;
type Error = crate::journal::Error;
async fn new(
context: Self::Context,
config: Self::Config,
range: NonEmptyRange<Location<F>>,
) -> Result<Self, Self::Error> {
Self::init_sync(context, config.clone(), *range.start()..*range.end()).await
}
async fn resize(self, start: Location<F>) -> Result<Self, Self::Error> {
if Contiguous::bounds(&self).end <= *start {
self.clear_to_size(*start).await
} else {
let (journal, _) = self.prune(*start).await?;
Ok(journal)
}
}
async fn sync(self) -> Result<Self, Self::Error> {
Self::sync(self).await
}
fn size(&self) -> u64 {
Contiguous::bounds(self).end
}
async fn append(self, ops: &[Self::Op]) -> Result<Self, Self::Error> {
let (journal, _) = self.append_many(Many::Flat(ops)).await?;
Ok(journal)
}
}
impl<F, E, A> Journal<F> for crate::journal::contiguous::fixed::Journal<E, A>
where
F: Family,
E: crate::Context,
A: commonware_codec::CodecFixedShared,
{
type Context = E;
type Config = crate::journal::contiguous::fixed::Config;
type Op = A;
type Error = crate::journal::Error;
async fn new(
context: Self::Context,
config: Self::Config,
range: NonEmptyRange<Location<F>>,
) -> Result<Self, Self::Error> {
let mut journal = Self::init(context, config).await?;
let size = Contiguous::bounds(&journal).end;
if size == 0 && *range.start() == 0 {
return Ok(journal);
}
let bounds = journal.bounds();
if bounds.start > *range.start() {
return journal.clear_to_size(*range.start()).await;
}
if size > *range.end() {
journal = journal.rewind(*range.end()).await?;
}
if size <= *range.start() {
journal = journal.clear_to_size(*range.start()).await?;
} else {
(journal, _) = journal.prune(*range.start()).await?;
}
Ok(journal)
}
async fn resize(self, start: Location<F>) -> Result<Self, Self::Error> {
if Contiguous::bounds(&self).end <= *start {
self.clear_to_size(*start).await
} else {
let (journal, _) = self.prune(*start).await?;
Ok(journal)
}
}
async fn sync(self) -> Result<Self, Self::Error> {
Self::sync(self).await
}
fn size(&self) -> u64 {
Contiguous::bounds(self).end
}
async fn append(self, ops: &[Self::Op]) -> Result<Self, Self::Error> {
let (journal, _) = self.append_many(Many::Flat(ops)).await?;
Ok(journal)
}
}
pub struct Memory<F: Family, E, Op> {
start: Location<F>,
ops: Vec<Op>,
_context: std::marker::PhantomData<fn() -> E>,
}
impl<F: Family, E, Op> Memory<F, E, Op> {
pub(crate) fn into_parts(self) -> (Location<F>, Vec<Op>) {
(self.start, self.ops)
}
}
impl<F, E, Op> Journal<F> for Memory<F, E, Op>
where
F: Family,
E: Send,
Op: Clone + Send + Sync,
{
type Context = E;
type Config = ();
type Op = Op;
type Error = crate::qmdb::Error<F>;
async fn new(
_context: Self::Context,
_config: Self::Config,
range: NonEmptyRange<Location<F>>,
) -> Result<Self, Self::Error> {
Ok(Self {
start: range.start(),
ops: Vec::new(),
_context: std::marker::PhantomData,
})
}
async fn resize(mut self, start: Location<F>) -> Result<Self, Self::Error> {
if start < self.start || *start >= self.size() {
self.start = start;
self.ops.clear();
} else {
self.ops.drain(..(*start - *self.start) as usize);
self.start = start;
}
Ok(self)
}
async fn sync(self) -> Result<Self, Self::Error> {
Ok(self)
}
fn size(&self) -> u64 {
*self.start + self.ops.len() as u64
}
async fn append(mut self, ops: &[Self::Op]) -> Result<Self, Self::Error> {
self.ops.extend_from_slice(ops);
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::journal::contiguous::{fixed, variable};
use commonware_cryptography::sha256::Digest;
use commonware_macros::test_traced;
use commonware_runtime::{
Blob, BufferPooler, Runner, Storage, Supervisor as _, buffer::paged::CacheRef,
deterministic,
};
use commonware_utils::{NZU16, NZU64, NZUsize, non_empty_range};
type FixedJournal = fixed::Journal<deterministic::Context, Digest>;
type VariableJournal = variable::Journal<deterministic::Context, u64>;
type F = crate::merkle::mmr::Family;
fn test_cfg(pooler: &impl BufferPooler) -> fixed::Config {
fixed::Config {
partition: "sync-journal-test".into(),
items_per_blob: NZU64!(5),
page_cache: CacheRef::from_pooler(pooler, NZU16!(44), NZUsize!(3)),
write_buffer: NZUsize!(2048),
replay_buffer: NZUsize!(2048),
}
}
fn variable_test_cfg(pooler: &impl BufferPooler) -> variable::Config<()> {
variable::Config {
partition: "variable-sync-journal-test".into(),
items_per_section: NZU64!(5),
compression: None,
codec_config: (),
write_buffer: NZUsize!(2048),
replay_buffer: NZUsize!(2048),
page_cache: CacheRef::from_pooler(pooler, NZU16!(44), NZUsize!(3)),
}
}
#[test_traced]
fn test_memory_journal() {
type Mem = Memory<F, (), u64>;
deterministic::Runner::default().start(|_context| async move {
let range = non_empty_range!(Location::new(10), Location::new(20));
let journal = <Mem as Journal<F>>::new((), (), range.clone())
.await
.unwrap();
assert_eq!(journal.size(), 10);
let journal = journal.append(&[1, 2, 3]).await.unwrap();
assert_eq!(journal.size(), 13);
let journal = journal.resize(Location::new(12)).await.unwrap();
assert_eq!(journal.size(), 13);
let (start, ops) = journal.into_parts();
assert_eq!(start, Location::new(12));
assert_eq!(ops, vec![3]);
let journal = <Mem as Journal<F>>::new((), (), range.clone())
.await
.unwrap();
let journal = journal.append(&[1, 2]).await.unwrap();
let journal = journal.resize(Location::new(15)).await.unwrap();
assert_eq!(journal.size(), 15);
let (start, ops) = journal.into_parts();
assert_eq!(start, Location::new(15));
assert!(ops.is_empty());
let journal = <Mem as Journal<F>>::new((), (), range).await.unwrap();
let journal = journal.append(&[1]).await.unwrap();
let journal = journal.resize(Location::new(5)).await.unwrap();
assert_eq!(journal.size(), 5);
let (start, ops) = journal.into_parts();
assert_eq!(start, Location::new(5));
assert!(ops.is_empty());
});
}
#[test_traced]
fn test_sync_journal_new_recovers_from_stale_clear_to_size() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let cfg = test_cfg(&context);
let journal = FixedJournal::init_at_size(context.child("setup"), cfg.clone(), 9)
.await
.unwrap();
let journal = journal.sync().await.unwrap();
drop(journal);
let blob_part = format!("{}-blobs", cfg.partition);
context.remove(&blob_part, None).await.unwrap();
let (blob, _) = context.open(&blob_part, &1u64.to_be_bytes()).await.unwrap();
blob.sync().await.unwrap();
let range = non_empty_range!(
crate::merkle::Location::<F>::new(7),
crate::merkle::Location::<F>::new(20)
);
let journal = <FixedJournal as Journal<F>>::new(context.child("sync"), cfg, range)
.await
.unwrap();
let size = Contiguous::bounds(&journal).end;
assert_eq!(size, 7);
let bounds = journal.bounds();
assert!(bounds.is_empty());
assert_eq!(bounds.start, 7);
journal.destroy().await.unwrap();
});
}
#[test_traced]
fn test_sync_journal_new_stale_empty_position_beyond_range_end() {
let executor = deterministic::Runner::default();
executor.start(|context| async move {
let cfg = test_cfg(&context);
let journal = FixedJournal::init_at_size(context.child("setup"), cfg.clone(), 30)
.await
.unwrap();
let journal = journal.sync().await.unwrap();
drop(journal);
let range = non_empty_range!(
crate::merkle::Location::<F>::new(7),
crate::merkle::Location::<F>::new(20)
);
let journal = <FixedJournal as Journal<F>>::new(context.child("sync"), cfg, range)
.await
.unwrap();
let size = Contiguous::bounds(&journal).end;
assert_eq!(size, 7);
let bounds = journal.bounds();
assert!(bounds.is_empty());
assert_eq!(bounds.start, 7);
journal.destroy().await.unwrap();
});
}
#[test_traced]
fn test_fixed_sync_journal_new_rewinds_ahead_and_discards_pruned_progress() {
deterministic::Runner::default().start(|context| async move {
let cfg = test_cfg(&context);
let mut journal = FixedJournal::init(context.child("setup"), cfg.clone())
.await
.unwrap();
for value in 0..30u8 {
(journal, _) = journal.append(&Digest([value; 32])).await.unwrap();
}
let journal = journal.sync().await.unwrap();
drop(journal);
let range = non_empty_range!(Location::<F>::new(7), Location::<F>::new(20));
let journal =
<FixedJournal as Journal<F>>::new(context.child("sync"), cfg.clone(), range)
.await
.unwrap();
assert_eq!(journal.bounds(), 5..20);
for value in 7..20u8 {
assert_eq!(
journal.read(value.into()).await.unwrap(),
Digest([value; 32])
);
}
journal.destroy().await.unwrap();
let mut journal = FixedJournal::init(context.child("pruned_setup"), cfg.clone())
.await
.unwrap();
for value in 0..50u8 {
(journal, _) = journal.append(&Digest([value; 32])).await.unwrap();
}
let journal = <FixedJournal as Journal<F>>::resize(journal, Location::new(40))
.await
.unwrap();
let journal = journal.sync().await.unwrap();
assert!(journal.bounds().start > 7);
drop(journal);
let range = non_empty_range!(Location::<F>::new(7), Location::<F>::new(60));
let journal =
<FixedJournal as Journal<F>>::new(context.child("pruned_sync"), cfg, range)
.await
.unwrap();
assert_eq!(journal.bounds(), 7..7);
journal.destroy().await.unwrap();
});
}
#[test_traced]
fn test_variable_sync_journal_new_rewinds_ahead_and_discards_pruned_progress() {
deterministic::Runner::default().start(|context| async move {
let cfg = variable_test_cfg(&context);
let mut journal = VariableJournal::init(context.child("setup"), cfg.clone())
.await
.unwrap();
for value in 0..30u64 {
(journal, _) = journal.append(&value).await.unwrap();
}
let journal = journal.sync().await.unwrap();
drop(journal);
let range = non_empty_range!(Location::<F>::new(7), Location::<F>::new(20));
let journal =
<VariableJournal as Journal<F>>::new(context.child("sync"), cfg.clone(), range)
.await
.unwrap();
assert_eq!(journal.bounds(), 5..20);
for value in 7..20u64 {
assert_eq!(journal.read(value).await.unwrap(), value);
}
journal.destroy().await.unwrap();
let mut journal = VariableJournal::init(context.child("pruned_setup"), cfg.clone())
.await
.unwrap();
for value in 0..50u64 {
(journal, _) = journal.append(&value).await.unwrap();
}
let journal = <VariableJournal as Journal<F>>::resize(journal, Location::new(40))
.await
.unwrap();
let journal = journal.sync().await.unwrap();
assert!(journal.bounds().start > 7);
drop(journal);
let range = non_empty_range!(Location::<F>::new(7), Location::<F>::new(60));
let journal =
<VariableJournal as Journal<F>>::new(context.child("pruned_sync"), cfg, range)
.await
.unwrap();
assert_eq!(journal.bounds(), 7..7);
journal.destroy().await.unwrap();
});
}
}