use std::ops::{Bound, RangeBounds, RangeInclusive};
use zakura_chain::{block::Height, parallel::commitment_aux::BlockCommitmentRoots};
use crate::service::finalized_state::{
disk_db::DiskWriteBatch, disk_format::shielded::CommitmentRootsByHeight, TypedColumnFamily,
};
use super::ZakuraDb;
pub const COMMITMENT_ROOTS_BY_HEIGHT: &str = "commitment_roots_by_height";
type CommitmentRootsCf<'cf> = TypedColumnFamily<'cf, Height, CommitmentRootsByHeight>;
fn disk_row(roots: &BlockCommitmentRoots) -> CommitmentRootsByHeight {
CommitmentRootsByHeight {
sapling: roots.sapling_root,
orchard: roots.orchard_root,
auth_data_root: roots.auth_data_root,
ironwood: roots.ironwood_root,
sapling_tx: roots.sapling_tx,
orchard_tx: roots.orchard_tx,
ironwood_tx: roots.ironwood_tx,
}
}
fn domain_roots(height: Height, row: CommitmentRootsByHeight) -> BlockCommitmentRoots {
BlockCommitmentRoots {
height,
sapling_root: row.sapling,
orchard_root: row.orchard,
auth_data_root: row.auth_data_root,
ironwood_root: row.ironwood,
sapling_tx: row.sapling_tx,
orchard_tx: row.orchard_tx,
ironwood_tx: row.ironwood_tx,
}
}
fn inclusive_bounds(range: impl RangeBounds<Height>) -> Option<(Height, Height)> {
let start = match range.start_bound() {
Bound::Included(height) => *height,
Bound::Excluded(height) => height.next().ok()?,
Bound::Unbounded => Height::MIN,
};
let end = match range.end_bound() {
Bound::Included(height) => *height,
Bound::Excluded(height) => height.previous().ok()?,
Bound::Unbounded => Height::MAX,
};
(start <= end).then_some((start, end))
}
impl ZakuraDb {
fn commitment_roots_cf(&self) -> CommitmentRootsCf<'_> {
CommitmentRootsCf::new(&self.db, COMMITMENT_ROOTS_BY_HEIGHT)
.expect("column family was created when database was created")
}
pub(super) fn has_commitment_roots_index(&self) -> bool {
CommitmentRootsCf::new(&self.db, COMMITMENT_ROOTS_BY_HEIGHT).is_some()
}
pub fn commitment_roots(&self, height: Height) -> Option<BlockCommitmentRoots> {
self.commitment_roots_cf()
.zs_get(&height)
.map(|row| domain_roots(height, row))
}
pub fn commitment_roots_by_height_range(
&self,
range: RangeInclusive<Height>,
) -> Vec<BlockCommitmentRoots> {
self.contiguous_commitment_roots(range)
}
pub fn zakura_header_commitment_roots_by_height_range(
&self,
range: impl RangeBounds<Height>,
) -> Vec<BlockCommitmentRoots> {
self.contiguous_commitment_roots(range)
}
fn contiguous_commitment_roots(
&self,
range: impl RangeBounds<Height>,
) -> Vec<BlockCommitmentRoots> {
let Some((start, end)) = inclusive_bounds(range) else {
return Vec::new();
};
let cf = self.commitment_roots_cf();
let mut roots = Vec::new();
for height in (start.0..=end.0).map(Height) {
let Some(row) = cf.zs_get(&height) else {
break;
};
roots.push(domain_roots(height, row));
}
roots
}
pub fn insert_zakura_header_commitment_roots(
&self,
roots: impl IntoIterator<Item = BlockCommitmentRoots>,
) -> Result<(), rocksdb::Error> {
let mut batch = DiskWriteBatch::new();
for roots in roots {
batch.insert_legacy_header_commitment_roots(self, &roots);
}
self.write_batch(batch)
}
pub fn delete_zakura_header_commitment_roots(
&self,
heights: impl IntoIterator<Item = Height>,
) -> Result<(), rocksdb::Error> {
let mut batch = DiskWriteBatch::new();
for height in heights {
batch.delete_legacy_header_commitment_root(self, height);
}
self.write_batch(batch)
}
pub(super) fn commitment_root_heights_for_repair(
&self,
start: Height,
limit: usize,
) -> Vec<Height> {
self.commitment_roots_cf()
.zs_forward_range_iter(start..)
.map(|(height, _row)| height)
.take(limit)
.collect()
}
pub(super) fn visit_commitment_roots_for_migration(
&self,
range: impl RangeBounds<Height>,
mut visit: impl FnMut(Height, BlockCommitmentRoots),
) {
for (height, row) in self.commitment_roots_cf().zs_forward_range_iter(range) {
visit(height, domain_roots(height, row));
}
}
}
impl DiskWriteBatch {
pub fn insert_body_derived_commitment_roots(
&mut self,
db: &ZakuraDb,
roots: &BlockCommitmentRoots,
) {
let _ = db
.commitment_roots_cf()
.with_batch_for_writing(self)
.zs_insert(&roots.height, &disk_row(roots));
}
pub(super) fn insert_legacy_header_commitment_roots(
&mut self,
db: &ZakuraDb,
roots: &BlockCommitmentRoots,
) {
if db.contains_height(roots.height) {
return;
}
let _ = db
.commitment_roots_cf()
.with_batch_for_writing(self)
.zs_insert(&roots.height, &disk_row(roots));
}
pub(super) fn delete_legacy_header_commitment_root(&mut self, db: &ZakuraDb, height: Height) {
let _ = db
.commitment_roots_cf()
.with_batch_for_writing(self)
.zs_delete(&height);
}
pub(super) fn delete_header_reorg_commitment_roots(
&mut self,
db: &ZakuraDb,
start: Height,
end: Height,
) {
if start > end {
return;
}
let mut writer = db.commitment_roots_cf().with_batch_for_writing(self);
for height in (start.0..=end.0).map(Height) {
writer = writer.zs_delete(&height);
}
let _ = writer;
}
pub(super) fn truncate_commitment_roots_after(&mut self, db: &ZakuraDb, target: Height) {
let Ok(start) = target.next() else {
return;
};
let writer = db
.commitment_roots_cf()
.with_batch_for_writing(self)
.zs_delete_range(&start, &Height::MAX)
.zs_delete(&Height::MAX);
let _ = writer;
}
pub(super) fn delete_commitment_root_for_repair(&mut self, db: &ZakuraDb, height: Height) {
let _ = db
.commitment_roots_cf()
.with_batch_for_writing(self)
.zs_delete(&height);
}
#[cfg(test)]
#[allow(clippy::too_many_arguments)]
pub fn insert_commitment_roots_by_height(
&mut self,
db: &ZakuraDb,
height: Height,
sapling_root: &zakura_chain::sapling::tree::Root,
orchard_root: &zakura_chain::orchard::tree::Root,
ironwood_root: &zakura_chain::ironwood::tree::Root,
sapling_tx: u64,
orchard_tx: u64,
ironwood_tx: u64,
auth_data_root: &zakura_chain::block::merkle::AuthDataRoot,
) {
self.insert_body_derived_commitment_roots(
db,
&BlockCommitmentRoots {
height,
sapling_root: *sapling_root,
orchard_root: *orchard_root,
auth_data_root: *auth_data_root,
ironwood_root: *ironwood_root,
sapling_tx,
orchard_tx,
ironwood_tx,
},
);
}
#[cfg(test)]
pub fn delete_range_commitment_roots_by_height(
&mut self,
db: &ZakuraDb,
from: &Height,
until_strictly_before: &Height,
) {
let _ = db
.commitment_roots_cf()
.with_batch_for_writing(self)
.zs_delete_range(from, until_strictly_before);
}
}
#[cfg(test)]
mod tests {
#[test]
fn production_root_column_access_is_centralized() {
let production_sources = [
("block.rs", include_str!("block.rs")),
("shielded.rs", include_str!("shielded.rs")),
("rollback.rs", include_str!("rollback.rs")),
(
"block/startup_audit.rs",
include_str!("block/startup_audit.rs"),
),
];
for (path, source) in production_sources {
let compact = source.split_whitespace().collect::<String>();
assert!(
!compact.contains("cf_handle(COMMITMENT_ROOTS_BY_HEIGHT)"),
"{path} accesses the commitment-root column family directly",
);
}
}
}