//! Authenticated databases that provides succinct proofs of _any_ value ever associated with //! a key. The submodules provide two classes of variants, one specialized for fixed-size values and //! the other allowing variable-size values. use crate::{ journal::{ authenticated, contiguous::fixed::{Config as JConfig, Journal}, }, mmr::{journaled::Config as MmrConfig, mem::Clean, Location}, qmdb::{ operation::Committable, store::{CleanStore, DirtyStore}, Error, }, translator::Translator, }; use commonware_codec::CodecFixed; use commonware_cryptography::{DigestOf, Hasher}; use commonware_runtime::{buffer::PoolRef, Clock, Metrics, Storage, ThreadPool}; use commonware_utils::Array; use std::{ future::Future, num::{NonZeroU64, NonZeroUsize}, ops::Range, }; pub(crate) mod db; mod operation; mod value; pub(crate) use value::{FixedValue, ValueEncoding, VariableValue}; mod ext; pub mod ordered; pub mod unordered; pub use ext::AnyExt; /// Extension trait for "Any" QMDBs in a clean (merkleized) state. pub trait CleanAny: CleanStore> { /// The key type for this database. type Key: Array; /// Get the value for a given key, or None if it has no value. fn get(&self, key: &Self::Key) -> impl Future, Error>>; /// Commit pending operations to the database, ensuring durability. /// Returns the location range of committed operations. fn commit( &mut self, metadata: Option, ) -> impl Future, Error>>; /// Sync all database state to disk. fn sync(&mut self) -> impl Future>; /// Prune historical operations prior to `prune_loc`. fn prune(&mut self, prune_loc: Location) -> impl Future>; /// Close the db. Uncommitted operations will be lost or rolled back on restart. fn close(self) -> impl Future>; /// Destroy the db, removing all data from disk. fn destroy(self) -> impl Future>; } /// Extension trait for "Any" QMDBs in a dirty (deferred merkleization) state. pub trait DirtyAny: DirtyStore { /// The key type for this database. type Key: Array; /// Get the value for a given key, or None if it has no value. fn get(&self, key: &Self::Key) -> impl Future, Error>>; /// Update `key` to have value `value`. Subject to rollback until next `commit`. fn update( &mut self, key: Self::Key, value: Self::Value, ) -> impl Future>; /// Create a new key-value pair. Returns true if created, false if key already existed. /// Subject to rollback until next `commit`. fn create( &mut self, key: Self::Key, value: Self::Value, ) -> impl Future>; /// Delete `key` and its value. Returns true if deleted, false if already inactive. /// Subject to rollback until next `commit`. fn delete(&mut self, key: Self::Key) -> impl Future>; } /// Configuration for an `Any` authenticated db with fixed-size values. #[derive(Clone)] pub struct FixedConfig { /// The name of the [Storage] partition used for the MMR's backing journal. pub mmr_journal_partition: String, /// The items per blob configuration value used by the MMR journal. pub mmr_items_per_blob: NonZeroU64, /// The size of the write buffer to use for each blob in the MMR journal. pub mmr_write_buffer: NonZeroUsize, /// The name of the [Storage] partition used for the MMR's metadata. pub mmr_metadata_partition: String, /// The name of the [Storage] partition used to persist the (pruned) log of operations. pub log_journal_partition: String, /// The items per blob configuration value used by the log journal. pub log_items_per_blob: NonZeroU64, /// The size of the write buffer to use for each blob in the log journal. pub log_write_buffer: NonZeroUsize, /// The translator used by the compressed index. pub translator: T, /// An optional thread pool to use for parallelizing batch operations. pub thread_pool: Option, /// The buffer pool to use for caching data. pub buffer_pool: PoolRef, } /// Configuration for an `Any` authenticated db with variable-sized values. #[derive(Clone)] pub struct VariableConfig { /// The name of the [Storage] partition used for the MMR's backing journal. pub mmr_journal_partition: String, /// The items per blob configuration value used by the MMR journal. pub mmr_items_per_blob: NonZeroU64, /// The size of the write buffer to use for each blob in the MMR journal. pub mmr_write_buffer: NonZeroUsize, /// The name of the [Storage] partition used for the MMR's metadata. pub mmr_metadata_partition: String, /// The name of the [Storage] partition used to persist the log of operations. pub log_partition: String, /// The size of the write buffer to use for each blob in the log journal. pub log_write_buffer: NonZeroUsize, /// Optional compression level (using `zstd`) to apply to log data before storing. pub log_compression: Option, /// The codec configuration to use for encoding and decoding log items. pub log_codec_config: C, /// The number of items to put in each blob of the journal. pub log_items_per_blob: NonZeroU64, /// The translator used by the compressed index. pub translator: T, /// An optional thread pool to use for parallelizing batch operations. pub thread_pool: Option, /// The buffer pool to use for caching data. pub buffer_pool: PoolRef, } type AuthenticatedLog>> = authenticated::Journal, H, S>; /// Initialize the authenticated log from the given config, returning it along with the inactivity /// floor specified by the last commit. pub(crate) async fn init_fixed_authenticated_log< E: Storage + Clock + Metrics, O: Committable + CodecFixed, H: Hasher, T: Translator, >( context: E, cfg: FixedConfig, ) -> Result, Error> { let mmr_config = MmrConfig { journal_partition: cfg.mmr_journal_partition, metadata_partition: cfg.mmr_metadata_partition, items_per_blob: cfg.mmr_items_per_blob, write_buffer: cfg.mmr_write_buffer, thread_pool: cfg.thread_pool, buffer_pool: cfg.buffer_pool.clone(), }; let journal_config = JConfig { partition: cfg.log_journal_partition, items_per_blob: cfg.log_items_per_blob, write_buffer: cfg.log_write_buffer, buffer_pool: cfg.buffer_pool, }; let log = AuthenticatedLog::new( context.with_label("log"), mmr_config, journal_config, O::is_commit, ) .await?; Ok(log) } #[cfg(test)] mod test { use super::*; use crate::{ qmdb::any::{FixedConfig, VariableConfig}, translator::TwoCap, }; use commonware_utils::{NZUsize, NZU64}; // Janky page & cache sizes to exercise boundary conditions. const PAGE_SIZE: usize = 101; const PAGE_CACHE_SIZE: usize = 11; pub(super) fn fixed_db_config(suffix: &str) -> FixedConfig { FixedConfig { mmr_journal_partition: format!("journal_{suffix}"), mmr_metadata_partition: format!("metadata_{suffix}"), mmr_items_per_blob: NZU64!(11), mmr_write_buffer: NZUsize!(1024), log_journal_partition: format!("log_journal_{suffix}"), log_items_per_blob: NZU64!(7), log_write_buffer: NZUsize!(1024), translator: TwoCap, thread_pool: None, buffer_pool: PoolRef::new(NZUsize!(PAGE_SIZE), NZUsize!(PAGE_CACHE_SIZE)), } } pub(super) fn variable_db_config(suffix: &str) -> VariableConfig { VariableConfig { mmr_journal_partition: format!("journal_{suffix}"), mmr_metadata_partition: format!("metadata_{suffix}"), mmr_items_per_blob: NZU64!(11), mmr_write_buffer: NZUsize!(1024), log_partition: format!("log_journal_{suffix}"), log_items_per_blob: NZU64!(7), log_write_buffer: NZUsize!(1024), log_compression: None, log_codec_config: (), translator: TwoCap, thread_pool: None, buffer_pool: PoolRef::new(NZUsize!(PAGE_SIZE), NZUsize!(PAGE_CACHE_SIZE)), } } }