use std::{
fmt,
fmt::{Display, Error, Formatter},
sync::Arc,
};
use primitive_types::U512;
use serde::{Deserialize, Serialize};
use tari_common_types::types::{BlockHash, CompressedCommitment, CompressedPublicKey, FixedHash, HashOutput};
use tari_node_components::blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader};
use tari_transaction_components::transaction_components::{OutputType, TransactionKernel, TransactionOutput};
use tari_utilities::hex::Hex;
use crate::{
blocks::UpdateBlockAccumulatedData,
chain_storage::{HorizonData, Reorg, error::ChainStorageError},
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HorizonSyncOutputCheckpoint {
pub checkpoint_height: u64,
pub checkpoint_hash: FixedHash,
pub sync_target_height: u64,
pub sync_target_hash: FixedHash,
}
#[derive(Debug)]
pub struct DbTransaction {
operations: Vec<WriteOperation>,
}
#[derive(Debug, Clone)]
pub struct HorizonStateTreeUpdate {
pub key: FixedHash,
pub value: Option<FixedHash>,
}
impl Display for DbTransaction {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fmt.write_str("Db transaction: \n")?;
for write_op in &self.operations {
fmt.write_str(&format!("{write_op}\n"))?;
}
Ok(())
}
}
impl Default for DbTransaction {
fn default() -> Self {
DbTransaction {
operations: Vec::with_capacity(128),
}
}
}
impl DbTransaction {
pub fn new() -> Self {
DbTransaction::default()
}
pub fn delete_orphan(&mut self, hash: HashOutput) -> &mut Self {
self.operations.push(WriteOperation::DeleteOrphan(hash));
self
}
pub fn delete_header(&mut self, height: u64) -> &mut Self {
self.operations.push(WriteOperation::DeleteHeader(height));
self
}
pub fn delete_tip_block(&mut self, block_hash: HashOutput) -> &mut Self {
self.operations.push(WriteOperation::DeleteTipBlock(block_hash));
self
}
pub fn delete_block_accumulated_data(&mut self, height: u64) -> &mut Self {
self.operations.push(WriteOperation::DeleteBlockAccumulatedData(height));
self
}
pub fn insert_kernel(
&mut self,
kernel: TransactionKernel,
header_hash: HashOutput,
mmr_position: u64,
) -> &mut Self {
self.operations.push(WriteOperation::InsertKernel {
header_hash,
kernel: Box::new(kernel),
mmr_position,
});
self
}
pub fn insert_chain_header(&mut self, chain_header: ChainHeader) -> &mut Self {
self.operations.push(WriteOperation::InsertChainHeader {
header: Box::new(chain_header),
});
self
}
pub fn insert_utxo(
&mut self,
utxo: TransactionOutput,
header_hash: HashOutput,
header_height: u64,
timestamp: u64,
) -> &mut Self {
self.operations.push(WriteOperation::InsertOutput {
header_hash,
header_height,
timestamp,
output: Box::new(utxo),
});
self
}
pub fn prune_outputs_spent_at_hash(&mut self, block_hash: BlockHash) -> &mut Self {
self.operations
.push(WriteOperation::PruneOutputsSpentAtHash { block_hash });
self
}
pub fn prune_output_from_all_dbs(
&mut self,
output_hash: HashOutput,
commitment: CompressedCommitment,
output_type: OutputType,
) -> &mut Self {
self.operations.push(WriteOperation::PruneOutputFromAllDbs {
output_hash,
commitment,
output_type,
});
self
}
pub fn delete_validator_node(
&mut self,
sidechain_public_key: Option<CompressedPublicKey>,
public_key: CompressedPublicKey,
) -> &mut Self {
self.operations.push(WriteOperation::DeleteValidatorNode {
sidechain_public_key,
public_key,
});
self
}
pub fn delete_all_kernerls_in_block(&mut self, block_hash: BlockHash) -> &mut Self {
self.operations
.push(WriteOperation::DeleteAllKernelsInBlock { block_hash });
self
}
pub fn delete_all_inputs_in_block(&mut self, block_hash: BlockHash) -> &mut Self {
self.operations
.push(WriteOperation::DeleteAllInputsInBlock { block_hash });
self
}
pub fn update_block_accumulated_data(
&mut self,
header_hash: HashOutput,
values: UpdateBlockAccumulatedData,
) -> &mut Self {
self.operations
.push(WriteOperation::UpdateBlockAccumulatedData { header_hash, values });
self
}
pub fn insert_tip_block_body(&mut self, block: Arc<ChainBlock>) -> &mut Self {
self.operations.push(WriteOperation::InsertTipBlockBody { block });
self
}
pub fn insert_bad_block(&mut self, block_hash: HashOutput, height: u64, reason: String) -> &mut Self {
self.operations.push(WriteOperation::InsertBadBlock {
hash: block_hash,
height,
reason,
});
self
}
pub fn insert_orphan(&mut self, orphan: Arc<Block>) -> &mut Self {
self.operations.push(WriteOperation::InsertOrphanBlock(orphan));
self
}
pub fn insert_chained_orphan(&mut self, orphan: Arc<ChainBlock>) -> &mut Self {
self.operations.push(WriteOperation::InsertChainOrphanBlock(orphan));
self
}
pub fn remove_orphan_chain_tip(&mut self, hash: HashOutput) -> &mut Self {
self.operations.push(WriteOperation::DeleteOrphanChainTip(hash));
self
}
pub fn insert_orphan_chain_tip(&mut self, hash: HashOutput, total_accumulated_difficulty: U512) -> &mut Self {
self.operations
.push(WriteOperation::InsertOrphanChainTip(hash, total_accumulated_difficulty));
self
}
pub fn set_accumulated_data_for_orphan(
&mut self,
block_version: u16,
accumulated_data: BlockHeaderAccumulatedData,
) -> &mut Self {
self.operations.push(WriteOperation::SetAccumulatedDataForOrphan {
version: block_version,
data: accumulated_data,
});
self
}
pub fn set_best_block(
&mut self,
height: u64,
hash: HashOutput,
accumulated_difficulty: U512,
expected_prev_best_block: HashOutput,
timestamp: u64,
) -> &mut Self {
self.operations.push(WriteOperation::SetBestBlock {
height,
hash,
accumulated_difficulty,
expected_prev_best_block,
timestamp,
});
self
}
pub fn set_pruning_horizon(&mut self, pruning_horizon: u64) -> &mut Self {
self.operations
.push(WriteOperation::SetPruningHorizonConfig(pruning_horizon));
self
}
pub fn set_pruned_height(&mut self, height: u64) -> &mut Self {
self.operations.push(WriteOperation::SetPrunedHeight { height });
self
}
pub fn set_horizon_data(&mut self, kernel_sum: CompressedCommitment, utxo_sum: CompressedCommitment) -> &mut Self {
self.operations.push(WriteOperation::SetHorizonData {
horizon_data: HorizonData::new(kernel_sum, utxo_sum),
});
self
}
pub fn set_horizon_sync_output_checkpoint(&mut self, checkpoint: HorizonSyncOutputCheckpoint) -> &mut Self {
self.operations.push(WriteOperation::SetHorizonSyncOutputCheckpoint {
checkpoint: Some(checkpoint),
});
self
}
pub fn clear_horizon_sync_output_checkpoint(&mut self) -> &mut Self {
self.operations
.push(WriteOperation::SetHorizonSyncOutputCheckpoint { checkpoint: None });
self
}
pub fn apply_horizon_state_tree_updates(
&mut self,
previous_version: u64,
version: u64,
updates: Vec<HorizonStateTreeUpdate>,
) -> &mut Self {
self.operations.push(WriteOperation::ApplyHorizonStateTreeUpdates {
previous_version,
version,
updates,
});
self
}
pub(crate) fn operations(&self) -> &[WriteOperation] {
&self.operations
}
pub fn insert_monero_seed_height(&mut self, monero_seed: Vec<u8>, height: u64) {
self.operations
.push(WriteOperation::InsertMoneroSeedHeight(monero_seed, height));
}
pub fn insert_reorg(&mut self, reorg: Reorg) -> &mut Self {
self.operations.push(WriteOperation::InsertReorg { reorg });
self
}
pub fn clear_all_reorgs(&mut self) -> &mut Self {
self.operations.push(WriteOperation::ClearAllReorgs);
self
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum WriteOperation {
InsertOrphanBlock(Arc<Block>),
InsertChainOrphanBlock(Arc<ChainBlock>),
InsertChainHeader {
header: Box<ChainHeader>,
},
InsertTipBlockBody {
block: Arc<ChainBlock>,
},
InsertKernel {
header_hash: HashOutput,
kernel: Box<TransactionKernel>,
mmr_position: u64,
},
InsertOutput {
header_hash: HashOutput,
header_height: u64,
timestamp: u64,
output: Box<TransactionOutput>,
},
InsertBadBlock {
hash: HashOutput,
height: u64,
reason: String,
},
DeleteHeader(u64),
DeleteOrphan(HashOutput),
DeleteTipBlock(HashOutput),
DeleteBlockAccumulatedData(u64),
DeleteOrphanChainTip(HashOutput),
InsertOrphanChainTip(HashOutput, U512),
InsertMoneroSeedHeight(Vec<u8>, u64),
UpdateBlockAccumulatedData {
header_hash: HashOutput,
values: UpdateBlockAccumulatedData,
},
PruneOutputsSpentAtHash {
block_hash: BlockHash,
},
PruneOutputFromAllDbs {
output_hash: HashOutput,
commitment: CompressedCommitment,
output_type: OutputType,
},
DeleteAllKernelsInBlock {
block_hash: BlockHash,
},
DeleteAllInputsInBlock {
block_hash: BlockHash,
},
SetAccumulatedDataForOrphan {
version: u16,
data: BlockHeaderAccumulatedData,
},
SetBestBlock {
height: u64,
hash: HashOutput,
accumulated_difficulty: U512,
expected_prev_best_block: HashOutput,
timestamp: u64,
},
SetPruningHorizonConfig(u64),
SetPrunedHeight {
height: u64,
},
SetHorizonData {
horizon_data: HorizonData,
},
ApplyHorizonStateTreeUpdates {
previous_version: u64,
version: u64,
updates: Vec<HorizonStateTreeUpdate>,
},
InsertReorg {
reorg: Reorg,
},
ClearAllReorgs,
DeleteValidatorNode {
sidechain_public_key: Option<CompressedPublicKey>,
public_key: CompressedPublicKey,
},
SetHorizonSyncOutputCheckpoint {
checkpoint: Option<HorizonSyncOutputCheckpoint>,
},
}
#[allow(clippy::too_many_lines)]
impl fmt::Display for WriteOperation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[allow(clippy::enum_glob_use)]
use WriteOperation::*;
match self {
InsertOrphanBlock(block) => write!(
f,
"InsertOrphanBlock({}, {})",
block.hash(),
block.body.to_counts_string()
),
InsertChainHeader { header } => {
write!(f, "InsertChainHeader(#{} {})", header.height(), header.hash())
},
InsertTipBlockBody { block } => write!(
f,
"InsertTipBlockBody({}, {})",
block.accumulated_data().hash,
block.block().body.to_counts_string(),
),
InsertKernel {
header_hash,
kernel,
mmr_position,
} => write!(
f,
"Insert kernel {} in block:{} position: {}",
kernel.hash(),
header_hash,
mmr_position
),
InsertOutput {
header_hash,
header_height,
output,
..
} => write!(
f,
"Insert output {} in block({}):{},",
output.hash(),
header_height,
header_hash,
),
DeleteOrphanChainTip(hash) => write!(f, "DeleteOrphanChainTip({hash})",),
InsertOrphanChainTip(hash, total_accumulated_difficulty) => {
write!(f, "InsertOrphanChainTip({hash}, {total_accumulated_difficulty})")
},
DeleteTipBlock(hash) => write!(f, "DeleteTipBlock({hash})"),
DeleteBlockAccumulatedData(height) => write!(f, "DeleteBlockAccumulatedData({height})"),
InsertMoneroSeedHeight(data, height) => {
write!(f, "Insert Monero seed string {} for height: {}", data.to_hex(), height)
},
InsertChainOrphanBlock(block) => write!(f, "InsertChainOrphanBlock({})", block.hash()),
UpdateBlockAccumulatedData { header_hash, .. } => {
write!(f, "Update Block data for block {header_hash}")
},
PruneOutputsSpentAtHash { block_hash } => write!(f, "Prune output(s) at hash: {block_hash}"),
PruneOutputFromAllDbs {
output_hash,
commitment,
output_type,
} => write!(
f,
"Prune output from all dbs, hash : {}, commitment: {},output_type: {}",
output_hash,
commitment.to_hex(),
output_type,
),
DeleteAllKernelsInBlock { block_hash } => write!(f, "Delete kernels in block {block_hash}"),
DeleteAllInputsInBlock { block_hash } => write!(f, "Delete outputs in block {block_hash}"),
SetAccumulatedDataForOrphan { version, data } => {
write!(f, "Set accumulated data for orphan {data} version {version}")
},
SetBestBlock {
height,
hash,
accumulated_difficulty,
expected_prev_best_block: _,
timestamp,
} => write!(
f,
"Update best block to height:{height} ({hash}) with difficulty: {accumulated_difficulty} and \
timestamp: {timestamp}"
),
SetPruningHorizonConfig(pruning_horizon) => write!(f, "Set config: pruning horizon to {pruning_horizon}"),
SetPrunedHeight { height, .. } => write!(f, "Set pruned height to {height}"),
DeleteHeader(height) => write!(f, "Delete header at height: {height}"),
DeleteOrphan(hash) => write!(f, "Delete orphan with hash: {hash}"),
InsertBadBlock { hash, height, reason } => {
write!(f, "Insert bad block #{height} {hash} for {reason}")
},
SetHorizonData { .. } => write!(f, "Set horizon data"),
ApplyHorizonStateTreeUpdates { version, updates, .. } => {
write!(
f,
"Apply horizon state tree updates at version {version} ({} updates)",
updates.len()
)
},
InsertReorg { .. } => write!(f, "Insert reorg"),
ClearAllReorgs => write!(f, "Clear all reorgs"),
DeleteValidatorNode { public_key, .. } => {
write!(f, "Delete validator node with public key: {public_key}")
},
SetHorizonSyncOutputCheckpoint { checkpoint: Some(cp) } => {
write!(
f,
"Set horizon sync output checkpoint to height {} ({}) targeting height {} ({})",
cp.checkpoint_height,
cp.checkpoint_hash.to_hex(),
cp.sync_target_height,
cp.sync_target_hash.to_hex()
)
},
SetHorizonSyncOutputCheckpoint { checkpoint: None } => {
write!(f, "Clear horizon sync output checkpoint")
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DbKey {
HeaderHeight(u64),
HeaderHash(BlockHash),
OrphanBlock(HashOutput),
}
impl DbKey {
pub fn to_value_not_found_error(&self) -> ChainStorageError {
let (entity, field, value) = match self {
DbKey::HeaderHeight(v) => ("BlockHeader", "Height", v.to_string()),
DbKey::HeaderHash(v) => ("Header", "Hash", v.to_hex()),
DbKey::OrphanBlock(v) => ("Orphan", "Hash", v.to_hex()),
};
ChainStorageError::ValueNotFound { entity, field, value }
}
}
#[derive(Debug)]
pub enum DbValue {
HeaderHeight(Box<BlockHeader>),
HeaderHash(Box<BlockHeader>),
OrphanBlock(Box<Block>),
}
impl DbValue {
pub fn into_header(self) -> Option<BlockHeader> {
match self {
DbValue::HeaderHeight(bh) | DbValue::HeaderHash(bh) => Some(*bh),
DbValue::OrphanBlock(_) => None,
}
}
}
impl Display for DbValue {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
DbValue::HeaderHeight(_) => f.write_str("Header by height"),
DbValue::HeaderHash(_) => f.write_str("Header by hash"),
DbValue::OrphanBlock(_) => f.write_str("Orphan block"),
}
}
}
impl Display for DbKey {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
DbKey::HeaderHeight(v) => f.write_str(&format!("Header height (#{v})")),
DbKey::HeaderHash(v) => f.write_str(&format!("Header hash (#{v})")),
DbKey::OrphanBlock(v) => f.write_str(&format!("Orphan block hash ({v})")),
}
}
}