Skip to main content

commonware_storage/qmdb/keyless/sync/
mod.rs

1use crate::{
2    Context,
3    journal::{
4        authenticated,
5        contiguous::{Contiguous as _, Mutable},
6    },
7    merkle::{
8        Family, Location,
9        full::{self, Merkle},
10    },
11    qmdb::{
12        self,
13        any::value::ValueEncoding,
14        keyless::{CompactDb, Keyless, Metrics, Operation, operation::Codec},
15        sync,
16    },
17};
18use commonware_codec::{EncodeShared, Read};
19use commonware_cryptography::Hasher;
20use commonware_parallel::Strategy;
21use commonware_utils::range::NonEmptyRange;
22use std::num::NonZeroU64;
23
24impl<F, E, V, C, H, S> sync::Database for Keyless<F, E, V, C, H, S>
25where
26    F: Family,
27    E: Context,
28    V: ValueEncoding + Codec,
29    C: Mutable<Item = Operation<F, V>> + sync::Journal<F, Context = E, Op = Operation<F, V>>,
30    C::Config: Clone + Send,
31    H: Hasher,
32    S: Strategy,
33    Operation<F, V>: EncodeShared,
34{
35    type Family = F;
36    type Op = Operation<F, V>;
37    type Journal = C;
38    type Hasher = H;
39    type Config = super::Config<C::Config, S>;
40    type Digest = H::Digest;
41    type Context = E;
42
43    /// Returns a [Keyless] db initialized from data collected in the sync process.
44    ///
45    /// # Behavior
46    ///
47    /// This method handles different initialization scenarios based on existing data:
48    /// - If the Merkle journal is empty or the last item is before the range start, it creates
49    ///   a fresh Merkle structure from the provided `pinned_nodes`
50    /// - If the Merkle journal has data but is incomplete (has length < range end), missing
51    ///   operations from the log are applied to bring it up to the target state
52    /// - If the Merkle journal has data beyond the range end, it is rewound to match the sync
53    ///   target
54    ///
55    /// # Returns
56    ///
57    /// A [Keyless] db populated with the state from the given range.
58    async fn from_sync_result(
59        context: Self::Context,
60        config: Self::Config,
61        log: Self::Journal,
62        pinned_nodes: Option<Vec<Self::Digest>>,
63        range: NonEmptyRange<Location<F>>,
64        apply_batch_size: NonZeroU64,
65    ) -> Result<Self, qmdb::Error<F>> {
66        let hasher = qmdb::hasher::<H>();
67
68        let merkle = Merkle::<F, _, _, S>::init_sync(
69            context.child("merkle"),
70            full::SyncConfig {
71                config: config.merkle.clone(),
72                range: range.clone(),
73                pinned_nodes,
74            },
75        )
76        .await?;
77
78        let journal = authenticated::Journal::<F, _, _, _, S>::from_components(
79            merkle,
80            log,
81            hasher,
82            apply_batch_size.get(),
83        )
84        .await?;
85
86        let (last_commit_loc, inactivity_floor_loc) = {
87            let bounds = journal.bounds();
88            let loc = bounds
89                .end
90                .checked_sub(1)
91                .ok_or(qmdb::Error::HistoricalFloorPruned(Location::new(
92                    bounds.end,
93                )))?;
94            let floor =
95                qmdb::find_inactivity_floor_at::<F, _>(&journal, Location::new(bounds.end)).await?;
96            (Location::new(loc), floor)
97        };
98        let inactive_peaks = F::inactive_peaks(last_commit_loc + 1, inactivity_floor_loc);
99        let root = journal.root(inactive_peaks)?;
100
101        let metrics = Metrics::new(context);
102        let db = Self {
103            journal,
104            root,
105            last_commit_loc,
106            inactivity_floor_loc,
107            metrics,
108        };
109        db.update_metrics();
110
111        db.sync().await
112    }
113
114    async fn persist_sync_result(self) -> Result<Self, qmdb::Error<F>> {
115        Ok(self)
116    }
117
118    async fn local_pinned_nodes(
119        context: Self::Context,
120        config: &Self::Config,
121        target: &sync::Target<F, Self::Digest>,
122        journal: &Self::Journal,
123    ) -> Result<Option<Vec<Self::Digest>>, qmdb::Error<F>> {
124        if target.range.start() == Location::new(0)
125            || !sync::journal_covers_range(journal.bounds(), &target.range)
126        {
127            return Ok(None);
128        }
129
130        // The inactivity floor is carried by the last commit operation rather than being
131        // the target range's start.
132        let inactivity_floor =
133            qmdb::find_inactivity_floor_at::<F, _>(journal, target.range.end()).await?;
134
135        sync::local_pinned_nodes::<F, _, H, S>(
136            context,
137            config.merkle.clone(),
138            target,
139            inactivity_floor,
140        )
141        .await
142    }
143
144    fn root(&self) -> Self::Digest {
145        self.root()
146    }
147}
148
149impl<F, E, V, H, Cfg, S> sync::Database for CompactDb<F, E, V, H, Cfg, S>
150where
151    F: Family,
152    E: Context,
153    V: ValueEncoding + Codec,
154    H: Hasher,
155    S: Strategy,
156    Operation<F, V>: EncodeShared,
157    Operation<F, V>: Read<Cfg = Cfg>,
158    Cfg: Clone + Send + Sync + 'static,
159{
160    type Family = F;
161    type Op = Operation<F, V>;
162    type Journal = sync::journal::Memory<F, E, Operation<F, V>>;
163    type Config = super::CompactConfig<Cfg, S>;
164    type Digest = H::Digest;
165    type Context = E;
166    type Hasher = H;
167
168    async fn from_sync_result(
169        context: Self::Context,
170        config: Self::Config,
171        log: Self::Journal,
172        pinned_nodes: Option<Vec<Self::Digest>>,
173        range: NonEmptyRange<Location<F>>,
174        _apply_batch_size: NonZeroU64,
175    ) -> Result<Self, qmdb::Error<F>> {
176        crate::qmdb::compact::from_sync_result(
177            context,
178            config,
179            log,
180            pinned_nodes,
181            range,
182            Self::init_from_sync,
183        )
184        .await
185    }
186
187    async fn persist_sync_result(self) -> Result<Self, qmdb::Error<F>> {
188        self.sync().await
189    }
190
191    async fn local_pinned_nodes(
192        _context: Self::Context,
193        _config: &Self::Config,
194        _target: &sync::Target<F, Self::Digest>,
195        _journal: &Self::Journal,
196    ) -> Result<Option<Vec<Self::Digest>>, qmdb::Error<F>> {
197        Ok(None)
198    }
199
200    fn root(&self) -> Self::Digest {
201        self.root()
202    }
203}
204
205#[cfg(test)]
206mod tests;