1use super::Immutable;
4use crate::{
5 Context,
6 journal::{authenticated, contiguous::Mutable},
7 merkle::{Family, Location, Proof},
8 qmdb::{
9 Error,
10 any::{ValueEncoding, batch::lookup_sorted},
11 batch_chain::{self, Bounds, Commitment},
12 immutable::operation::Operation,
13 operation::Key,
14 },
15 translator::Translator,
16};
17use commonware_codec::EncodeShared;
18use commonware_cryptography::{Digest, Hasher};
19use commonware_parallel::Strategy;
20use commonware_utils::iter::zip_eq;
21use std::{
22 collections::BTreeMap,
23 sync::{Arc, Weak},
24};
25
26type DiffVec<K, F, V> = Vec<(K, DiffEntry<F, V>)>;
27
28#[derive(Clone)]
30pub(crate) struct DiffEntry<F: Family, V> {
31 pub(crate) value: V,
32 pub(crate) loc: Location<F>,
33}
34
35#[allow(clippy::type_complexity)]
41pub struct UnmerkleizedBatch<F, H, K, V, S: Strategy>
42where
43 F: Family,
44 K: Key,
45 V: ValueEncoding,
46 H: Hasher,
47{
48 journal_batch: authenticated::UnmerkleizedBatch<F, H, Operation<F, K, V>, S>,
50
51 mutations: BTreeMap<K, V::Value>,
53
54 parent: Option<Arc<MerkleizedBatch<F, H::Digest, K, V, S>>>,
56
57 base: Commitment<F, H::Digest>,
60}
61
62type JournalBatch<F, D, K, V, S> = Arc<authenticated::MerkleizedBatch<F, D, Operation<F, K, V>, S>>;
64
65#[derive(Clone)]
74pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: ValueEncoding, S: Strategy> {
75 pub(super) journal_batch: JournalBatch<F, D, K, V, S>,
77
78 pub(super) diff: Arc<DiffVec<K, F, V::Value>>,
81
82 pub(super) parent: Option<Weak<Self>>,
84
85 pub(super) ancestor_diffs: Vec<Arc<DiffVec<K, F, V::Value>>>,
89
90 pub(super) bounds: batch_chain::Bounds<F, D>,
92}
93
94impl<F, H, K, V, S: Strategy> UnmerkleizedBatch<F, H, K, V, S>
95where
96 F: Family,
97 K: Key,
98 V: ValueEncoding,
99 H: Hasher,
100 Operation<F, K, V>: EncodeShared,
101{
102 pub(super) fn new<E, C, T>(
104 immutable: &Immutable<F, E, K, V, C, H, T, S>,
105 base: Commitment<F, H::Digest>,
106 ) -> Self
107 where
108 E: Context,
109 C: Mutable<Item = Operation<F, K, V>>,
110 C::Item: EncodeShared,
111 T: Translator,
112 {
113 Self {
114 journal_batch: immutable.journal.new_batch(),
115 mutations: BTreeMap::new(),
116 parent: None,
117 base,
118 }
119 }
120
121 fn db(&self) -> Commitment<F, H::Digest> {
125 self.parent
126 .as_ref()
127 .map_or(self.base, |parent| parent.bounds.db)
128 }
129
130 pub fn set(mut self, key: K, value: V::Value) -> Self {
135 self.mutations.insert(key, value);
136 self
137 }
138
139 pub async fn get<E, C, T>(
141 &self,
142 key: &K,
143 db: &Immutable<F, E, K, V, C, H, T, S>,
144 ) -> Result<Option<V::Value>, Error<F>>
145 where
146 E: Context,
147 C: Mutable<Item = Operation<F, K, V>>,
148 C::Item: EncodeShared,
149 T: Translator,
150 {
151 if let Some(value) = self.mutations.get(key) {
153 return Ok(Some(value.clone()));
154 }
155 if let Some(parent) = self.parent.as_ref() {
158 if let Some(entry) = lookup_sorted(parent.diff.as_slice(), key) {
159 return Ok(Some(entry.value.clone()));
160 }
161 for batch in parent.ancestors() {
162 if let Some(entry) = lookup_sorted(batch.diff.as_slice(), key) {
163 return Ok(Some(entry.value.clone()));
164 }
165 }
166 }
167 db.get(key).await
169 }
170
171 pub async fn get_many<E, C, T>(
175 &self,
176 keys: &[&K],
177 db: &Immutable<F, E, K, V, C, H, T, S>,
178 ) -> Result<Vec<Option<V::Value>>, Error<F>>
179 where
180 E: Context,
181 C: Mutable<Item = Operation<F, K, V>>,
182 C::Item: EncodeShared,
183 T: Translator,
184 {
185 if keys.is_empty() {
186 return Ok(Vec::new());
187 }
188
189 let mut results: Vec<Option<V::Value>> = Vec::with_capacity(keys.len());
190 let mut db_indices = Vec::new();
191 let mut db_keys = Vec::new();
192
193 for (i, key) in keys.iter().enumerate() {
194 if let Some(value) = self.mutations.get(*key) {
196 results.push(Some(value.clone()));
197 continue;
198 }
199
200 let mut found = false;
202 if let Some(parent) = self.parent.as_ref() {
203 if let Some(entry) = lookup_sorted(parent.diff.as_slice(), *key) {
204 results.push(Some(entry.value.clone()));
205 found = true;
206 }
207 if !found {
208 for batch in parent.ancestors() {
209 if let Some(entry) = lookup_sorted(batch.diff.as_slice(), *key) {
210 results.push(Some(entry.value.clone()));
211 found = true;
212 break;
213 }
214 }
215 }
216 }
217
218 if found {
219 continue;
220 }
221
222 db_indices.push(i);
224 db_keys.push(*key);
225 results.push(None);
226 }
227
228 if !db_keys.is_empty() {
229 let db_results = db.get_many(&db_keys).await?;
230 for (slot, value) in zip_eq(db_indices, db_results) {
231 results[slot] = value;
232 }
233 }
234
235 Ok(results)
236 }
237
238 #[tracing::instrument(name = "qmdb.immutable.batch.merkleize", level = "info", skip_all)]
243 pub async fn merkleize<E, C, T>(
244 self,
245 db: &Immutable<F, E, K, V, C, H, T, S>,
246 metadata: Option<V::Value>,
247 inactivity_floor: Location<F>,
248 ) -> Arc<MerkleizedBatch<F, H::Digest, K, V, S>>
249 where
250 E: Context,
251 C: Mutable<Item = Operation<F, K, V>>,
252 C::Item: EncodeShared,
253 T: Translator,
254 {
255 let base = self.base.size;
256
257 let live_ancestors: Vec<_> =
258 batch_chain::parent_and_ancestors(self.parent.as_ref(), |parent| parent.ancestors())
259 .collect();
260 let boundary = batch_chain::effective_boundary(
261 self.db(),
262 live_ancestors.last().map(|oldest| oldest.bounds.base),
263 );
264
265 let mut ops: Vec<Operation<F, K, V>> = Vec::with_capacity(self.mutations.len() + 1);
268 let mut diff: DiffVec<K, F, V::Value> = Vec::with_capacity(self.mutations.len());
269
270 for (key, value) in self.mutations {
271 let loc = base + ops.len() as u64;
272 ops.push(Operation::Set(key.clone(), value.clone()));
273 diff.push((key, DiffEntry { value, loc }));
274 }
275 assert!(diff.is_sorted_by(|a, b| a.0 < b.0));
276
277 ops.push(Operation::Commit(metadata, inactivity_floor));
278
279 let total_size = base + ops.len() as u64;
280 let inactive_peaks = F::inactive_peaks(total_size, inactivity_floor);
281
282 let (journal, root) = db
285 .journal
286 .merkleize(self.journal_batch, ops, inactive_peaks)
287 .await
288 .expect("inactive_peaks computed from batch size");
289
290 let mut ancestor_diffs = Vec::new();
292 let mut ancestors = Vec::new();
293 for batch in live_ancestors {
294 ancestor_diffs.push(Arc::clone(&batch.diff));
295 ancestors.push(batch_chain::AncestorBounds {
296 floor: batch.bounds.inactivity_floor,
297 state: batch.commitment(),
298 });
299 }
300
301 Arc::new(MerkleizedBatch {
302 journal_batch: journal,
303 diff: Arc::new(diff),
304 parent: self.parent.as_ref().map(Arc::downgrade),
305 ancestor_diffs,
306 bounds: batch_chain::Bounds {
307 base: self.base,
308 db: boundary,
309 tip: Commitment::new(total_size, root),
310 ancestors,
311 inactivity_floor,
312 },
313 })
314 }
315}
316
317impl<F: Family, D: Digest, K: Key, V: ValueEncoding, S: Strategy> MerkleizedBatch<F, D, K, V, S>
318where
319 Operation<F, K, V>: EncodeShared,
320{
321 pub const fn root(&self) -> D {
323 self.bounds.tip.root
324 }
325
326 pub const fn bounds(&self) -> &Bounds<F, D> {
328 &self.bounds
329 }
330
331 #[allow(clippy::type_complexity)]
333 pub fn operations(&self) -> (Location<F>, Arc<Vec<Operation<F, K, V>>>) {
334 (
335 self.bounds.base.size,
336 Arc::clone(self.journal_batch.items()),
337 )
338 }
339
340 pub fn proof<E, C, H, T>(
355 &self,
356 db: &Immutable<F, E, K, V, C, H, T, S>,
357 ) -> Result<Proof<F, D>, Error<F>>
358 where
359 E: Context,
360 C: Mutable<Item = Operation<F, K, V>>,
361 H: Hasher<Digest = D>,
362 T: Translator,
363 {
364 let inactive_peaks = F::inactive_peaks(self.bounds.tip.size, self.bounds.inactivity_floor);
365 db.journal
366 .speculative_proof(&self.journal_batch, inactive_peaks)
367 .map_err(Into::into)
368 }
369
370 pub fn pinned_nodes<E, C, H, T>(
385 &self,
386 db: &Immutable<F, E, K, V, C, H, T, S>,
387 ) -> Result<Vec<D>, Error<F>>
388 where
389 E: Context,
390 C: Mutable<Item = Operation<F, K, V>>,
391 H: Hasher<Digest = D>,
392 T: Translator,
393 {
394 db.journal
395 .speculative_pinned_nodes(&self.journal_batch)
396 .map_err(Into::into)
397 }
398
399 pub(super) fn ancestors(&self) -> impl Iterator<Item = Arc<Self>> + use<F, D, K, V, S> {
401 batch_chain::ancestors(self.parent.clone(), |batch| batch.parent.as_ref())
402 }
403
404 pub(super) const fn commitment(&self) -> Commitment<F, D> {
406 self.bounds.tip
407 }
408
409 pub async fn get<E, C, H, T>(
411 &self,
412 key: &K,
413 db: &Immutable<F, E, K, V, C, H, T, S>,
414 ) -> Result<Option<V::Value>, Error<F>>
415 where
416 E: Context,
417 C: Mutable<Item = Operation<F, K, V>>,
418 C::Item: EncodeShared,
419 H: Hasher<Digest = D>,
420 T: Translator,
421 {
422 if let Some(entry) = lookup_sorted(self.diff.as_slice(), key) {
423 return Ok(Some(entry.value.clone()));
424 }
425 for batch in self.ancestors() {
426 if let Some(entry) = lookup_sorted(batch.diff.as_slice(), key) {
427 return Ok(Some(entry.value.clone()));
428 }
429 }
430 db.get(key).await
431 }
432
433 pub async fn get_many<E, C, H, T>(
437 &self,
438 keys: &[&K],
439 db: &Immutable<F, E, K, V, C, H, T, S>,
440 ) -> Result<Vec<Option<V::Value>>, Error<F>>
441 where
442 E: Context,
443 C: Mutable<Item = Operation<F, K, V>>,
444 C::Item: EncodeShared,
445 H: Hasher<Digest = D>,
446 T: Translator,
447 {
448 if keys.is_empty() {
449 return Ok(Vec::new());
450 }
451
452 let mut results: Vec<Option<V::Value>> = Vec::with_capacity(keys.len());
453 let mut db_indices = Vec::new();
454 let mut db_keys = Vec::new();
455
456 for (i, key) in keys.iter().enumerate() {
457 if let Some(entry) = lookup_sorted(self.diff.as_slice(), *key) {
459 results.push(Some(entry.value.clone()));
460 continue;
461 }
462
463 let mut found = false;
465 for batch in self.ancestors() {
466 if let Some(entry) = lookup_sorted(batch.diff.as_slice(), *key) {
467 results.push(Some(entry.value.clone()));
468 found = true;
469 break;
470 }
471 }
472
473 if found {
474 continue;
475 }
476
477 db_indices.push(i);
479 db_keys.push(*key);
480 results.push(None);
481 }
482
483 if !db_keys.is_empty() {
484 let db_results = db.get_many(&db_keys).await?;
485 for (slot, value) in zip_eq(db_indices, db_results) {
486 results[slot] = value;
487 }
488 }
489
490 Ok(results)
491 }
492
493 pub fn new_batch<H>(self: &Arc<Self>) -> UnmerkleizedBatch<F, H, K, V, S>
499 where
500 H: Hasher<Digest = D>,
501 {
502 UnmerkleizedBatch {
503 journal_batch: self.journal_batch.new_batch::<H>(),
504 mutations: BTreeMap::new(),
505 parent: Some(Arc::clone(self)),
506 base: self.commitment(),
507 }
508 }
509}
510
511impl<F, E, K, V, C, H, T, S> Immutable<F, E, K, V, C, H, T, S>
512where
513 F: Family,
514 E: Context,
515 K: Key,
516 V: ValueEncoding,
517 C: Mutable<Item = Operation<F, K, V>>,
518 C::Item: EncodeShared,
519 H: Hasher,
520 T: Translator,
521 S: Strategy,
522{
523 pub fn to_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, K, V, S>> {
525 Arc::new(MerkleizedBatch {
526 journal_batch: self.journal.to_merkleized_batch(),
527 diff: Arc::new(Vec::new()),
528 parent: None,
529 ancestor_diffs: Vec::new(),
530 bounds: batch_chain::Bounds::from_db(self.commitment(), self.inactivity_floor_loc),
531 })
532 }
533}