use std::sync::Arc;
use kaspa_consensus_core::{
blockhash::{self, BlockHashExtensions, BlockHashes},
BlockHashMap, BlueWorkType, HashMapCustomHasher,
};
use kaspa_hashes::Hash;
use kaspa_utils::refs::Refs;
use crate::{
model::{
services::reachability::ReachabilityService,
stores::{
ghostdag::{GhostdagData, GhostdagStoreReader, HashKTypeMap, KType},
headers::HeaderStoreReader,
relations::RelationsStoreReader,
},
},
processes::difficulty::calc_work,
};
use super::ordering::*;
#[derive(Clone)]
pub struct GhostdagManager<T: GhostdagStoreReader, S: RelationsStoreReader, U: ReachabilityService, V: HeaderStoreReader> {
genesis_hash: Hash,
pub(super) k: KType,
pub(super) ghostdag_store: Arc<T>,
pub(super) relations_store: S,
pub(super) headers_store: Arc<V>,
pub(super) reachability_service: U,
}
impl<T: GhostdagStoreReader, S: RelationsStoreReader, U: ReachabilityService, V: HeaderStoreReader> GhostdagManager<T, S, U, V> {
pub fn new(
genesis_hash: Hash,
k: KType,
ghostdag_store: Arc<T>,
relations_store: S,
headers_store: Arc<V>,
reachability_service: U,
) -> Self {
Self { genesis_hash, k, ghostdag_store, relations_store, reachability_service, headers_store }
}
pub fn genesis_ghostdag_data(&self) -> GhostdagData {
GhostdagData::new(
0,
Default::default(), blockhash::ORIGIN,
BlockHashes::new(Vec::new()),
BlockHashes::new(Vec::new()),
HashKTypeMap::new(BlockHashMap::new()),
)
}
pub fn origin_ghostdag_data(&self) -> Arc<GhostdagData> {
Arc::new(GhostdagData::new(
0,
Default::default(),
0.into(),
BlockHashes::new(Vec::new()),
BlockHashes::new(Vec::new()),
HashKTypeMap::new(BlockHashMap::new()),
))
}
pub fn find_selected_parent(&self, parents: impl IntoIterator<Item = Hash>) -> Hash {
parents
.into_iter()
.map(|parent| SortableBlock { hash: parent, blue_work: self.ghostdag_store.get_blue_work(parent).unwrap() })
.max()
.unwrap()
.hash
}
pub fn ghostdag(&self, parents: &[Hash]) -> GhostdagData {
assert!(!parents.is_empty(), "genesis must be added via a call to init");
let selected_parent = self.find_selected_parent(&mut parents.iter().copied());
let mut new_block_data = GhostdagData::new_with_selected_parent(selected_parent, self.k);
let ordered_mergeset = self.ordered_mergeset_without_selected_parent(selected_parent, parents);
for blue_candidate in ordered_mergeset.iter().cloned() {
let coloring = self.check_blue_candidate(&new_block_data, blue_candidate);
if let ColoringOutput::Blue(blue_anticone_size, blues_anticone_sizes) = coloring {
new_block_data.add_blue(blue_candidate, blue_anticone_size, &blues_anticone_sizes);
} else {
new_block_data.add_red(blue_candidate);
}
}
let blue_score = self.ghostdag_store.get_blue_score(selected_parent).unwrap() + new_block_data.mergeset_blues.len() as u64;
let added_blue_work: BlueWorkType = new_block_data
.mergeset_blues
.iter()
.cloned()
.map(|hash| if hash.is_origin() { 0.into() } else { calc_work(self.headers_store.get_bits(hash).unwrap()) })
.sum();
let blue_work = self.ghostdag_store.get_blue_work(selected_parent).unwrap() + added_blue_work;
new_block_data.finalize_score_and_work(blue_score, blue_work);
new_block_data
}
fn check_blue_candidate_with_chain_block(
&self,
new_block_data: &GhostdagData,
chain_block: &ChainBlock,
blue_candidate: Hash,
candidate_blues_anticone_sizes: &mut BlockHashMap<KType>,
candidate_blue_anticone_size: &mut KType,
) -> ColoringState {
if let Some(hash) = chain_block.hash {
if self.reachability_service.is_dag_ancestor_of(hash, blue_candidate) {
return ColoringState::Blue;
}
}
for &block in chain_block.data.mergeset_blues.iter() {
if self.reachability_service.is_dag_ancestor_of(block, blue_candidate) {
continue;
}
candidate_blues_anticone_sizes.insert(block, self.blue_anticone_size(block, new_block_data));
*candidate_blue_anticone_size += 1;
if *candidate_blue_anticone_size > self.k {
return ColoringState::Red;
}
if *candidate_blues_anticone_sizes.get(&block).unwrap() == self.k {
return ColoringState::Red;
}
assert!(*candidate_blues_anticone_sizes.get(&block).unwrap() <= self.k, "found blue anticone larger than K");
}
ColoringState::Pending
}
fn blue_anticone_size(&self, block: Hash, context: &GhostdagData) -> KType {
let mut current_blues_anticone_sizes = HashKTypeMap::clone(&context.blues_anticone_sizes);
let mut current_selected_parent = context.selected_parent;
loop {
if let Some(size) = current_blues_anticone_sizes.get(&block) {
return *size;
}
if current_selected_parent == self.genesis_hash || current_selected_parent == blockhash::ORIGIN {
panic!("block {block} is not in blue set of the given context");
}
current_blues_anticone_sizes = self.ghostdag_store.get_blues_anticone_sizes(current_selected_parent).unwrap();
current_selected_parent = self.ghostdag_store.get_selected_parent(current_selected_parent).unwrap();
}
}
fn check_blue_candidate(&self, new_block_data: &GhostdagData, blue_candidate: Hash) -> ColoringOutput {
if new_block_data.mergeset_blues.len() as KType == self.k + 1 {
return ColoringOutput::Red;
}
let mut candidate_blues_anticone_sizes: BlockHashMap<KType> = BlockHashMap::with_capacity(self.k as usize);
let mut chain_block = ChainBlock { hash: None, data: new_block_data.into() };
let mut candidate_blue_anticone_size: KType = 0;
loop {
let state = self.check_blue_candidate_with_chain_block(
new_block_data,
&chain_block,
blue_candidate,
&mut candidate_blues_anticone_sizes,
&mut candidate_blue_anticone_size,
);
match state {
ColoringState::Blue => return ColoringOutput::Blue(candidate_blue_anticone_size, candidate_blues_anticone_sizes),
ColoringState::Red => return ColoringOutput::Red,
ColoringState::Pending => (), }
chain_block = ChainBlock {
hash: Some(chain_block.data.selected_parent),
data: self.ghostdag_store.get_data(chain_block.data.selected_parent).unwrap().into(),
}
}
}
}
struct ChainBlock<'a> {
hash: Option<Hash>, data: Refs<'a, GhostdagData>,
}
enum ColoringState {
Blue,
Red,
Pending,
}
enum ColoringOutput {
Blue(KType, BlockHashMap<KType>), Red,
}