use super::*;
use crate::finality::{decode, verify};
use core::cmp;
impl<T> NonFinalizedTree<T> {
pub fn finality_checkpoints(&self) -> impl Iterator<Item = (u64, &[u8; 32])> {
debug_assert!(
self.blocks_trigger_gp_change.is_empty()
|| !matches!(self.finality, Finality::Outsourced)
);
self.blocks_trigger_gp_change
.range((
ops::Bound::Excluded((
Some(self.finalized_block_number),
fork_tree::NodeIndex::MAX,
)),
ops::Bound::Unbounded,
))
.map(|(_prev_auth_change_trigger_number, block_index)| {
debug_assert!(
_prev_auth_change_trigger_number
.map_or(false, |n| n > self.finalized_block_number)
);
let block = self
.blocks
.get(*block_index)
.unwrap_or_else(|| unreachable!());
(block.number, &block.hash)
})
}
pub fn verify_justification(
&'_ mut self,
consensus_engine_id: [u8; 4],
scale_encoded_justification: &[u8],
randomness_seed: [u8; 32],
) -> Result<FinalityApply<'_, T>, JustificationVerifyError> {
match (&self.finality, &consensus_engine_id) {
(Finality::Grandpa { .. }, b"FRNK") => {
let decoded = decode::decode_grandpa_justification(
scale_encoded_justification,
self.block_number_bytes,
)
.map_err(JustificationVerifyError::InvalidJustification)?;
let (block_index, authorities_set_id, authorities_list) = self
.verify_grandpa_finality_inner(decoded.target_hash, decoded.target_number)
.map_err(JustificationVerifyError::FinalityVerify)?;
verify::verify_justification(verify::JustificationVerifyConfig {
justification: scale_encoded_justification,
block_number_bytes: self.block_number_bytes,
authorities_set_id,
authorities_list,
randomness_seed,
})
.map_err(JustificationVerifyError::VerificationFailed)?;
Ok(FinalityApply {
chain: self,
to_finalize: block_index,
})
}
_ => Err(JustificationVerifyError::JustificationEngineMismatch),
}
}
pub fn verify_grandpa_commit_message(
&'_ mut self,
scale_encoded_commit: &[u8],
randomness_seed: [u8; 32],
) -> Result<FinalityApply<'_, T>, CommitVerifyError> {
if !matches!(self.finality, Finality::Grandpa { .. }) {
return Err(CommitVerifyError::NotGrandpa);
}
let decoded_commit =
decode::decode_grandpa_commit(scale_encoded_commit, self.block_number_bytes)
.map_err(|_| CommitVerifyError::InvalidCommit)?;
let (block_index, expected_authorities_set_id, authorities_list) = self
.verify_grandpa_finality_inner(decoded_commit.target_hash, decoded_commit.target_number)
.map_err(CommitVerifyError::FinalityVerify)?;
let mut verification = verify::verify_commit(verify::CommitVerifyConfig {
commit: scale_encoded_commit,
block_number_bytes: self.block_number_bytes,
expected_authorities_set_id,
num_authorities: u32::try_from(authorities_list.clone().count()).unwrap(),
randomness_seed,
});
loop {
match verification {
verify::CommitVerify::Finished(Ok(())) => {
drop(authorities_list);
return Ok(FinalityApply {
chain: self,
to_finalize: block_index,
});
}
verify::CommitVerify::FinishedUnknown => {
return Err(CommitVerifyError::NotEnoughKnownBlocks {
target_block_number: decoded_commit.target_number,
});
}
verify::CommitVerify::Finished(Err(error)) => {
return Err(CommitVerifyError::VerificationFailed(error));
}
verify::CommitVerify::IsAuthority(is_authority) => {
let to_find = is_authority.authority_public_key();
let result = authorities_list.clone().any(|a| a == to_find);
verification = is_authority.resume(result);
}
verify::CommitVerify::IsParent(is_parent) => {
match self.blocks_by_hash.get(is_parent.block_hash()) {
Some(idx) => {
let result = self.blocks.is_ancestor(block_index, *idx);
verification = is_parent.resume(Some(result));
}
None => {
verification = is_parent.resume(None);
}
};
}
}
}
}
pub fn set_finalized_block(
&'_ mut self,
block_hash: &[u8; 32],
) -> Result<SetFinalizedBlockIter<'_, T>, SetFinalizedError> {
let block_index = match self.blocks_by_hash.get(block_hash) {
Some(idx) => *idx,
None => return Err(SetFinalizedError::UnknownBlock),
};
Ok(self.set_finalized_block_inner(block_index))
}
fn verify_grandpa_finality_inner(
&self,
target_hash: &[u8; 32],
target_number: u64,
) -> Result<
(
fork_tree::NodeIndex,
u64,
impl Iterator<Item = &[u8]> + Clone,
),
FinalityVerifyError,
> {
match &self.finality {
Finality::Outsourced => panic!(),
Finality::Grandpa {
after_finalized_block_authorities_set_id,
finalized_scheduled_change,
finalized_triggered_authorities,
} => {
match target_number.cmp(&self.finalized_block_number) {
cmp::Ordering::Equal if *target_hash == self.finalized_block_hash => {
return Err(FinalityVerifyError::EqualToFinalized);
}
cmp::Ordering::Equal => {
return Err(FinalityVerifyError::EqualFinalizedHeightButInequalHash);
}
cmp::Ordering::Less => return Err(FinalityVerifyError::BelowFinalized),
_ => {}
}
let block_index = match self.blocks_by_hash.get(target_hash) {
Some(idx) => *idx,
None => {
return Err(FinalityVerifyError::UnknownTargetBlock {
block_number: target_number,
block_hash: *target_hash,
});
}
};
if let BlockFinality::Grandpa {
ref prev_auth_change_trigger_number,
..
} = self.blocks.get(block_index).unwrap().finality
{
if let Some(prev_auth_change_trigger_number) = prev_auth_change_trigger_number {
if *prev_auth_change_trigger_number > self.finalized_block_number {
return Err(FinalityVerifyError::TooFarAhead {
justification_block_number: target_number,
justification_block_hash: *target_hash,
block_to_finalize_number: *prev_auth_change_trigger_number,
});
}
}
} else {
unreachable!()
}
let authorities_list = finalized_scheduled_change
.as_ref()
.filter(|(trigger_height, _)| *trigger_height < target_number)
.map_or(finalized_triggered_authorities, |(_, list)| list);
Ok((
block_index,
*after_finalized_block_authorities_set_id,
authorities_list.iter().map(|a| &a.public_key[..]),
))
}
}
}
fn set_finalized_block_inner(
&'_ mut self,
block_index_to_finalize: fork_tree::NodeIndex,
) -> SetFinalizedBlockIter<'_, T> {
let new_finalized_block = self.blocks.get_mut(block_index_to_finalize).unwrap();
match (&mut self.finality, &new_finalized_block.finality) {
(Finality::Outsourced, BlockFinality::Outsourced) => {}
(
Finality::Grandpa {
after_finalized_block_authorities_set_id,
finalized_scheduled_change,
finalized_triggered_authorities,
},
BlockFinality::Grandpa {
after_block_authorities_set_id,
triggered_authorities,
scheduled_change,
..
},
) => {
debug_assert!(
*after_finalized_block_authorities_set_id <= *after_block_authorities_set_id
);
debug_assert!(
scheduled_change
.as_ref()
.map_or(true, |(n, _)| *n > new_finalized_block.number)
);
*after_finalized_block_authorities_set_id = *after_block_authorities_set_id;
*finalized_triggered_authorities = triggered_authorities.clone();
*finalized_scheduled_change = scheduled_change.clone();
}
_ => unreachable!(),
}
let updates_best_block = {
let current_best: Option<fork_tree::NodeIndex> = self
.blocks_by_best_score
.last_key_value()
.map(|(_, idx)| *idx);
Some(block_index_to_finalize) == current_best
|| current_best.map_or(true, |current_best| {
!self
.blocks
.is_ancestor(block_index_to_finalize, current_best)
})
};
let new_finalized_block = self.blocks.get_mut(block_index_to_finalize).unwrap();
match (
&mut self.finalized_consensus,
&new_finalized_block.consensus,
) {
(
FinalizedConsensus::Aura {
authorities_list, ..
},
BlockConsensus::Aura {
authorities_list: new_list,
},
) => {
*authorities_list = new_list.clone();
}
(
FinalizedConsensus::Babe {
block_epoch_information,
next_epoch_transition,
..
},
BlockConsensus::Babe {
current_epoch,
next_epoch,
},
) => {
*block_epoch_information = current_epoch.clone();
*next_epoch_transition = next_epoch.clone();
}
_ => unreachable!(),
}
mem::swap(
&mut self.finalized_block_header,
&mut new_finalized_block.header,
);
self.finalized_block_hash = new_finalized_block.hash;
self.finalized_block_number = new_finalized_block.number;
self.finalized_best_score = new_finalized_block.best_score;
debug_assert_eq!(self.blocks.len(), self.blocks_by_hash.len());
debug_assert_eq!(self.blocks.len(), self.blocks_by_best_score.len());
debug_assert!(self.blocks.len() >= self.blocks_trigger_gp_change.len());
SetFinalizedBlockIter {
iter: self.blocks.prune_ancestors(block_index_to_finalize),
blocks_by_hash: &mut self.blocks_by_hash,
blocks_by_best_score: &mut self.blocks_by_best_score,
blocks_trigger_gp_change: &mut self.blocks_trigger_gp_change,
updates_best_block,
}
}
}
#[must_use]
pub struct FinalityApply<'c, T> {
chain: &'c mut NonFinalizedTree<T>,
to_finalize: fork_tree::NodeIndex,
}
impl<'c, T> FinalityApply<'c, T> {
pub fn apply(self) -> SetFinalizedBlockIter<'c, T> {
self.chain.set_finalized_block_inner(self.to_finalize)
}
pub fn block_user_data(&mut self) -> &mut T {
&mut self
.chain
.blocks
.get_mut(self.to_finalize)
.unwrap()
.user_data
}
pub fn is_current_best_block(&self) -> bool {
Some(self.to_finalize)
== self
.chain
.blocks_by_best_score
.last_key_value()
.map(|(_, idx)| *idx)
}
}
impl<'c, T> fmt::Debug for FinalityApply<'c, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("FinalityApply").finish()
}
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum JustificationVerifyError {
JustificationEngineMismatch,
#[display("Error while decoding the justification: {_0}")]
InvalidJustification(decode::JustificationDecodeError),
#[display("{_0}")]
VerificationFailed(verify::JustificationVerifyError),
#[display("{_0}")]
FinalityVerify(FinalityVerifyError),
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum CommitVerifyError {
NotGrandpa,
InvalidCommit,
#[display("{_0}")]
FinalityVerify(FinalityVerifyError),
#[display("Not enough blocks are known to verify this commit")]
NotEnoughKnownBlocks {
target_block_number: u64,
},
#[display("{_0}")]
VerificationFailed(verify::CommitVerifyError),
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum FinalityVerifyError {
EqualToFinalized,
EqualFinalizedHeightButInequalHash,
BelowFinalized,
#[display("Justification targets a block (#{block_number}) that isn't in the chain.")]
UnknownTargetBlock {
block_number: u64,
block_hash: [u8; 32],
},
#[display(
"There exists a block in-between the latest finalized block and the block \
targeted by the justification that must first be finalized"
)]
TooFarAhead {
justification_block_number: u64,
justification_block_hash: [u8; 32],
block_to_finalize_number: u64,
},
}
pub struct SetFinalizedBlockIter<'a, T> {
iter: fork_tree::PruneAncestorsIter<'a, Block<T>>,
blocks_by_hash: &'a mut HashMap<[u8; 32], fork_tree::NodeIndex, fnv::FnvBuildHasher>,
blocks_by_best_score: &'a mut BTreeMap<BestScore, fork_tree::NodeIndex>,
blocks_trigger_gp_change: &'a mut BTreeSet<(Option<u64>, fork_tree::NodeIndex)>,
updates_best_block: bool,
}
impl<'a, T> SetFinalizedBlockIter<'a, T> {
pub fn updates_best_block(&self) -> bool {
self.updates_best_block
}
}
impl<'a, T> Iterator for SetFinalizedBlockIter<'a, T> {
type Item = RemovedBlock<T>;
fn next(&mut self) -> Option<Self::Item> {
let pruned = self.iter.next()?;
let _removed = self.blocks_by_hash.remove(&pruned.user_data.hash);
debug_assert_eq!(_removed, Some(pruned.index));
let _removed = self
.blocks_by_best_score
.remove(&pruned.user_data.best_score);
debug_assert_eq!(_removed, Some(pruned.index));
if let BlockFinality::Grandpa {
prev_auth_change_trigger_number,
triggers_change: true,
..
} = pruned.user_data.finality
{
let _removed = self
.blocks_trigger_gp_change
.remove(&(prev_auth_change_trigger_number, pruned.index));
debug_assert!(_removed);
}
Some(RemovedBlock {
block_hash: pruned.user_data.hash,
block_number: pruned.user_data.number,
scale_encoded_header: pruned.user_data.header,
user_data: pruned.user_data.user_data,
ty: if pruned.is_prune_target_ancestor {
RemovedBlockType::Finalized
} else {
RemovedBlockType::Pruned
},
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'a, T> Drop for SetFinalizedBlockIter<'a, T> {
fn drop(&mut self) {
for _ in self {}
}
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum SetFinalizedError {
UnknownBlock,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RemovedBlock<T> {
pub block_hash: [u8; 32],
pub block_number: u64,
pub user_data: T,
pub ty: RemovedBlockType,
pub scale_encoded_header: Vec<u8>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum RemovedBlockType {
Finalized,
Pruned,
}