use std::collections::VecDeque;
use massa_consensus_exports::{
block_status::{BlockStatus, DiscardReason},
error::ConsensusError,
};
use massa_logging::massa_trace;
use massa_models::{
block_id::{BlockId, BlockIdSerializer},
clique::Clique,
prehash::PreHashSet,
slot::Slot,
};
use massa_serialization::Serializer;
use super::ConsensusState;
impl ConsensusState {
pub fn insert_parents_descendants(
&mut self,
add_block_id: BlockId,
add_block_slot: Slot,
parents_hash: Vec<BlockId>,
) {
for parent_h in parents_hash.iter() {
if let Some(BlockStatus::Active {
a_block: a_parent, ..
}) = self.blocks_state.get_mut(parent_h)
{
a_parent.children[add_block_slot.thread as usize]
.insert(add_block_id, add_block_slot.period);
}
}
let same_thread_parent_creator =
parents_hash
.get(add_block_slot.thread as usize)
.and_then(|parent_id| {
if let Some(BlockStatus::Active { a_block, .. }) =
self.blocks_state.get(parent_id)
{
Some(a_block.creator_address)
} else {
None
}
});
if let Some(BlockStatus::Active { a_block, .. }) = self.blocks_state.get_mut(&add_block_id)
{
a_block.same_thread_parent_creator = same_thread_parent_creator;
} else {
panic!("block status should be active");
};
let mut ancestors: VecDeque<BlockId> = parents_hash.iter().copied().collect();
let mut visited = PreHashSet::<BlockId>::default();
while let Some(ancestor_h) = ancestors.pop_back() {
if !visited.insert(ancestor_h) {
continue;
}
if let Some(BlockStatus::Active { a_block: ab, .. }) =
self.blocks_state.get_mut(&ancestor_h)
{
if ab.is_final {
continue;
}
ab.descendants.insert(add_block_id);
for (ancestor_parent_h, _) in ab.parents.iter() {
ancestors.push_front(*ancestor_parent_h);
}
}
}
}
pub fn compute_fitness_find_blockclique(
&mut self,
add_block_id: &BlockId,
) -> Result<usize, ConsensusError> {
let block_id_serializer = BlockIdSerializer::new();
let mut blockclique_i = 0usize;
let mut max_clique_fitness = (0u64, num::BigInt::default());
for (clique_i, clique) in self.max_cliques.iter_mut().enumerate() {
clique.fitness = 0;
clique.is_blockclique = false;
let mut sum_hash = num::BigInt::default();
for block_h in clique.block_ids.iter() {
let fitness = match self.blocks_state.get(block_h) {
Some(BlockStatus::Active { a_block, .. }) => a_block.fitness,
_ => return Err(ConsensusError::ContainerInconsistency(format!("inconsistency inside block statuses computing fitness while adding {} - missing {}", add_block_id, block_h))),
};
clique.fitness = clique
.fitness
.checked_add(fitness)
.ok_or(ConsensusError::FitnessOverflow)?;
let mut bytes = Vec::new();
block_id_serializer
.serialize(block_h, &mut bytes)
.map_err(|err| ConsensusError::SerializationError(err.to_string()))?;
sum_hash -= num::BigInt::from_bytes_be(num::bigint::Sign::Plus, &bytes);
}
let cur_fit = (clique.fitness, sum_hash);
if cur_fit > max_clique_fitness {
blockclique_i = clique_i;
max_clique_fitness = cur_fit;
}
}
self.max_cliques[blockclique_i].is_blockclique = true;
Ok(blockclique_i)
}
pub fn list_stale_blocks(&self, fitness_threshold: u64) -> PreHashSet<BlockId> {
let mut indices: Vec<usize> = (0..self.max_cliques.len()).collect();
indices.sort_unstable_by_key(|&i| std::cmp::Reverse(self.max_cliques[i].block_ids.len()));
let mut high_set = PreHashSet::<BlockId>::default();
let mut low_set = PreHashSet::<BlockId>::default();
for clique_i in indices.into_iter() {
if self.max_cliques[clique_i].fitness >= fitness_threshold {
high_set.extend(&self.max_cliques[clique_i].block_ids);
} else {
low_set.extend(&self.max_cliques[clique_i].block_ids);
}
}
&low_set - &high_set
}
pub fn remove_block(&mut self, add_block_id: &BlockId, block_id: &BlockId) {
let sequence_number = self.blocks_state.sequence_counter();
self.blocks_state.transition_map(block_id, |block_status, block_statuses| {
if let Some(BlockStatus::Active {
a_block: active_block,
..
}) = block_status
{
if active_block.is_final {
panic!("inconsistency inside block statuses removing stale blocks adding {} - block {} was already final", add_block_id, block_id);
}
if let Some(other_incomps) = self.gi_head.remove(block_id) {
for other_incomp in other_incomps.into_iter() {
if let Some(other_incomp_lst) = self.gi_head.get_mut(&other_incomp) {
other_incomp_lst.remove(block_id);
}
}
}
let stale_block_fitness = active_block.fitness;
self.max_cliques.iter_mut().for_each(|c| {
if c.block_ids.remove(block_id) {
c.fitness -= stale_block_fitness;
}
});
self.max_cliques.retain(|c| !c.block_ids.is_empty()); if self.max_cliques.is_empty() {
self.max_cliques = vec![Clique {
block_ids: PreHashSet::<BlockId>::default(),
fitness: 0,
is_blockclique: true,
}];
}
for (parent_h, _parent_period) in active_block.parents.iter() {
if let Some(BlockStatus::Active {
a_block: parent_active_block,
..
}) = block_statuses.get_mut(parent_h)
{
parent_active_block.children[active_block.slot.thread as usize]
.remove(block_id);
}
}
massa_trace!("consensus.block_graph.add_block_to_graph.stale", {
"hash": block_id
});
self.new_stale_blocks
.insert(*block_id, (active_block.creator_address, active_block.slot));
Some(
BlockStatus::Discarded {
slot: active_block.slot,
creator: active_block.creator_address,
parents: active_block.parents.iter().map(|(h, _)| *h).collect(),
reason: DiscardReason::Stale,
sequence_number,
}
)
} else {
panic!("inconsistency inside block statuses removing stale blocks adding {} - block {} is missing", add_block_id, block_id);
}
});
}
pub fn list_final_blocks(&self) -> Result<PreHashSet<BlockId>, ConsensusError> {
let mut indices: Vec<usize> = (0..self.max_cliques.len()).collect();
indices.sort_unstable_by_key(|&i| self.max_cliques[i].block_ids.len());
let mut indices_iter = indices.iter();
let mut final_candidates = self.max_cliques
[*indices_iter.next().expect("expected at least one clique")]
.block_ids
.clone();
for i in indices_iter {
final_candidates.retain(|v| self.max_cliques[*i].block_ids.contains(v));
if final_candidates.is_empty() {
break;
}
}
massa_trace!(
"consensus.block_graph.add_block_to_graph.list_final_blocks.restrict",
{}
);
indices.retain(|&i| self.max_cliques[i].fitness > self.config.delta_f0);
indices.sort_unstable_by_key(|&i| std::cmp::Reverse(self.max_cliques[i].fitness));
let mut final_blocks = PreHashSet::<BlockId>::default();
for clique_i in indices.into_iter() {
massa_trace!(
"consensus.block_graph.add_block_to_graph.list_final_blocks.loop",
{ "clique_i": clique_i }
);
if final_candidates.is_empty() {
break;
}
let clique = &self.max_cliques[clique_i];
let loc_candidates = final_candidates.clone();
for candidate_h in loc_candidates.into_iter() {
let descendants = match self.blocks_state.get(&candidate_h) {
Some(BlockStatus::Active { a_block, .. }) => &a_block.descendants,
_ => {
return Err(ConsensusError::MissingBlock(format!(
"missing block when computing total fitness of descendants: {}",
candidate_h
)))
}
};
let desc_fit: u64 = descendants
.intersection(&clique.block_ids)
.map(|h| {
if let Some(BlockStatus::Active { a_block: ab, .. }) =
self.blocks_state.get(h)
{
return ab.fitness;
}
0
})
.sum();
if desc_fit > self.config.delta_f0 {
final_candidates.remove(&candidate_h);
final_blocks.insert(candidate_h);
}
}
}
Ok(final_blocks)
}
pub fn get_blockclique(&self) -> PreHashSet<BlockId> {
self.max_cliques
.iter()
.find(|c| c.is_blockclique)
.expect("blockclique missing")
.block_ids
.clone()
}
pub fn mark_final_blocks(
&mut self,
add_block_id: &BlockId,
final_blocks: PreHashSet<BlockId>,
) -> Result<(), ConsensusError> {
for block_id in final_blocks.into_iter() {
if let Some(other_incomps) = self.gi_head.remove(&block_id) {
for other_incomp in other_incomps.into_iter() {
if let Some(other_incomp_lst) = self.gi_head.get_mut(&other_incomp) {
other_incomp_lst.remove(&block_id);
}
}
}
if let Some(BlockStatus::Active {
a_block: final_block,
..
}) = self.blocks_state.get_mut(&block_id)
{
massa_trace!("consensus.block_graph.add_block_to_graph.final", {
"hash": block_id
});
final_block.is_final = true;
let final_block_fitness = final_block.fitness;
self.max_cliques.iter_mut().for_each(|c| {
if c.block_ids.remove(&block_id) {
c.fitness -= final_block_fitness;
}
});
self.max_cliques.retain(|c| !c.block_ids.is_empty()); if self.max_cliques.is_empty() {
self.max_cliques = vec![Clique {
block_ids: PreHashSet::<BlockId>::default(),
fitness: 0,
is_blockclique: true,
}];
}
if final_block.slot.period
> self.latest_final_blocks_periods[final_block.slot.thread as usize].1
{
self.latest_final_blocks_periods[final_block.slot.thread as usize] =
(block_id, final_block.slot.period);
}
self.new_final_blocks.insert(block_id);
} else {
return Err(ConsensusError::ContainerInconsistency(format!("inconsistency inside block statuses updating final blocks adding {} - block {} is missing", add_block_id, block_id)));
}
}
Ok(())
}
}