use crate::{ merkle::{full, Family, Location}, qmdb::sync::{Journal, Target}, translator::Translator, Context, }; use commonware_cryptography::{Digest, Hasher}; use commonware_parallel::Strategy; use commonware_utils::range::NonEmptyRange; use std::future::Future; pub trait Config { type JournalConfig; fn journal_config(&self) -> Self::JournalConfig; } impl Config for crate::qmdb::any::Config { type JournalConfig = J; fn journal_config(&self) -> Self::JournalConfig { self.journal_config.clone() } } impl Config for crate::qmdb::immutable::Config { type JournalConfig = C; fn journal_config(&self) -> Self::JournalConfig { self.log.clone() } } impl Config for crate::qmdb::keyless::Config { type JournalConfig = J; fn journal_config(&self) -> Self::JournalConfig { self.log.clone() } } pub trait Database: Sized + Send { type Family: Family; type Op: Send; type Journal: Journal; type Config: Config>::Config>; type Digest: Digest; type Context: commonware_runtime::Storage + commonware_runtime::Clock + commonware_runtime::Metrics; type Hasher: commonware_cryptography::Hasher; /// Build a database from the journal and pinned nodes populated by the sync engine. fn from_sync_result( context: Self::Context, config: Self::Config, journal: Self::Journal, pinned_nodes: Option>, range: NonEmptyRange>, apply_batch_size: usize, ) -> impl Future>> + Send; /// Return locally available boundary nodes for the target, if persisted local state can /// authenticate them. /// /// Returning `Some` lets a completed sync journal reuse boundary nodes from an on-disk /// database instead of fetching them from peers. Returning `None` always falls back to /// fetching from peers. Simple append-only variants may verify only the persisted tree size /// and root. Variants with additional pruning-dependent state should also ensure their /// persisted lower bound still covers `target.range.start()`. fn local_boundary_nodes( context: Self::Context, config: &Self::Config, target: &crate::qmdb::sync::Target, journal: &Self::Journal, ) -> impl Future>, crate::qmdb::Error>> + Send; /// Get the root digest of the database for verification fn root(&self) -> Self::Digest; } /// Whether a completed sync journal's `bounds` cover `range`: retained data reaches back to /// `range.start()` and ends exactly at `range.end()`. pub(crate) fn journal_covers_range( bounds: std::ops::Range, range: &NonEmptyRange>, ) -> bool { Location::new(bounds.start) <= range.start() && Location::new(bounds.end) == range.end() } /// Shared body for [`Database::local_boundary_nodes`] implementations backed by a persisted /// [`full::Merkle`]: reopen it from `config` under `context` and return the boundary nodes at /// `target.range.start()` if the persisted bounds cover the target and the root, computed with /// `inactivity_floor`, matches `target.root`. Returns `Ok(None)` when the persisted state /// cannot authenticate the target. pub(crate) async fn local_boundary_nodes( context: E, config: full::Config, target: &Target, inactivity_floor: Location, ) -> Result>, crate::qmdb::Error> where F: Family, E: Context, H: Hasher, S: Strategy, { let hasher = crate::qmdb::hasher::(); let merkle = full::Merkle::::init(context, &hasher, config).await?; let bounds = merkle.bounds(); if bounds.start > target.range.start() || bounds.end != target.range.end() { return Ok(None); } let inactive_peaks = F::inactive_peaks( F::location_to_position(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) }