commonware_storage/qmdb/sync/
database.rs1use crate::{
2 Context,
3 merkle::{Family, Location, full},
4 qmdb::sync::{Journal, Target},
5 translator::Translator,
6};
7use commonware_cryptography::{Digest, Hasher};
8use commonware_parallel::Strategy;
9use commonware_utils::range::NonEmptyRange;
10use std::{future::Future, num::NonZeroU64};
11
12pub trait Config {
14 type JournalConfig;
15 fn journal_config(&self) -> Self::JournalConfig;
16}
17
18impl<T: Translator, J: Clone, S: Strategy> Config for crate::qmdb::any::Config<T, J, S> {
19 type JournalConfig = J;
20
21 fn journal_config(&self) -> Self::JournalConfig {
22 self.journal_config.clone()
23 }
24}
25
26impl<T: Translator, C: Clone, S: Strategy> Config for crate::qmdb::immutable::Config<T, C, S> {
27 type JournalConfig = C;
28
29 fn journal_config(&self) -> Self::JournalConfig {
30 self.log.clone()
31 }
32}
33
34impl<J: Clone, S: Strategy> Config for crate::qmdb::keyless::Config<J, S> {
35 type JournalConfig = J;
36
37 fn journal_config(&self) -> Self::JournalConfig {
38 self.log.clone()
39 }
40}
41
42impl<C: Clone + Send + Sync + 'static, S: Strategy> Config for crate::qmdb::compact::Config<C, S> {
43 type JournalConfig = ();
44
45 fn journal_config(&self) -> Self::JournalConfig {}
46}
47
48pub trait Database: Sized + Send {
49 type Family: Family;
50 type Op: Send + Sync;
51 type Journal: Journal<Self::Family, Context = Self::Context, Op = Self::Op>;
52 type Config: Config<JournalConfig = <Self::Journal as Journal<Self::Family>>::Config>;
53 type Digest: Digest;
54 type Context: commonware_runtime::Storage
55 + commonware_runtime::Clock
56 + commonware_runtime::Metrics;
57 type Hasher: commonware_cryptography::Hasher<Digest = Self::Digest>;
58
59 fn from_sync_result(
61 context: Self::Context,
62 config: Self::Config,
63 journal: Self::Journal,
64 pinned_nodes: Option<Vec<Self::Digest>>,
65 range: NonEmptyRange<Location<Self::Family>>,
66 apply_batch_size: NonZeroU64,
67 ) -> impl Future<Output = Result<Self, crate::qmdb::Error<Self::Family>>> + Send;
68
69 fn persist_sync_result(
74 self,
75 ) -> impl Future<Output = Result<Self, crate::qmdb::Error<Self::Family>>> + Send;
76
77 fn local_pinned_nodes(
84 context: Self::Context,
85 config: &Self::Config,
86 target: &crate::qmdb::sync::Target<Self::Family, Self::Digest>,
87 journal: &Self::Journal,
88 ) -> impl Future<Output = Result<Option<Vec<Self::Digest>>, crate::qmdb::Error<Self::Family>>> + Send;
89
90 fn root(&self) -> Self::Digest;
92}
93
94pub(crate) fn journal_covers_range<F: Family>(
97 bounds: std::ops::Range<u64>,
98 range: &NonEmptyRange<Location<F>>,
99) -> bool {
100 Location::new(bounds.start) <= range.start() && Location::new(bounds.end) == range.end()
101}
102
103pub(crate) async fn local_pinned_nodes<F, E, H, S>(
110 context: E,
111 config: full::Config<S>,
112 target: &Target<F, H::Digest>,
113 inactivity_floor: Location<F>,
114) -> Result<Option<Vec<H::Digest>>, crate::qmdb::Error<F>>
115where
116 F: Family,
117 E: Context,
118 H: Hasher,
119 S: Strategy,
120{
121 let hasher = crate::qmdb::hasher::<H>();
122
123 let merkle = match full::Merkle::<F, _, _, S>::init(context, &hasher, config).await {
126 Ok(merkle) => merkle,
127 Err(crate::merkle::Error::MissingNode(_)) => return Ok(None),
128 Err(err) => return Err(err.into()),
129 };
130 let bounds = merkle.bounds();
131 if bounds.start > target.range.start() || bounds.end != target.range.end() {
132 return Ok(None);
133 }
134
135 let inactive_peaks = F::inactive_peaks(target.range.end(), inactivity_floor);
136 if merkle.root(&hasher, inactive_peaks)? != target.root {
137 return Ok(None);
138 }
139
140 merkle
141 .pinned_nodes_at(target.range.start())
142 .await
143 .map(Some)
144 .map_err(Into::into)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::{journal_covers_range, local_pinned_nodes};
150 use crate::{
151 journal::contiguous::fixed,
152 merkle::{Location, Position, full, mmr::Family as MmrFamily},
153 qmdb::sync::Target,
154 };
155 use commonware_cryptography::{Sha256, sha256::Digest};
156 use commonware_parallel::Sequential;
157 use commonware_runtime::{
158 BufferPooler, Runner as _, Supervisor as _, buffer::paged::CacheRef, deterministic,
159 };
160 use commonware_utils::{NZU16, NZU64, NZUsize, non_empty_range, range::NonEmptyRange};
161
162 fn merkle_config(pooler: &impl BufferPooler) -> full::Config<Sequential> {
163 full::Config {
164 journal_partition: "local-pins-journal".into(),
165 metadata_partition: "local-pins-metadata".into(),
166 items_per_blob: NZU64!(7),
167 write_buffer: NZUsize!(1024),
168 replay_buffer: NZUsize!(1024),
169 strategy: Sequential,
170 page_cache: CacheRef::from_pooler(pooler, NZU16!(111), NZUsize!(5)),
171 }
172 }
173
174 #[test]
175 fn test_journal_covers_range() {
176 let range: NonEmptyRange<Location<MmrFamily>> =
177 non_empty_range!(Location::new(10), Location::new(20));
178
179 assert!(journal_covers_range(10..20, &range));
181 assert!(journal_covers_range(5..20, &range));
182
183 assert!(!journal_covers_range(11..20, &range));
185
186 assert!(!journal_covers_range(10..19, &range));
188 assert!(!journal_covers_range(10..21, &range));
189 assert!(!journal_covers_range(0..0, &range));
190 }
191
192 #[test]
193 fn local_pinned_nodes_treats_interrupted_reset_as_unavailable() {
194 deterministic::Runner::default().start(|context| async move {
195 let hasher = crate::qmdb::hasher::<Sha256>();
196 let config = merkle_config(&context);
197 let mut merkle = full::Merkle::<MmrFamily, _, Digest, Sequential>::init(
198 context.child("init"),
199 &hasher,
200 config.clone(),
201 )
202 .await
203 .unwrap();
204 let mut batch = merkle.new_batch();
205 for i in 0u64..50 {
206 batch = batch.add(&hasher, &i.to_be_bytes());
207 }
208 let batch = merkle.with_mem(|mem| batch.merkleize(mem, &hasher));
209 merkle = merkle.apply_batch(&batch).unwrap();
210 let merkle = merkle.sync().await.unwrap();
211 let merkle = merkle.prune(Location::new(30)).await.unwrap();
212 let merkle = merkle.sync().await.unwrap();
213 drop(merkle);
214
215 let restart = Location::new(7);
218 let journal_config = fixed::Config {
219 partition: config.journal_partition.clone(),
220 items_per_blob: config.items_per_blob,
221 page_cache: config.page_cache.clone(),
222 write_buffer: config.write_buffer,
223 replay_buffer: config.replay_buffer,
224 };
225 let journal = fixed::Journal::<_, Digest>::init(
226 context.child("interrupted_reset"),
227 journal_config,
228 )
229 .await
230 .unwrap();
231 let reset_pos = Position::<MmrFamily>::try_from(restart).unwrap();
232 let journal = journal.clear_to_size(*reset_pos).await.unwrap();
233 drop(journal);
234
235 let target = Target {
236 root: Digest::from([0; 32]),
237 range: non_empty_range!(restart, Location::new(20)),
238 };
239 let pinned = local_pinned_nodes::<MmrFamily, _, Sha256, Sequential>(
240 context.child("local_pins"),
241 config,
242 &target,
243 restart,
244 )
245 .await
246 .unwrap();
247 assert!(pinned.is_none());
248 });
249 }
250}