use crate::{ Context, journal::{authenticated, contiguous::Mutable}, merkle::{ Family, Location, full::{self, Merkle}, }, qmdb::{ self, Error, any::ValueEncoding, immutable::{self, CompactDb, Metrics, Operation}, operation::Key, sync, }, translator::Translator, }; use commonware_codec::{EncodeShared, Read}; use commonware_cryptography::Hasher; use commonware_parallel::Strategy; use commonware_utils::range::NonEmptyRange; use std::num::NonZeroU64; impl sync::Database for immutable::Immutable where F: Family, E: Context, K: Key, V: ValueEncoding, C: Mutable> + sync::Journal>, C::Item: EncodeShared, H: Hasher, T: Translator, S: Strategy, { type Family = F; type Op = Operation; type Journal = C; type Hasher = H; type Config = immutable::Config; type Digest = H::Digest; type Context = E; /// Returns an [Immutable](immutable::Immutable) initialized from data collected in the sync process. /// /// # Behavior /// /// This method handles different initialization scenarios based on existing data: /// - If the Merkle journal is empty or the last item is before the range start, it creates a /// fresh Merkle structure from the provided `pinned_nodes` /// - If the Merkle journal has data but is incomplete (has length < range end), missing /// operations from the log are applied to bring it up to the target state /// - If the Merkle journal has data beyond the range end, initialization truncates it to the /// sync target /// /// # Returns /// /// A [super::Immutable] db populated with the state from the given range. /// Its inactivity floor comes from the final commit, while the Merkle pruning boundary is /// the range start. async fn from_sync_result( context: Self::Context, db_config: Self::Config, log: Self::Journal, pinned_nodes: Option>, range: NonEmptyRange>, apply_batch_size: NonZeroU64, ) -> Result> { let hasher = qmdb::hasher::(); // Initialize Merkle structure for sync let merkle = Merkle::::init_sync( context.child("merkle"), full::SyncConfig { config: db_config.merkle_config, range: range.clone(), pinned_nodes, }, ) .await?; let journal = authenticated::Journal::<_, _, _, _, S>::from_components( merkle, log, hasher, apply_batch_size, ) .await?; // The synced range ends at the target commit, whose inactivity floor is where the // snapshot replay starts. An empty journal holds no commit to take the floor from. let size = journal.size(); if size == 0 { return Err(Error::HistoricalFloorPruned(size)); } let inactivity_floor_loc = crate::qmdb::find_inactivity_floor_at::(&journal.journal, size).await?; // Replay the log from the inactivity floor to build the snapshot. let snapshot = immutable::build_snapshot( context.child("snapshot"), db_config.translator, inactivity_floor_loc, &journal.journal, db_config.init_buffer, ) .await?; let inactive_peaks = F::inactive_peaks(size, inactivity_floor_loc); let root = journal.root(inactive_peaks)?; let metrics = Metrics::new(context); let db = Self { journal, root, snapshot, inactivity_floor_loc, metrics, }; db.update_metrics(); db.sync().await } async fn persist_sync_result(self) -> Result> { Ok(self) } async fn local_pinned_nodes( context: Self::Context, config: &Self::Config, target: &sync::Target, journal: &Self::Journal, ) -> Result>, Error> { if target.range.start() == Location::new(0) || !sync::journal_covers_range(journal.bounds(), &target.range) { return Ok(None); } // The inactivity floor is carried by the last commit operation rather than being // the target range's start. let inactivity_floor = qmdb::find_inactivity_floor_at::(journal, target.range.end()).await?; sync::local_pinned_nodes::( context, config.merkle_config.clone(), target, inactivity_floor, ) .await } fn root(&self) -> Self::Digest { self.root() } } impl sync::Database for CompactDb where F: Family, E: Context, K: Key, V: ValueEncoding, H: Hasher, S: Strategy, Operation: EncodeShared, Operation: Read, { type Family = F; type Op = Operation; type Journal = sync::journal::Memory>; type Config = immutable::CompactConfig< as Read>::Cfg, S>; type Digest = H::Digest; type Context = E; type Hasher = H; async fn from_sync_result( context: Self::Context, config: Self::Config, log: Self::Journal, pinned_nodes: Option>, range: NonEmptyRange>, _apply_batch_size: NonZeroU64, ) -> Result> { crate::qmdb::compact::from_sync_result( context, config, log, pinned_nodes, range, Self::init_from_sync, ) .await } async fn persist_sync_result(self) -> Result> { self.sync().await } async fn local_pinned_nodes( _context: Self::Context, _config: &Self::Config, _target: &sync::Target, _journal: &Self::Journal, ) -> Result>, Error> { Ok(None) } fn root(&self) -> Self::Digest { self.root() } } #[cfg(test)] mod tests;