use super::{operation::Operation, Keyless};
use crate::{
journal::{authenticated, contiguous::Mutable},
merkle::{Family, Location},
qmdb::{
any::value::ValueEncoding,
batch_chain::{self, Bounds},
Error,
},
Context,
};
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_size: u64,
db_size: u64,
}
#[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) root: D,
pub(super) parent: Option<Weak<Self>>,
pub(super) bounds: batch_chain::Bounds<F>,
}
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>> {
batch_chain::ancestors(self.parent.clone(), |batch| batch.parent.as_ref())
}
}
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>, journal_size: u64) -> Self
where
E: Context,
C: Mutable<Item = Operation<F, V>>,
{
Self {
journal_batch: keyless.journal.new_batch(),
appends: Vec::new(),
parent: None,
base_size: journal_size,
db_size: journal_size,
}
}
pub const fn size(&self) -> Location<F> {
Location::new(self.base_size + self.appends.len() as u64)
}
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() {
if loc_val >= self.db_size {
if 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() {
if loc_val >= self.db_size {
if 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 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(
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 ancestors =
batch_chain::parent_and_ancestors(self.parent.as_ref(), |parent| parent.ancestors());
let ancestors = batch_chain::collect_ancestor_bounds(
ancestors,
|batch| batch.bounds.inactivity_floor,
|batch| batch.bounds.total_size,
);
Arc::new(MerkleizedBatch {
journal_batch: journal,
root,
parent: self.parent.as_ref().map(Arc::downgrade),
bounds: batch_chain::Bounds {
base_size: self.base_size,
db_size: self.db_size,
total_size,
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.root
}
pub const fn bounds(&self) -> &Bounds<F> {
&self.bounds
}
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 {
if 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 {
if 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_size: self.bounds.total_size,
db_size: self.bounds.db_size,
}
}
}