1use super::Immutable;
4use crate::{
5 journal::{authenticated, contiguous::Mutable},
6 merkle::{Family, Location},
7 qmdb::{
8 any::{batch::lookup_sorted, ValueEncoding},
9 batch_chain::{self, Bounds},
10 immutable::operation::Operation,
11 operation::Key,
12 Error,
13 },
14 translator::Translator,
15 Context,
16};
17use commonware_codec::EncodeShared;
18use commonware_cryptography::{Digest, Hasher};
19use commonware_parallel::Strategy;
20use std::{
21 collections::BTreeMap,
22 sync::{Arc, Weak},
23};
24
25type DiffVec<K, F, V> = Vec<(K, DiffEntry<F, V>)>;
26
27#[derive(Clone)]
29pub(crate) struct DiffEntry<F: Family, V> {
30 pub(crate) value: V,
31 pub(crate) loc: Location<F>,
32}
33
34#[allow(clippy::type_complexity)]
40pub struct UnmerkleizedBatch<F, H, K, V, S: Strategy>
41where
42 F: Family,
43 K: Key,
44 V: ValueEncoding,
45 H: Hasher,
46{
47 journal_batch: authenticated::UnmerkleizedBatch<F, H, Operation<F, K, V>, S>,
49
50 mutations: BTreeMap<K, V::Value>,
52
53 parent: Option<Arc<MerkleizedBatch<F, H::Digest, K, V, S>>>,
55
56 base_size: u64,
59
60 db_size: u64,
62}
63
64type JournalBatch<F, D, K, V, S> = Arc<authenticated::MerkleizedBatch<F, D, Operation<F, K, V>, S>>;
66
67#[derive(Clone)]
70pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: ValueEncoding, S: Strategy> {
71 pub(super) journal_batch: JournalBatch<F, D, K, V, S>,
73
74 pub(super) root: D,
76
77 pub(super) diff: Arc<DiffVec<K, F, V::Value>>,
80
81 pub(super) parent: Option<Weak<Self>>,
83
84 pub(super) ancestor_diffs: Vec<Arc<DiffVec<K, F, V::Value>>>,
88
89 pub(super) bounds: batch_chain::Bounds<F>,
91}
92
93impl<F, H, K, V, S: Strategy> UnmerkleizedBatch<F, H, K, V, S>
94where
95 F: Family,
96 K: Key,
97 V: ValueEncoding,
98 H: Hasher,
99 Operation<F, K, V>: EncodeShared,
100{
101 pub(super) fn new<E, C, T>(
103 immutable: &Immutable<F, E, K, V, C, H, T, S>,
104 journal_size: u64,
105 ) -> Self
106 where
107 E: Context,
108 C: Mutable<Item = Operation<F, K, V>>,
109 C::Item: EncodeShared,
110 T: Translator,
111 {
112 Self {
113 journal_batch: immutable.journal.new_batch(),
114 mutations: BTreeMap::new(),
115 parent: None,
116 base_size: journal_size,
117 db_size: journal_size,
118 }
119 }
120
121 pub fn set(mut self, key: K, value: V::Value) -> Self {
126 self.mutations.insert(key, value);
127 self
128 }
129
130 pub async fn get<E, C, T>(
132 &self,
133 key: &K,
134 db: &Immutable<F, E, K, V, C, H, T, S>,
135 ) -> Result<Option<V::Value>, Error<F>>
136 where
137 E: Context,
138 C: Mutable<Item = Operation<F, K, V>>,
139 C::Item: EncodeShared,
140 T: Translator,
141 {
142 if let Some(value) = self.mutations.get(key) {
144 return Ok(Some(value.clone()));
145 }
146 if let Some(parent) = self.parent.as_ref() {
149 if let Some(entry) = lookup_sorted(parent.diff.as_slice(), key) {
150 return Ok(Some(entry.value.clone()));
151 }
152 for batch in parent.ancestors() {
153 if let Some(entry) = lookup_sorted(batch.diff.as_slice(), key) {
154 return Ok(Some(entry.value.clone()));
155 }
156 }
157 }
158 db.get(key).await
160 }
161
162 pub async fn get_many<E, C, T>(
166 &self,
167 keys: &[&K],
168 db: &Immutable<F, E, K, V, C, H, T, S>,
169 ) -> Result<Vec<Option<V::Value>>, Error<F>>
170 where
171 E: Context,
172 C: Mutable<Item = Operation<F, K, V>>,
173 C::Item: EncodeShared,
174 T: Translator,
175 {
176 if keys.is_empty() {
177 return Ok(Vec::new());
178 }
179
180 let mut results: Vec<Option<V::Value>> = Vec::with_capacity(keys.len());
181 let mut db_indices = Vec::new();
182 let mut db_keys = Vec::new();
183
184 for (i, key) in keys.iter().enumerate() {
185 if let Some(value) = self.mutations.get(*key) {
187 results.push(Some(value.clone()));
188 continue;
189 }
190
191 let mut found = false;
193 if let Some(parent) = self.parent.as_ref() {
194 if let Some(entry) = lookup_sorted(parent.diff.as_slice(), *key) {
195 results.push(Some(entry.value.clone()));
196 found = true;
197 }
198 if !found {
199 for batch in parent.ancestors() {
200 if let Some(entry) = lookup_sorted(batch.diff.as_slice(), *key) {
201 results.push(Some(entry.value.clone()));
202 found = true;
203 break;
204 }
205 }
206 }
207 }
208
209 if found {
210 continue;
211 }
212
213 db_indices.push(i);
215 db_keys.push(*key);
216 results.push(None);
217 }
218
219 if !db_keys.is_empty() {
220 let db_results = db.get_many(&db_keys).await?;
221 for (slot, value) in db_indices.into_iter().zip(db_results) {
222 results[slot] = value;
223 }
224 }
225
226 Ok(results)
227 }
228
229 #[tracing::instrument(name = "qmdb.immutable.batch.merkleize", level = "info", skip_all)]
234 pub async fn merkleize<E, C, T>(
235 self,
236 db: &Immutable<F, E, K, V, C, H, T, S>,
237 metadata: Option<V::Value>,
238 inactivity_floor: Location<F>,
239 ) -> Arc<MerkleizedBatch<F, H::Digest, K, V, S>>
240 where
241 E: Context,
242 C: Mutable<Item = Operation<F, K, V>>,
243 C::Item: EncodeShared,
244 T: Translator,
245 {
246 let base = self.base_size;
247
248 let mut ops: Vec<Operation<F, K, V>> = Vec::with_capacity(self.mutations.len() + 1);
251 let mut diff: DiffVec<K, F, V::Value> = Vec::with_capacity(self.mutations.len());
252
253 for (key, value) in self.mutations {
254 let loc = Location::new(base + ops.len() as u64);
255 ops.push(Operation::Set(key.clone(), value.clone()));
256 diff.push((key, DiffEntry { value, loc }));
257 }
258 assert!(diff.is_sorted_by(|a, b| a.0 < b.0));
259
260 ops.push(Operation::Commit(metadata, inactivity_floor));
261
262 let total_size = base + ops.len() as u64;
263 let inactive_peaks = F::inactive_peaks(
264 F::location_to_position(Location::new(total_size)),
265 inactivity_floor,
266 );
267
268 let (journal, root) = db
271 .journal
272 .merkleize(self.journal_batch, ops, inactive_peaks)
273 .await
274 .expect("inactive_peaks computed from batch size");
275
276 let mut ancestor_diffs = Vec::new();
278 let mut ancestors = Vec::new();
279 for batch in
280 batch_chain::parent_and_ancestors(self.parent.as_ref(), |parent| parent.ancestors())
281 {
282 ancestor_diffs.push(Arc::clone(&batch.diff));
283 ancestors.push(batch_chain::AncestorBounds {
284 floor: batch.bounds.inactivity_floor,
285 end: batch.bounds.total_size,
286 });
287 }
288
289 Arc::new(MerkleizedBatch {
290 journal_batch: journal,
291 root,
292 diff: Arc::new(diff),
293 parent: self.parent.as_ref().map(Arc::downgrade),
294 ancestor_diffs,
295 bounds: batch_chain::Bounds {
296 base_size: self.base_size,
297 db_size: self.db_size,
298 total_size,
299 ancestors,
300 inactivity_floor,
301 },
302 })
303 }
304}
305
306impl<F: Family, D: Digest, K: Key, V: ValueEncoding, S: Strategy> MerkleizedBatch<F, D, K, V, S>
307where
308 Operation<F, K, V>: EncodeShared,
309{
310 pub const fn root(&self) -> D {
312 self.root
313 }
314
315 pub const fn bounds(&self) -> &Bounds<F> {
317 &self.bounds
318 }
319
320 pub(super) fn ancestors(&self) -> impl Iterator<Item = Arc<Self>> {
322 batch_chain::ancestors(self.parent.clone(), |batch| batch.parent.as_ref())
323 }
324
325 pub async fn get<E, C, H, T>(
327 &self,
328 key: &K,
329 db: &Immutable<F, E, K, V, C, H, T, S>,
330 ) -> Result<Option<V::Value>, Error<F>>
331 where
332 E: Context,
333 C: Mutable<Item = Operation<F, K, V>>,
334 C::Item: EncodeShared,
335 H: Hasher<Digest = D>,
336 T: Translator,
337 {
338 if let Some(entry) = lookup_sorted(self.diff.as_slice(), key) {
339 return Ok(Some(entry.value.clone()));
340 }
341 for batch in self.ancestors() {
342 if let Some(entry) = lookup_sorted(batch.diff.as_slice(), key) {
343 return Ok(Some(entry.value.clone()));
344 }
345 }
346 db.get(key).await
347 }
348
349 pub async fn get_many<E, C, H, T>(
353 &self,
354 keys: &[&K],
355 db: &Immutable<F, E, K, V, C, H, T, S>,
356 ) -> Result<Vec<Option<V::Value>>, Error<F>>
357 where
358 E: Context,
359 C: Mutable<Item = Operation<F, K, V>>,
360 C::Item: EncodeShared,
361 H: Hasher<Digest = D>,
362 T: Translator,
363 {
364 if keys.is_empty() {
365 return Ok(Vec::new());
366 }
367
368 let mut results: Vec<Option<V::Value>> = Vec::with_capacity(keys.len());
369 let mut db_indices = Vec::new();
370 let mut db_keys = Vec::new();
371
372 for (i, key) in keys.iter().enumerate() {
373 if let Some(entry) = lookup_sorted(self.diff.as_slice(), *key) {
375 results.push(Some(entry.value.clone()));
376 continue;
377 }
378
379 let mut found = false;
381 for batch in self.ancestors() {
382 if let Some(entry) = lookup_sorted(batch.diff.as_slice(), *key) {
383 results.push(Some(entry.value.clone()));
384 found = true;
385 break;
386 }
387 }
388
389 if found {
390 continue;
391 }
392
393 db_indices.push(i);
395 db_keys.push(*key);
396 results.push(None);
397 }
398
399 if !db_keys.is_empty() {
400 let db_results = db.get_many(&db_keys).await?;
401 for (slot, value) in db_indices.into_iter().zip(db_results) {
402 results[slot] = value;
403 }
404 }
405
406 Ok(results)
407 }
408
409 pub fn new_batch<H>(self: &Arc<Self>) -> UnmerkleizedBatch<F, H, K, V, S>
415 where
416 H: Hasher<Digest = D>,
417 {
418 UnmerkleizedBatch {
419 journal_batch: self.journal_batch.new_batch::<H>(),
420 mutations: BTreeMap::new(),
421 parent: Some(Arc::clone(self)),
422 base_size: self.bounds.total_size,
423 db_size: self.bounds.db_size,
424 }
425 }
426}
427
428impl<F, E, K, V, C, H, T, S> Immutable<F, E, K, V, C, H, T, S>
429where
430 F: Family,
431 E: Context,
432 K: Key,
433 V: ValueEncoding,
434 C: Mutable<Item = Operation<F, K, V>>,
435 C::Item: EncodeShared,
436 H: Hasher,
437 T: Translator,
438 S: Strategy,
439{
440 pub fn to_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, K, V, S>> {
442 let journal_size = *self.last_commit_loc + 1;
443 Arc::new(MerkleizedBatch {
444 journal_batch: self.journal.to_merkleized_batch(),
445 root: self.root,
446 diff: Arc::new(Vec::new()),
447 parent: None,
448 ancestor_diffs: Vec::new(),
449 bounds: batch_chain::Bounds {
450 base_size: journal_size,
451 db_size: journal_size,
452 total_size: journal_size,
453 ancestors: Vec::new(),
454 inactivity_floor: self.inactivity_floor_loc,
455 },
456 })
457 }
458}