use crate::{
chain::{chain_information, fork_tree},
header,
};
use alloc::{
collections::{BTreeMap, BTreeSet},
format,
sync::Arc,
vec::Vec,
};
use core::{cmp, fmt, mem, num::NonZero, ops, time::Duration};
use hashbrown::HashMap;
mod finality;
mod tests;
mod verify;
pub use self::finality::*;
pub use self::verify::*;
#[derive(Debug, Clone)]
pub struct Config {
pub chain_information: chain_information::ValidChainInformation,
pub block_number_bytes: usize,
pub blocks_capacity: usize,
pub allow_unknown_consensus_engines: bool,
}
pub struct NonFinalizedTree<T> {
finalized_block_header: Vec<u8>,
finalized_block_hash: [u8; 32],
finalized_block_number: u64,
finality: Finality,
finalized_consensus: FinalizedConsensus,
finalized_best_score: BestScore,
blocks: fork_tree::ForkTree<Block<T>>,
blocks_insertion_counter: u128,
blocks_by_hash: HashMap<[u8; 32], fork_tree::NodeIndex, fnv::FnvBuildHasher>,
blocks_by_best_score: BTreeMap<BestScore, fork_tree::NodeIndex>,
blocks_trigger_gp_change: BTreeSet<(Option<u64>, fork_tree::NodeIndex)>,
block_number_bytes: usize,
allow_unknown_consensus_engines: bool,
}
impl<T> NonFinalizedTree<T> {
pub fn new(config: Config) -> Self {
let chain_information: chain_information::ChainInformation =
config.chain_information.into();
NonFinalizedTree {
finalized_block_number: chain_information.finalized_block_header.number,
finalized_block_hash: chain_information
.finalized_block_header
.hash(config.block_number_bytes),
finalized_block_header: chain_information
.finalized_block_header
.scale_encoding_vec(config.block_number_bytes),
finality: match chain_information.finality {
chain_information::ChainInformationFinality::Outsourced => Finality::Outsourced,
chain_information::ChainInformationFinality::Grandpa {
after_finalized_block_authorities_set_id,
finalized_scheduled_change,
finalized_triggered_authorities,
} => Finality::Grandpa {
after_finalized_block_authorities_set_id,
finalized_scheduled_change: finalized_scheduled_change
.map(|(n, l)| (n, l.into_iter().collect())),
finalized_triggered_authorities: finalized_triggered_authorities
.into_iter()
.collect(),
},
},
finalized_consensus: match chain_information.consensus {
chain_information::ChainInformationConsensus::Unknown => {
FinalizedConsensus::Unknown
}
chain_information::ChainInformationConsensus::Aura {
finalized_authorities_list,
slot_duration,
} => FinalizedConsensus::Aura {
authorities_list: Arc::new(finalized_authorities_list),
slot_duration,
},
chain_information::ChainInformationConsensus::Babe {
finalized_block_epoch_information,
finalized_next_epoch_transition,
slots_per_epoch,
} => FinalizedConsensus::Babe {
slots_per_epoch,
block_epoch_information: finalized_block_epoch_information.map(Arc::from),
next_epoch_transition: Arc::from(finalized_next_epoch_transition),
},
},
finalized_best_score: BestScore {
num_primary_slots: 0,
num_secondary_slots: 0,
insertion_counter: 0,
},
blocks: fork_tree::ForkTree::with_capacity(config.blocks_capacity),
blocks_insertion_counter: 1,
blocks_by_hash: hashbrown::HashMap::with_capacity_and_hasher(
config.blocks_capacity,
Default::default(),
),
blocks_by_best_score: BTreeMap::new(),
blocks_trigger_gp_change: BTreeSet::new(),
block_number_bytes: config.block_number_bytes,
allow_unknown_consensus_engines: config.allow_unknown_consensus_engines,
}
}
pub fn clear(&mut self) {
self.blocks.clear();
self.blocks_by_hash.clear();
self.blocks_by_best_score.clear();
self.blocks_trigger_gp_change.clear();
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
pub fn len(&self) -> usize {
self.blocks.len()
}
pub fn iter_unordered(&'_ self) -> impl Iterator<Item = header::HeaderRef<'_>> {
self.blocks
.iter_unordered()
.map(move |(_, b)| header::decode(&b.header, self.block_number_bytes).unwrap())
}
pub fn iter_ancestry_order(&'_ self) -> impl Iterator<Item = header::HeaderRef<'_>> {
self.blocks
.iter_ancestry_order()
.map(move |(_, b)| header::decode(&b.header, self.block_number_bytes).unwrap())
}
pub fn reserve(&mut self, additional: usize) {
self.blocks_by_hash.reserve(additional);
self.blocks.reserve(additional);
}
pub fn shrink_to_fit(&mut self) {
self.blocks_by_hash.shrink_to_fit();
self.blocks.shrink_to_fit();
}
pub fn block_number_bytes(&self) -> usize {
self.block_number_bytes
}
pub fn as_chain_information(&'_ self) -> chain_information::ValidChainInformationRef<'_> {
let attempt = chain_information::ChainInformationRef {
finalized_block_header: header::decode(
&self.finalized_block_header,
self.block_number_bytes,
)
.unwrap(),
consensus: match &self.finalized_consensus {
FinalizedConsensus::Unknown => {
chain_information::ChainInformationConsensusRef::Unknown
}
FinalizedConsensus::Aura {
authorities_list,
slot_duration,
} => chain_information::ChainInformationConsensusRef::Aura {
finalized_authorities_list: header::AuraAuthoritiesIter::from_slice(
authorities_list,
),
slot_duration: *slot_duration,
},
FinalizedConsensus::Babe {
block_epoch_information,
next_epoch_transition,
slots_per_epoch,
} => chain_information::ChainInformationConsensusRef::Babe {
slots_per_epoch: *slots_per_epoch,
finalized_block_epoch_information: block_epoch_information
.as_ref()
.map(|info| From::from(&**info)),
finalized_next_epoch_transition: next_epoch_transition.as_ref().into(),
},
},
finality: match &self.finality {
Finality::Outsourced => chain_information::ChainInformationFinalityRef::Outsourced,
Finality::Grandpa {
after_finalized_block_authorities_set_id,
finalized_triggered_authorities,
finalized_scheduled_change,
} => chain_information::ChainInformationFinalityRef::Grandpa {
after_finalized_block_authorities_set_id:
*after_finalized_block_authorities_set_id,
finalized_scheduled_change: finalized_scheduled_change
.as_ref()
.map(|(n, l)| (*n, &l[..])),
finalized_triggered_authorities,
},
},
};
chain_information::ValidChainInformationRef::try_from(attempt).unwrap()
}
pub fn finalized_block_header(&self) -> &[u8] {
&self.finalized_block_header
}
pub fn finalized_block_hash(&self) -> &[u8; 32] {
&self.finalized_block_hash
}
pub fn finalized_block_height(&self) -> u64 {
self.finalized_block_number
}
pub fn best_block_header(&self) -> &[u8] {
if let Some((_, index)) = self.blocks_by_best_score.last_key_value() {
&self.blocks.get(*index).unwrap().header
} else {
&self.finalized_block_header
}
}
pub fn best_block_hash(&self) -> &[u8; 32] {
if let Some((_, index)) = self.blocks_by_best_score.last_key_value() {
&self.blocks.get(*index).unwrap().hash
} else {
&self.finalized_block_hash
}
}
pub fn best_block_height(&self) -> u64 {
if let Some((_, index)) = self.blocks_by_best_score.last_key_value() {
self.blocks.get(*index).unwrap().number
} else {
self.finalized_block_number
}
}
pub fn best_block_consensus(&'_ self) -> chain_information::ChainInformationConsensusRef<'_> {
match (
&self.finalized_consensus,
self.blocks_by_best_score
.last_key_value()
.map(|(_, idx)| &self.blocks.get(*idx).unwrap().consensus),
) {
(FinalizedConsensus::Unknown, _) => {
chain_information::ChainInformationConsensusRef::Unknown
}
(
FinalizedConsensus::Aura {
authorities_list,
slot_duration,
},
None,
)
| (
FinalizedConsensus::Aura { slot_duration, .. },
Some(BlockConsensus::Aura { authorities_list }),
) => chain_information::ChainInformationConsensusRef::Aura {
finalized_authorities_list: header::AuraAuthoritiesIter::from_slice(
authorities_list,
),
slot_duration: *slot_duration,
},
(
FinalizedConsensus::Babe {
block_epoch_information,
next_epoch_transition,
slots_per_epoch,
},
None,
) => chain_information::ChainInformationConsensusRef::Babe {
slots_per_epoch: *slots_per_epoch,
finalized_block_epoch_information: block_epoch_information
.as_ref()
.map(|info| From::from(&**info)),
finalized_next_epoch_transition: next_epoch_transition.as_ref().into(),
},
(
FinalizedConsensus::Babe {
slots_per_epoch, ..
},
Some(BlockConsensus::Babe {
current_epoch,
next_epoch,
}),
) => chain_information::ChainInformationConsensusRef::Babe {
slots_per_epoch: *slots_per_epoch,
finalized_block_epoch_information: current_epoch
.as_ref()
.map(|info| From::from(&**info)),
finalized_next_epoch_transition: next_epoch.as_ref().into(),
},
_ => unreachable!(),
}
}
pub fn contains_non_finalized_block(&self, hash: &[u8; 32]) -> bool {
self.blocks_by_hash.contains_key(hash)
}
pub fn non_finalized_block_user_data(&self, hash: &[u8; 32]) -> Option<&T> {
let node_index = *self.blocks_by_hash.get(hash)?;
Some(&self.blocks.get(node_index).unwrap().user_data)
}
pub fn non_finalized_block_user_data_mut(&mut self, hash: &[u8; 32]) -> Option<&mut T> {
let node_index = *self.blocks_by_hash.get(hash)?;
Some(&mut self.blocks.get_mut(node_index).unwrap().user_data)
}
pub fn non_finalized_block_header(&self, hash: &[u8; 32]) -> Option<&[u8]> {
let node_index = *self.blocks_by_hash.get(hash)?;
Some(&self.blocks.get(node_index).unwrap().header)
}
}
impl<T> fmt::Debug for NonFinalizedTree<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
struct Blocks<'a, T>(&'a NonFinalizedTree<T>);
impl<'a, T> fmt::Debug for Blocks<'a, T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_map()
.entries(
self.0
.blocks
.iter_unordered()
.map(|(_, v)| (format!("0x{}", hex::encode(v.hash)), &v.user_data)),
)
.finish()
}
}
f.debug_struct("NonFinalizedTree")
.field(
"finalized_block_hash",
&format!(
"0x{}",
hex::encode(header::hash_from_scale_encoded_header(
&self.finalized_block_header
))
),
)
.field("non_finalized_blocks", &Blocks(self))
.finish()
}
}
impl<'a, T> ops::Index<&'a [u8; 32]> for NonFinalizedTree<T> {
type Output = T;
#[track_caller]
fn index(&self, block_hash: &'a [u8; 32]) -> &T {
let node_index = self
.blocks_by_hash
.get(block_hash)
.unwrap_or_else(|| panic!("invalid block hash"));
&self
.blocks
.get(*node_index)
.unwrap_or_else(|| unreachable!())
.user_data
}
}
impl<'a, T> ops::IndexMut<&'a [u8; 32]> for NonFinalizedTree<T> {
#[track_caller]
fn index_mut(&mut self, block_hash: &'a [u8; 32]) -> &mut T {
let node_index = self
.blocks_by_hash
.get(block_hash)
.unwrap_or_else(|| panic!("invalid block hash"));
&mut self
.blocks
.get_mut(*node_index)
.unwrap_or_else(|| unreachable!())
.user_data
}
}
#[derive(Clone)]
enum FinalizedConsensus {
Unknown,
Aura {
authorities_list: Arc<Vec<header::AuraAuthority>>,
slot_duration: NonZero<u64>,
},
Babe {
block_epoch_information: Option<Arc<chain_information::BabeEpochInformation>>,
next_epoch_transition: Arc<chain_information::BabeEpochInformation>,
slots_per_epoch: NonZero<u64>,
},
}
#[derive(Clone)]
enum Finality {
Outsourced,
Grandpa {
after_finalized_block_authorities_set_id: u64,
finalized_triggered_authorities: Arc<[header::GrandpaAuthority]>,
finalized_scheduled_change: Option<(u64, Arc<[header::GrandpaAuthority]>)>,
},
}
struct Block<T> {
header: Vec<u8>,
hash: [u8; 32],
number: u64,
consensus: BlockConsensus,
finality: BlockFinality,
best_score: BestScore,
user_data: T,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct BestScore {
num_primary_slots: u64,
num_secondary_slots: u64,
insertion_counter: u128,
}
impl cmp::PartialOrd for BestScore {
fn partial_cmp(&self, other: &BestScore) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl cmp::Ord for BestScore {
fn cmp(&self, other: &Self) -> cmp::Ordering {
match self.num_primary_slots.cmp(&other.num_primary_slots) {
cmp::Ordering::Equal => {
match self.num_secondary_slots.cmp(&other.num_secondary_slots) {
cmp::Ordering::Equal => {
other.insertion_counter.cmp(&self.insertion_counter)
}
other => other,
}
}
other => other,
}
}
}
#[derive(Clone)]
enum BlockConsensus {
Aura {
authorities_list: Arc<Vec<header::AuraAuthority>>,
},
Babe {
current_epoch: Option<Arc<chain_information::BabeEpochInformation>>,
next_epoch: Arc<chain_information::BabeEpochInformation>,
},
}
#[derive(Clone)]
enum BlockFinality {
Outsourced,
Grandpa {
prev_auth_change_trigger_number: Option<u64>,
after_block_authorities_set_id: u64,
triggers_change: bool,
triggered_authorities: Arc<[header::GrandpaAuthority]>,
scheduled_change: Option<(u64, Arc<[header::GrandpaAuthority]>)>,
},
}