Skip to main content

commonware_storage/qmdb/keyless/sync/
mod.rs

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