use super::Immutable;
use crate::{
journal::{authenticated, contiguous::Mutable},
merkle::{Family, Location},
qmdb::{
any::{batch::lookup_sorted, ValueEncoding},
batch_chain::{self, Bounds},
immutable::operation::Operation,
operation::Key,
Error,
},
translator::Translator,
Context,
};
use commonware_codec::EncodeShared;
use commonware_cryptography::{Digest, Hasher};
use commonware_parallel::Strategy;
use std::{
collections::BTreeMap,
sync::{Arc, Weak},
};
type DiffVec<K, F, V> = Vec<(K, DiffEntry<F, V>)>;
#[derive(Clone)]
pub(crate) struct DiffEntry<F: Family, V> {
pub(crate) value: V,
pub(crate) loc: Location<F>,
}
#[allow(clippy::type_complexity)]
pub struct UnmerkleizedBatch<F, H, K, V, S: Strategy>
where
F: Family,
K: Key,
V: ValueEncoding,
H: Hasher,
{
journal_batch: authenticated::UnmerkleizedBatch<F, H, Operation<F, K, V>, S>,
mutations: BTreeMap<K, V::Value>,
parent: Option<Arc<MerkleizedBatch<F, H::Digest, K, V, S>>>,
base_size: u64,
db_size: u64,
}
type JournalBatch<F, D, K, V, S> = Arc<authenticated::MerkleizedBatch<F, D, Operation<F, K, V>, S>>;
#[derive(Clone)]
pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: ValueEncoding, S: Strategy> {
pub(super) journal_batch: JournalBatch<F, D, K, V, S>,
pub(super) root: D,
pub(super) diff: Arc<DiffVec<K, F, V::Value>>,
pub(super) parent: Option<Weak<Self>>,
pub(super) ancestor_diffs: Vec<Arc<DiffVec<K, F, V::Value>>>,
pub(super) bounds: batch_chain::Bounds<F>,
}
impl<F, H, K, V, S: Strategy> UnmerkleizedBatch<F, H, K, V, S>
where
F: Family,
K: Key,
V: ValueEncoding,
H: Hasher,
Operation<F, K, V>: EncodeShared,
{
pub(super) fn new<E, C, T>(
immutable: &Immutable<F, E, K, V, C, H, T, S>,
journal_size: u64,
) -> Self
where
E: Context,
C: Mutable<Item = Operation<F, K, V>>,
C::Item: EncodeShared,
T: Translator,
{
Self {
journal_batch: immutable.journal.new_batch(),
mutations: BTreeMap::new(),
parent: None,
base_size: journal_size,
db_size: journal_size,
}
}
pub fn set(mut self, key: K, value: V::Value) -> Self {
self.mutations.insert(key, value);
self
}
pub async fn get<E, C, T>(
&self,
key: &K,
db: &Immutable<F, E, K, V, C, H, T, S>,
) -> Result<Option<V::Value>, Error<F>>
where
E: Context,
C: Mutable<Item = Operation<F, K, V>>,
C::Item: EncodeShared,
T: Translator,
{
if let Some(value) = self.mutations.get(key) {
return Ok(Some(value.clone()));
}
if let Some(parent) = self.parent.as_ref() {
if let Some(entry) = lookup_sorted(parent.diff.as_slice(), key) {
return Ok(Some(entry.value.clone()));
}
for batch in parent.ancestors() {
if let Some(entry) = lookup_sorted(batch.diff.as_slice(), key) {
return Ok(Some(entry.value.clone()));
}
}
}
db.get(key).await
}
pub async fn get_many<E, C, T>(
&self,
keys: &[&K],
db: &Immutable<F, E, K, V, C, H, T, S>,
) -> Result<Vec<Option<V::Value>>, Error<F>>
where
E: Context,
C: Mutable<Item = Operation<F, K, V>>,
C::Item: EncodeShared,
T: Translator,
{
if keys.is_empty() {
return Ok(Vec::new());
}
let mut results: Vec<Option<V::Value>> = Vec::with_capacity(keys.len());
let mut db_indices = Vec::new();
let mut db_keys = Vec::new();
for (i, key) in keys.iter().enumerate() {
if let Some(value) = self.mutations.get(*key) {
results.push(Some(value.clone()));
continue;
}
let mut found = false;
if let Some(parent) = self.parent.as_ref() {
if let Some(entry) = lookup_sorted(parent.diff.as_slice(), *key) {
results.push(Some(entry.value.clone()));
found = true;
}
if !found {
for batch in parent.ancestors() {
if let Some(entry) = lookup_sorted(batch.diff.as_slice(), *key) {
results.push(Some(entry.value.clone()));
found = true;
break;
}
}
}
}
if found {
continue;
}
db_indices.push(i);
db_keys.push(*key);
results.push(None);
}
if !db_keys.is_empty() {
let db_results = db.get_many(&db_keys).await?;
for (slot, value) in db_indices.into_iter().zip(db_results) {
results[slot] = value;
}
}
Ok(results)
}
#[tracing::instrument(name = "qmdb.immutable.batch.merkleize", level = "info", skip_all)]
pub async fn merkleize<E, C, T>(
self,
db: &Immutable<F, E, K, V, C, H, T, S>,
metadata: Option<V::Value>,
inactivity_floor: Location<F>,
) -> Arc<MerkleizedBatch<F, H::Digest, K, V, S>>
where
E: Context,
C: Mutable<Item = Operation<F, K, V>>,
C::Item: EncodeShared,
T: Translator,
{
let base = self.base_size;
let mut ops: Vec<Operation<F, K, V>> = Vec::with_capacity(self.mutations.len() + 1);
let mut diff: DiffVec<K, F, V::Value> = Vec::with_capacity(self.mutations.len());
for (key, value) in self.mutations {
let loc = Location::new(base + ops.len() as u64);
ops.push(Operation::Set(key.clone(), value.clone()));
diff.push((key, DiffEntry { value, loc }));
}
assert!(diff.is_sorted_by(|a, b| a.0 < b.0));
ops.push(Operation::Commit(metadata, inactivity_floor));
let total_size = base + ops.len() as u64;
let inactive_peaks = F::inactive_peaks(
F::location_to_position(Location::new(total_size)),
inactivity_floor,
);
let (journal, root) = db
.journal
.merkleize(self.journal_batch, ops, inactive_peaks)
.await
.expect("inactive_peaks computed from batch size");
let mut ancestor_diffs = Vec::new();
let mut ancestors = Vec::new();
for batch in
batch_chain::parent_and_ancestors(self.parent.as_ref(), |parent| parent.ancestors())
{
ancestor_diffs.push(Arc::clone(&batch.diff));
ancestors.push(batch_chain::AncestorBounds {
floor: batch.bounds.inactivity_floor,
end: batch.bounds.total_size,
});
}
Arc::new(MerkleizedBatch {
journal_batch: journal,
root,
diff: Arc::new(diff),
parent: self.parent.as_ref().map(Arc::downgrade),
ancestor_diffs,
bounds: batch_chain::Bounds {
base_size: self.base_size,
db_size: self.db_size,
total_size,
ancestors,
inactivity_floor,
},
})
}
}
impl<F: Family, D: Digest, K: Key, V: ValueEncoding, S: Strategy> MerkleizedBatch<F, D, K, V, S>
where
Operation<F, K, V>: EncodeShared,
{
pub const fn root(&self) -> D {
self.root
}
pub const fn bounds(&self) -> &Bounds<F> {
&self.bounds
}
pub(super) fn ancestors(&self) -> impl Iterator<Item = Arc<Self>> {
batch_chain::ancestors(self.parent.clone(), |batch| batch.parent.as_ref())
}
pub async fn get<E, C, H, T>(
&self,
key: &K,
db: &Immutable<F, E, K, V, C, H, T, S>,
) -> Result<Option<V::Value>, Error<F>>
where
E: Context,
C: Mutable<Item = Operation<F, K, V>>,
C::Item: EncodeShared,
H: Hasher<Digest = D>,
T: Translator,
{
if let Some(entry) = lookup_sorted(self.diff.as_slice(), key) {
return Ok(Some(entry.value.clone()));
}
for batch in self.ancestors() {
if let Some(entry) = lookup_sorted(batch.diff.as_slice(), key) {
return Ok(Some(entry.value.clone()));
}
}
db.get(key).await
}
pub async fn get_many<E, C, H, T>(
&self,
keys: &[&K],
db: &Immutable<F, E, K, V, C, H, T, S>,
) -> Result<Vec<Option<V::Value>>, Error<F>>
where
E: Context,
C: Mutable<Item = Operation<F, K, V>>,
C::Item: EncodeShared,
H: Hasher<Digest = D>,
T: Translator,
{
if keys.is_empty() {
return Ok(Vec::new());
}
let mut results: Vec<Option<V::Value>> = Vec::with_capacity(keys.len());
let mut db_indices = Vec::new();
let mut db_keys = Vec::new();
for (i, key) in keys.iter().enumerate() {
if let Some(entry) = lookup_sorted(self.diff.as_slice(), *key) {
results.push(Some(entry.value.clone()));
continue;
}
let mut found = false;
for batch in self.ancestors() {
if let Some(entry) = lookup_sorted(batch.diff.as_slice(), *key) {
results.push(Some(entry.value.clone()));
found = true;
break;
}
}
if found {
continue;
}
db_indices.push(i);
db_keys.push(*key);
results.push(None);
}
if !db_keys.is_empty() {
let db_results = db.get_many(&db_keys).await?;
for (slot, value) in db_indices.into_iter().zip(db_results) {
results[slot] = value;
}
}
Ok(results)
}
pub fn new_batch<H>(self: &Arc<Self>) -> UnmerkleizedBatch<F, H, K, V, S>
where
H: Hasher<Digest = D>,
{
UnmerkleizedBatch {
journal_batch: self.journal_batch.new_batch::<H>(),
mutations: BTreeMap::new(),
parent: Some(Arc::clone(self)),
base_size: self.bounds.total_size,
db_size: self.bounds.db_size,
}
}
}
impl<F, E, K, V, C, H, T, S> Immutable<F, E, K, V, C, H, T, S>
where
F: Family,
E: Context,
K: Key,
V: ValueEncoding,
C: Mutable<Item = Operation<F, K, V>>,
C::Item: EncodeShared,
H: Hasher,
T: Translator,
S: Strategy,
{
pub fn to_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, K, V, S>> {
let journal_size = *self.last_commit_loc + 1;
Arc::new(MerkleizedBatch {
journal_batch: self.journal.to_merkleized_batch(),
root: self.root,
diff: Arc::new(Vec::new()),
parent: None,
ancestor_diffs: Vec::new(),
bounds: batch_chain::Bounds {
base_size: journal_size,
db_size: journal_size,
total_size: journal_size,
ancestors: Vec::new(),
inactivity_floor: self.inactivity_floor_loc,
},
})
}
}