Skip to main content

commonware_storage/qmdb/keyless/sync/
mod.rs

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