use super::{Keyless, operation::Operation};
use crate::{
Context,
journal::{authenticated, contiguous::Mutable},
merkle::{Family, Location, Proof},
qmdb::{
Error,
any::value::ValueEncoding,
batch_chain::{self, Bounds, Commitment},
},
};
use commonware_codec::EncodeShared;
use commonware_cryptography::{Digest, DigestOf, Hasher};
use commonware_parallel::Strategy;
use std::sync::{Arc, Weak};
type MerkleizedParent<F, H, V, S> = Arc<MerkleizedBatch<F, DigestOf<H>, V, S>>;
pub struct UnmerkleizedBatch<F, H, V, S: Strategy>
where
F: Family,
V: ValueEncoding,
H: Hasher,
Operation<F, V>: EncodeShared,
{
journal_batch: authenticated::UnmerkleizedBatch<F, H, Operation<F, V>, S>,
appends: Vec<V::Value>,
parent: Option<MerkleizedParent<F, H, V, S>>,
base: Commitment<F, H::Digest>,
}
#[derive(Clone)]
pub struct MerkleizedBatch<F: Family, D: Digest, V: ValueEncoding, S: Strategy>
where
Operation<F, V>: EncodeShared,
{
pub(super) journal_batch: Arc<authenticated::MerkleizedBatch<F, D, Operation<F, V>, S>>,
pub(super) parent: Option<Weak<Self>>,
pub(super) bounds: batch_chain::Bounds<F, D>,
}
impl<F: Family, D: Digest, V: ValueEncoding, S: Strategy> MerkleizedBatch<F, D, V, S>
where
Operation<F, V>: EncodeShared,
{
pub(super) fn ancestors(&self) -> impl Iterator<Item = Arc<Self>> + use<F, D, V, S> {
batch_chain::ancestors(self.parent.clone(), |batch| batch.parent.as_ref())
}
pub(super) const fn commitment(&self) -> Commitment<F, D> {
self.bounds.tip
}
}
fn read_chain_op<F: Family, D: Digest, V: ValueEncoding, S: Strategy>(
batch: &MerkleizedBatch<F, D, V, S>,
loc: u64,
) -> Option<Operation<F, V>>
where
Operation<F, V>: EncodeShared,
{
let self_end = batch.journal_batch.size();
let self_base = self_end - batch.journal_batch.items().len() as u64;
if loc >= self_base && loc < self_end {
return Some(batch.journal_batch.items()[(loc - self_base) as usize].clone());
}
for ancestor in batch.ancestors() {
let end = ancestor.journal_batch.size();
let base = end - ancestor.journal_batch.items().len() as u64;
if loc >= base && loc < end {
return Some(ancestor.journal_batch.items()[(loc - base) as usize].clone());
}
}
None
}
impl<F, H, V, S: Strategy> UnmerkleizedBatch<F, H, V, S>
where
F: Family,
V: ValueEncoding,
H: Hasher,
Operation<F, V>: EncodeShared,
{
pub(super) fn new<E, C>(
keyless: &Keyless<F, E, V, C, H, S>,
base: Commitment<F, H::Digest>,
) -> Self
where
E: Context,
C: Mutable<Item = Operation<F, V>>,
{
Self {
journal_batch: keyless.journal.new_batch(),
appends: Vec::new(),
parent: None,
base,
}
}
pub fn size(&self) -> Location<F> {
self.base.size + self.appends.len() as u64
}
fn db(&self) -> Commitment<F, H::Digest> {
self.parent
.as_ref()
.map_or(self.base, |parent| parent.bounds.db)
}
pub fn append(mut self, value: V::Value) -> Self {
self.appends.push(value);
self
}
pub async fn get<E, C>(
&self,
loc: Location<F>,
db: &Keyless<F, E, V, C, H, S>,
) -> Result<Option<V::Value>, Error<F>>
where
E: Context,
C: Mutable<Item = Operation<F, V>>,
{
let loc_val = *loc;
if loc_val >= self.base.size {
let idx = (loc_val - *self.base.size) as usize;
return if idx < self.appends.len() {
Ok(Some(self.appends[idx].clone()))
} else {
Ok(None)
};
}
if let Some(parent) = self.parent.as_ref()
&& loc_val >= parent.bounds.db.size
&& let Some(op) = read_chain_op(parent, loc_val)
{
return Ok(op.into_value());
}
db.get(loc).await
}
pub async fn get_many<E, C>(
&self,
locs: &[Location<F>],
db: &Keyless<F, E, V, C, H, S>,
) -> Result<Vec<Option<V::Value>>, Error<F>>
where
E: Context,
C: Mutable<Item = Operation<F, V>>,
{
if locs.is_empty() {
return Ok(Vec::new());
}
assert!(
locs.is_sorted_by(|a, b| a < b),
"locations must be strictly increasing"
);
let mut results = Vec::with_capacity(locs.len());
let mut db_indices = Vec::new();
let mut db_locs = Vec::new();
for (i, &loc) in locs.iter().enumerate() {
let loc_val = *loc;
if loc_val >= self.base.size {
let idx = (loc_val - *self.base.size) as usize;
results.push(if idx < self.appends.len() {
Some(self.appends[idx].clone())
} else {
None
});
continue;
}
if let Some(parent) = self.parent.as_ref()
&& loc_val >= parent.bounds.db.size
&& let Some(op) = read_chain_op(parent, loc_val)
{
results.push(op.into_value());
continue;
}
db_indices.push(i);
db_locs.push(loc);
results.push(None);
}
if !db_locs.is_empty() {
let db_results = db.get_many(&db_locs).await?;
for (slot, value) in db_indices.into_iter().zip(db_results) {
results[slot] = value;
}
}
Ok(results)
}
#[tracing::instrument(name = "qmdb.keyless.batch.merkleize", level = "info", skip_all)]
pub async fn merkleize<E, C>(
self,
db: &Keyless<F, E, V, C, H, S>,
metadata: Option<V::Value>,
inactivity_floor: Location<F>,
) -> Arc<MerkleizedBatch<F, H::Digest, V, S>>
where
E: Context,
C: Mutable<Item = Operation<F, V>>,
{
let live_ancestors: Vec<_> =
batch_chain::parent_and_ancestors(self.parent.as_ref(), |parent| parent.ancestors())
.collect();
let boundary = batch_chain::effective_boundary(
self.db(),
live_ancestors.last().map(|oldest| oldest.bounds.base),
);
let mut ops: Vec<Operation<F, V>> = Vec::with_capacity(self.appends.len() + 1);
for value in self.appends {
ops.push(Operation::Append(value));
}
ops.push(Operation::Commit(metadata, inactivity_floor));
let total_size = self.base.size + ops.len() as u64;
let inactive_peaks = F::inactive_peaks(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 ancestors = batch_chain::collect_ancestor_bounds(
live_ancestors,
|batch| batch.bounds.inactivity_floor,
|batch| batch.commitment(),
);
Arc::new(MerkleizedBatch {
journal_batch: journal,
parent: self.parent.as_ref().map(Arc::downgrade),
bounds: batch_chain::Bounds {
base: self.base,
db: boundary,
tip: Commitment::new(total_size, root),
ancestors,
inactivity_floor,
},
})
}
}
impl<F: Family, D: Digest, V: ValueEncoding, S: Strategy> MerkleizedBatch<F, D, V, S>
where
Operation<F, V>: EncodeShared,
{
pub const fn root(&self) -> D {
self.bounds.tip.root
}
pub const fn bounds(&self) -> &Bounds<F, D> {
&self.bounds
}
pub fn operations(&self) -> (Location<F>, Arc<Vec<Operation<F, V>>>) {
(
self.bounds.base.size,
Arc::clone(self.journal_batch.items()),
)
}
pub fn proof<E, C, H>(&self, db: &Keyless<F, E, V, C, H, S>) -> Result<Proof<F, D>, Error<F>>
where
E: Context,
C: Mutable<Item = Operation<F, V>>,
H: Hasher<Digest = D>,
{
let inactive_peaks = F::inactive_peaks(self.bounds.tip.size, self.bounds.inactivity_floor);
db.journal
.speculative_proof(&self.journal_batch, inactive_peaks)
.map_err(Into::into)
}
pub fn pinned_nodes<E, C, H>(&self, db: &Keyless<F, E, V, C, H, S>) -> Result<Vec<D>, Error<F>>
where
E: Context,
C: Mutable<Item = Operation<F, V>>,
H: Hasher<Digest = D>,
{
db.journal
.speculative_pinned_nodes(&self.journal_batch)
.map_err(Into::into)
}
pub async fn get<E, H, C>(
&self,
loc: Location<F>,
db: &Keyless<F, E, V, C, H, S>,
) -> Result<Option<V::Value>, Error<F>>
where
E: Context,
H: Hasher<Digest = D>,
C: Mutable<Item = Operation<F, V>>,
{
let loc_val = *loc;
if loc_val >= self.bounds.db.size
&& let Some(op) = read_chain_op(self, loc_val)
{
return Ok(op.into_value());
}
db.get(loc).await
}
pub async fn get_many<E, H, C>(
&self,
locs: &[Location<F>],
db: &Keyless<F, E, V, C, H, S>,
) -> Result<Vec<Option<V::Value>>, Error<F>>
where
E: Context,
H: Hasher<Digest = D>,
C: Mutable<Item = Operation<F, V>>,
{
if locs.is_empty() {
return Ok(Vec::new());
}
assert!(
locs.is_sorted_by(|a, b| a < b),
"locations must be strictly increasing"
);
let mut results = Vec::with_capacity(locs.len());
let mut db_indices = Vec::new();
let mut db_locs = Vec::new();
for (i, &loc) in locs.iter().enumerate() {
let loc_val = *loc;
if loc_val >= self.bounds.db.size
&& let Some(op) = read_chain_op(self, loc_val)
{
results.push(op.into_value());
continue;
}
db_indices.push(i);
db_locs.push(loc);
results.push(None);
}
if !db_locs.is_empty() {
let db_results = db.get_many(&db_locs).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, V, S>
where
H: Hasher<Digest = D>,
{
UnmerkleizedBatch {
journal_batch: self.journal_batch.new_batch::<H>(),
appends: Vec::new(),
parent: Some(Arc::clone(self)),
base: self.commitment(),
}
}
}