use super::interval::Interval;
use super::{tree::*, *};
use crate::model::stores::reachability::{ReachabilityStore, ReachabilityStoreReader};
use kaspa_consensus_core::blockhash;
use kaspa_hashes::Hash;
pub fn init(store: &mut (impl ReachabilityStore + ?Sized)) -> Result<()> {
init_with_params(store, blockhash::ORIGIN, Interval::maximal())
}
pub(super) fn init_with_params(store: &mut (impl ReachabilityStore + ?Sized), origin: Hash, capacity: Interval) -> Result<()> {
if store.has(origin)? {
return Ok(());
}
store.init(origin, capacity)?;
Ok(())
}
type HashIterator<'a> = &'a mut dyn Iterator<Item = Hash>;
pub fn add_block(
store: &mut (impl ReachabilityStore + ?Sized),
new_block: Hash,
selected_parent: Hash,
mergeset_iterator: HashIterator,
) -> Result<()> {
add_block_with_params(store, new_block, selected_parent, mergeset_iterator, None, None)
}
fn add_block_with_params(
store: &mut (impl ReachabilityStore + ?Sized),
new_block: Hash,
selected_parent: Hash,
mergeset_iterator: HashIterator,
reindex_depth: Option<u64>,
reindex_slack: Option<u64>,
) -> Result<()> {
add_tree_block(
store,
new_block,
selected_parent,
reindex_depth.unwrap_or(crate::constants::perf::DEFAULT_REINDEX_DEPTH),
reindex_slack.unwrap_or(crate::constants::perf::DEFAULT_REINDEX_SLACK),
)?;
add_dag_block(store, new_block, mergeset_iterator)?;
Ok(())
}
fn add_dag_block(store: &mut (impl ReachabilityStore + ?Sized), new_block: Hash, mergeset_iterator: HashIterator) -> Result<()> {
for merged_block in mergeset_iterator {
insert_to_future_covering_set(store, merged_block, new_block)?;
}
Ok(())
}
fn insert_to_future_covering_set(store: &mut (impl ReachabilityStore + ?Sized), merged_block: Hash, new_block: Hash) -> Result<()> {
match binary_search_descendant(store, store.get_future_covering_set(merged_block)?.as_slice(), new_block)? {
SearchOutput::Found(_, _) => Err(ReachabilityError::DataInconsistency),
SearchOutput::NotFound(i) => {
store.insert_future_covering_item(merged_block, new_block, i)?;
Ok(())
}
}
}
pub fn hint_virtual_selected_parent(store: &mut (impl ReachabilityStore + ?Sized), hint: Hash) -> Result<()> {
try_advancing_reindex_root(
store,
hint,
crate::constants::perf::DEFAULT_REINDEX_DEPTH,
crate::constants::perf::DEFAULT_REINDEX_SLACK,
)
}
pub fn is_strict_chain_ancestor_of(store: &(impl ReachabilityStoreReader + ?Sized), this: Hash, queried: Hash) -> Result<bool> {
Ok(store.get_interval(this)?.strictly_contains(store.get_interval(queried)?))
}
pub fn is_chain_ancestor_of(store: &(impl ReachabilityStoreReader + ?Sized), this: Hash, queried: Hash) -> Result<bool> {
Ok(store.get_interval(this)?.contains(store.get_interval(queried)?))
}
pub fn is_dag_ancestor_of(store: &(impl ReachabilityStoreReader + ?Sized), this: Hash, queried: Hash) -> Result<bool> {
if is_chain_ancestor_of(store, this, queried)? {
return Ok(true);
}
match binary_search_descendant(store, store.get_future_covering_set(this)?.as_slice(), queried)? {
SearchOutput::Found(_, _) => Ok(true),
SearchOutput::NotFound(_) => Ok(false),
}
}
pub fn get_next_chain_ancestor(store: &(impl ReachabilityStoreReader + ?Sized), descendant: Hash, ancestor: Hash) -> Result<Hash> {
if descendant == ancestor {
return Err(ReachabilityError::BadQuery);
}
if !is_strict_chain_ancestor_of(store, ancestor, descendant)? {
return Err(ReachabilityError::BadQuery);
}
get_next_chain_ancestor_unchecked(store, descendant, ancestor)
}
pub(super) fn get_next_chain_ancestor_unchecked(
store: &(impl ReachabilityStoreReader + ?Sized),
descendant: Hash,
ancestor: Hash,
) -> Result<Hash> {
match binary_search_descendant(store, store.get_children(ancestor)?.as_slice(), descendant)? {
SearchOutput::Found(hash, _) => Ok(hash),
SearchOutput::NotFound(_) => Err(ReachabilityError::BadQuery),
}
}
enum SearchOutput {
NotFound(usize), Found(Hash, usize),
}
fn binary_search_descendant(
store: &(impl ReachabilityStoreReader + ?Sized),
ordered_hashes: &[Hash],
descendant: Hash,
) -> Result<SearchOutput> {
if cfg!(debug_assertions) {
assert_hashes_ordered(store, ordered_hashes);
}
let point = store.get_interval(descendant)?.end;
match ordered_hashes.binary_search_by_key(&point, |c| store.get_interval(*c).unwrap().start) {
Ok(i) => Ok(SearchOutput::Found(ordered_hashes[i], i)),
Err(i) => {
if i > 0 && is_chain_ancestor_of(store, ordered_hashes[i - 1], descendant)? {
Ok(SearchOutput::Found(ordered_hashes[i - 1], i - 1))
} else {
Ok(SearchOutput::NotFound(i))
}
}
}
}
fn assert_hashes_ordered(store: &(impl ReachabilityStoreReader + ?Sized), ordered_hashes: &[Hash]) {
let intervals: Vec<Interval> = ordered_hashes.iter().cloned().map(|c| store.get_interval(c).unwrap()).collect();
debug_assert!(intervals.as_slice().windows(2).all(|w| w[0].end < w[1].start))
}
#[cfg(test)]
mod tests {
use super::super::tests::*;
use super::*;
use crate::{model::stores::reachability::MemoryReachabilityStore, processes::reachability::interval::Interval};
#[test]
fn test_add_tree_blocks() {
let mut store = MemoryReachabilityStore::new();
let root: Hash = 1.into();
TreeBuilder::new(&mut store)
.init_with_params(root, Interval::new(1, 15))
.add_block(2.into(), root)
.add_block(3.into(), 2.into())
.add_block(4.into(), 2.into())
.add_block(5.into(), 3.into())
.add_block(6.into(), 5.into())
.add_block(7.into(), 1.into())
.add_block(8.into(), 6.into())
.add_block(9.into(), 6.into())
.add_block(10.into(), 6.into())
.add_block(11.into(), 6.into());
store.validate_intervals(root).unwrap();
}
#[test]
fn test_add_early_blocks() {
let mut store = MemoryReachabilityStore::new();
let root: Hash = 1.into();
let mut builder = TreeBuilder::new_with_params(&mut store, 2, 5);
builder.init_with_params(root, Interval::maximal());
for i in 2u64..100 {
builder.add_block(i.into(), (i / 2).into());
}
builder.add_block(100.into(), 2.into());
store.validate_intervals(root).unwrap();
}
#[test]
fn test_add_dag_blocks() {
let mut store = MemoryReachabilityStore::new();
DagBuilder::new(&mut store)
.init()
.add_block(DagBlock::new(1.into(), vec![blockhash::ORIGIN]))
.add_block(DagBlock::new(2.into(), vec![1.into()]))
.add_block(DagBlock::new(3.into(), vec![1.into()]))
.add_block(DagBlock::new(4.into(), vec![2.into(), 3.into()]))
.add_block(DagBlock::new(5.into(), vec![4.into()]))
.add_block(DagBlock::new(6.into(), vec![1.into()]))
.add_block(DagBlock::new(7.into(), vec![5.into(), 6.into()]))
.add_block(DagBlock::new(8.into(), vec![1.into()]))
.add_block(DagBlock::new(9.into(), vec![1.into()]))
.add_block(DagBlock::new(10.into(), vec![7.into(), 8.into(), 9.into()]))
.add_block(DagBlock::new(11.into(), vec![1.into()]))
.add_block(DagBlock::new(12.into(), vec![11.into(), 10.into()]));
store.validate_intervals(blockhash::ORIGIN).unwrap();
for i in 2u64..=12 {
assert!(store.in_past_of(1, i));
}
assert!(store.in_past_of(2, 4));
assert!(store.in_past_of(2, 5));
assert!(store.in_past_of(2, 7));
assert!(store.in_past_of(5, 10));
assert!(store.in_past_of(6, 10));
assert!(store.in_past_of(10, 12));
assert!(store.in_past_of(11, 12));
assert!(store.are_anticone(2, 3));
assert!(store.are_anticone(2, 6));
assert!(store.are_anticone(3, 6));
assert!(store.are_anticone(5, 6));
assert!(store.are_anticone(3, 8));
assert!(store.are_anticone(11, 2));
assert!(store.are_anticone(11, 4));
assert!(store.are_anticone(11, 6));
assert!(store.are_anticone(11, 9));
}
}