Zebra/zebra-state/src/service/finalized_state/zebra_db/chain.rs

145 lines
5.2 KiB
Rust

//! Provides high-level access to database whole-chain:
//! - history trees
//! - chain value pools
//!
//! This module makes sure that:
//! - all disk writes happen inside a RocksDB transaction, and
//! - format-specific invariants are maintained.
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
use std::{borrow::Borrow, collections::HashMap, sync::Arc};
use zebra_chain::{
amount::NonNegative, block::Height, history_tree::HistoryTree, transparent,
value_balance::ValueBalance,
};
use crate::{
service::finalized_state::{
disk_db::{DiskDb, DiskWriteBatch, ReadDisk, WriteDisk},
disk_format::RawBytes,
zebra_db::ZebraDb,
},
BoxError, SemanticallyVerifiedBlock,
};
impl ZebraDb {
// History tree methods
/// Returns the ZIP-221 history tree of the finalized tip.
///
/// If history trees have not been activated yet (pre-Heartwood), or the state is empty,
/// returns an empty history tree.
pub fn history_tree(&self) -> Arc<HistoryTree> {
if self.is_empty() {
return Arc::<HistoryTree>::default();
}
let history_tree_cf = self.db.cf_handle("history_tree").unwrap();
// # Backwards Compatibility
//
// This code can read the column family format in 1.2.0 and earlier (tip height key),
// and after PR #7392 is merged (empty key). The height-based code can be removed when
// versions 1.2.0 and earlier are no longer supported.
//
// # Concurrency
//
// There is only one entry in this column family, which is atomically updated by a block
// write batch (database transaction). If we used a height as the key in this column family,
// any updates between reading the tip height and reading the tree could cause panics.
//
// So we use the empty key `()`. Since the key has a constant value, we will always read
// the latest tree.
let mut history_tree: Option<Arc<HistoryTree>> = self.db.zs_get(&history_tree_cf, &());
if history_tree.is_none() {
let tip_height = self
.finalized_tip_height()
.expect("just checked for an empty database");
history_tree = self.db.zs_get(&history_tree_cf, &tip_height);
}
history_tree.unwrap_or_default()
}
/// Returns all the history tip trees.
/// We only store the history tree for the tip, so this method is mainly used in tests.
pub fn history_trees_full_tip(
&self,
) -> impl Iterator<Item = (RawBytes, Arc<HistoryTree>)> + '_ {
let history_tree_cf = self.db.cf_handle("history_tree").unwrap();
self.db.zs_range_iter(&history_tree_cf, ..)
}
// Value pool methods
/// Returns the stored `ValueBalance` for the best chain at the finalized tip height.
pub fn finalized_value_pool(&self) -> ValueBalance<NonNegative> {
let value_pool_cf = self.db.cf_handle("tip_chain_value_pool").unwrap();
self.db
.zs_get(&value_pool_cf, &())
.unwrap_or_else(ValueBalance::zero)
}
}
impl DiskWriteBatch {
// History tree methods
/// Updates the history tree for the tip, if it is not empty.
pub fn update_history_tree(&mut self, zebra_db: &ZebraDb, tree: &HistoryTree) {
let history_tree_cf = zebra_db.db.cf_handle("history_tree").unwrap();
if let Some(tree) = tree.as_ref().as_ref() {
self.zs_insert(&history_tree_cf, (), tree);
}
}
/// Legacy method: Deletes the range of history trees at the given [`Height`]s.
/// Doesn't delete the upper bound.
///
/// From state format 25.3.0 onwards, the history trees are indexed by an empty key,
/// so this method does nothing.
pub fn delete_range_history_tree(&mut self, zebra_db: &ZebraDb, from: &Height, to: &Height) {
let history_tree_cf = zebra_db.db.cf_handle("history_tree").unwrap();
// TODO: convert zs_delete_range() to take std::ops::RangeBounds
self.zs_delete_range(&history_tree_cf, from, to);
}
// Value pool methods
/// Prepare a database batch containing the chain value pool update from `finalized.block`,
/// and return it (without actually writing anything).
///
/// If this method returns an error, it will be propagated,
/// and the batch should not be written to the database.
///
/// # Errors
///
/// - Propagates any errors from updating value pools
#[allow(clippy::unwrap_in_result)]
pub fn prepare_chain_value_pools_batch(
&mut self,
db: &DiskDb,
finalized: &SemanticallyVerifiedBlock,
utxos_spent_by_block: HashMap<transparent::OutPoint, transparent::Utxo>,
value_pool: ValueBalance<NonNegative>,
) -> Result<(), BoxError> {
let tip_chain_value_pool = db.cf_handle("tip_chain_value_pool").unwrap();
let SemanticallyVerifiedBlock { block, .. } = finalized;
let new_pool = value_pool.add_block(block.borrow(), &utxos_spent_by_block)?;
self.zs_insert(&tip_chain_value_pool, (), new_pool);
Ok(())
}
}