Skip to main content

commonware_storage/qmdb/immutable/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, Error,
10        any::ValueEncoding,
11        immutable::{self, CompactDb, Metrics, Operation},
12        operation::Key,
13        sync,
14    },
15    translator::Translator,
16};
17use commonware_codec::{EncodeShared, Read};
18use commonware_cryptography::Hasher;
19use commonware_parallel::Strategy;
20use commonware_utils::range::NonEmptyRange;
21use std::num::NonZeroU64;
22
23impl<F, E, K, V, C, H, T, S> sync::Database for immutable::Immutable<F, E, K, V, C, H, T, S>
24where
25    F: Family,
26    E: Context,
27    K: Key,
28    V: ValueEncoding,
29    C: Mutable<Item = Operation<F, K, V>> + sync::Journal<F, Context = E, Op = Operation<F, K, V>>,
30    C::Item: EncodeShared,
31    H: Hasher,
32    T: Translator,
33    S: Strategy,
34{
35    type Family = F;
36    type Op = Operation<F, K, V>;
37    type Journal = C;
38    type Hasher = H;
39    type Config = immutable::Config<T, C::Config, S>;
40    type Digest = H::Digest;
41    type Context = E;
42
43    /// Returns an [Immutable](immutable::Immutable) 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 a
49    ///   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, initialization truncates it to the
53    ///   sync target
54    ///
55    /// # Returns
56    ///
57    /// A [super::Immutable] db populated with the state from the given range.
58    /// Its inactivity floor comes from the final commit, while the Merkle pruning boundary is
59    /// the range start.
60    async fn from_sync_result(
61        context: Self::Context,
62        db_config: Self::Config,
63        log: Self::Journal,
64        pinned_nodes: Option<Vec<Self::Digest>>,
65        range: NonEmptyRange<Location<F>>,
66        apply_batch_size: NonZeroU64,
67    ) -> Result<Self, Error<F>> {
68        let hasher = qmdb::hasher::<H>();
69
70        // Initialize Merkle structure for sync
71        let merkle = Merkle::<F, _, _, S>::init_sync(
72            context.child("merkle"),
73            full::SyncConfig {
74                config: db_config.merkle_config,
75                range: range.clone(),
76                pinned_nodes,
77            },
78        )
79        .await?;
80
81        let journal = authenticated::Journal::<_, _, _, _, S>::from_components(
82            merkle,
83            log,
84            hasher,
85            apply_batch_size,
86        )
87        .await?;
88
89        // The synced range ends at the target commit, whose inactivity floor is where the
90        // snapshot replay starts. An empty journal holds no commit to take the floor from.
91        let size = journal.size();
92        if size == 0 {
93            return Err(Error::HistoricalFloorPruned(size));
94        }
95        let inactivity_floor_loc =
96            crate::qmdb::find_inactivity_floor_at::<F, _>(&journal.journal, size).await?;
97
98        // Replay the log from the inactivity floor to build the snapshot.
99        let snapshot = immutable::build_snapshot(
100            context.child("snapshot"),
101            db_config.translator,
102            inactivity_floor_loc,
103            &journal.journal,
104            db_config.init_buffer,
105        )
106        .await?;
107        let inactive_peaks = F::inactive_peaks(size, inactivity_floor_loc);
108        let root = journal.root(inactive_peaks)?;
109
110        let metrics = Metrics::new(context);
111        let db = Self {
112            journal,
113            root,
114            snapshot,
115            inactivity_floor_loc,
116            metrics,
117        };
118        db.update_metrics();
119
120        db.sync().await
121    }
122
123    async fn persist_sync_result(self) -> Result<Self, Error<F>> {
124        Ok(self)
125    }
126
127    async fn local_pinned_nodes(
128        context: Self::Context,
129        config: &Self::Config,
130        target: &sync::Target<F, Self::Digest>,
131        journal: &Self::Journal,
132    ) -> Result<Option<Vec<Self::Digest>>, Error<F>> {
133        if target.range.start() == Location::new(0)
134            || !sync::journal_covers_range(journal.bounds(), &target.range)
135        {
136            return Ok(None);
137        }
138
139        // The inactivity floor is carried by the last commit operation rather than being
140        // the target range's start.
141        let inactivity_floor =
142            qmdb::find_inactivity_floor_at::<F, _>(journal, target.range.end()).await?;
143
144        sync::local_pinned_nodes::<F, _, H, S>(
145            context,
146            config.merkle_config.clone(),
147            target,
148            inactivity_floor,
149        )
150        .await
151    }
152
153    fn root(&self) -> Self::Digest {
154        self.root()
155    }
156}
157
158impl<F, E, K, V, H, S> sync::Database for CompactDb<F, E, K, V, H, S>
159where
160    F: Family,
161    E: Context,
162    K: Key,
163    V: ValueEncoding,
164    H: Hasher,
165    S: Strategy,
166    Operation<F, K, V>: EncodeShared,
167    Operation<F, K, V>: Read,
168{
169    type Family = F;
170    type Op = Operation<F, K, V>;
171    type Journal = sync::journal::Memory<F, E, Operation<F, K, V>>;
172    type Config = immutable::CompactConfig<<Operation<F, K, V> as Read>::Cfg, S>;
173    type Digest = H::Digest;
174    type Context = E;
175    type Hasher = H;
176
177    async fn from_sync_result(
178        context: Self::Context,
179        config: Self::Config,
180        log: Self::Journal,
181        pinned_nodes: Option<Vec<Self::Digest>>,
182        range: NonEmptyRange<Location<F>>,
183        _apply_batch_size: NonZeroU64,
184    ) -> Result<Self, Error<F>> {
185        crate::qmdb::compact::from_sync_result(
186            context,
187            config,
188            log,
189            pinned_nodes,
190            range,
191            Self::init_from_sync,
192        )
193        .await
194    }
195
196    async fn persist_sync_result(self) -> Result<Self, Error<F>> {
197        self.sync().await
198    }
199
200    async fn local_pinned_nodes(
201        _context: Self::Context,
202        _config: &Self::Config,
203        _target: &sync::Target<F, Self::Digest>,
204        _journal: &Self::Journal,
205    ) -> Result<Option<Vec<Self::Digest>>, Error<F>> {
206        Ok(None)
207    }
208
209    fn root(&self) -> Self::Digest {
210        self.root()
211    }
212}
213
214#[cfg(test)]
215mod tests;