use crate::header;
use alloc::{boxed::Box, vec::Vec};
use core::num::NonZero;
pub mod build;
#[derive(Debug, Clone)]
pub struct ValidChainInformation {
inner: ChainInformation,
}
impl From<ValidChainInformation> for ChainInformation {
fn from(i: ValidChainInformation) -> Self {
i.inner
}
}
impl ValidChainInformation {
pub fn as_ref(&'_ self) -> ChainInformationRef<'_> {
From::from(&self.inner)
}
}
impl<'a> From<ValidChainInformationRef<'a>> for ValidChainInformation {
fn from(info: ValidChainInformationRef<'a>) -> ValidChainInformation {
ValidChainInformation {
inner: info.inner.into(),
}
}
}
impl TryFrom<ChainInformation> for ValidChainInformation {
type Error = ValidityError;
fn try_from(info: ChainInformation) -> Result<Self, Self::Error> {
ChainInformationRef::from(&info).validate()?;
Ok(ValidChainInformation { inner: info })
}
}
#[derive(Debug, Clone)]
pub struct ValidChainInformationRef<'a> {
inner: ChainInformationRef<'a>,
}
impl<'a> From<&'a ValidChainInformation> for ValidChainInformationRef<'a> {
fn from(info: &'a ValidChainInformation) -> ValidChainInformationRef<'a> {
ValidChainInformationRef {
inner: From::from(&info.inner),
}
}
}
impl<'a> TryFrom<ChainInformationRef<'a>> for ValidChainInformationRef<'a> {
type Error = ValidityError;
fn try_from(info: ChainInformationRef<'a>) -> Result<Self, Self::Error> {
info.validate()?;
Ok(ValidChainInformationRef { inner: info })
}
}
impl<'a> ValidChainInformationRef<'a> {
pub fn as_ref(&self) -> ChainInformationRef<'a> {
self.inner.clone()
}
}
#[derive(Debug, Clone)]
pub struct ChainInformation {
pub finalized_block_header: Box<header::Header>,
pub consensus: ChainInformationConsensus,
pub finality: ChainInformationFinality,
}
impl<'a> From<ChainInformationRef<'a>> for ChainInformation {
fn from(info: ChainInformationRef<'a>) -> ChainInformation {
ChainInformation {
finalized_block_header: Box::new(info.finalized_block_header.into()),
consensus: match info.consensus {
ChainInformationConsensusRef::Unknown => ChainInformationConsensus::Unknown,
ChainInformationConsensusRef::Aura {
finalized_authorities_list,
slot_duration,
} => ChainInformationConsensus::Aura {
finalized_authorities_list: finalized_authorities_list
.map(|a| a.into())
.collect(),
slot_duration,
},
ChainInformationConsensusRef::Babe {
slots_per_epoch,
finalized_next_epoch_transition,
finalized_block_epoch_information,
} => ChainInformationConsensus::Babe {
slots_per_epoch,
finalized_block_epoch_information: finalized_block_epoch_information
.map(|i| Box::new(i.into())),
finalized_next_epoch_transition: Box::new(
finalized_next_epoch_transition.into(),
),
},
},
finality: info.finality.into(),
}
}
}
#[derive(Debug, Clone)]
pub enum ChainInformationConsensus {
Unknown,
Aura {
finalized_authorities_list: Vec<header::AuraAuthority>,
slot_duration: NonZero<u64>,
},
Babe {
slots_per_epoch: NonZero<u64>,
finalized_block_epoch_information: Option<Box<BabeEpochInformation>>,
finalized_next_epoch_transition: Box<BabeEpochInformation>,
},
}
#[derive(Debug, Clone)]
pub struct BabeEpochInformation {
pub epoch_index: u64,
pub start_slot_number: Option<u64>,
pub authorities: Vec<header::BabeAuthority>,
pub randomness: [u8; 32],
pub c: (u64, u64),
pub allowed_slots: header::BabeAllowedSlots,
}
impl BabeEpochInformation {
pub fn validate(&self) -> Result<(), BabeValidityError> {
BabeEpochInformationRef::from(self).validate()
}
}
impl<'a> From<BabeEpochInformationRef<'a>> for BabeEpochInformation {
fn from(info: BabeEpochInformationRef<'a>) -> BabeEpochInformation {
BabeEpochInformation {
epoch_index: info.epoch_index,
start_slot_number: info.start_slot_number,
authorities: info.authorities.map(Into::into).collect(),
randomness: *info.randomness,
c: info.c,
allowed_slots: info.allowed_slots,
}
}
}
#[derive(Debug, Clone)]
pub enum ChainInformationFinality {
Outsourced,
Grandpa {
after_finalized_block_authorities_set_id: u64,
finalized_triggered_authorities: Vec<header::GrandpaAuthority>,
finalized_scheduled_change: Option<(u64, Vec<header::GrandpaAuthority>)>,
},
}
impl<'a> From<ChainInformationFinalityRef<'a>> for ChainInformationFinality {
fn from(finality: ChainInformationFinalityRef<'a>) -> ChainInformationFinality {
match finality {
ChainInformationFinalityRef::Outsourced => ChainInformationFinality::Outsourced,
ChainInformationFinalityRef::Grandpa {
after_finalized_block_authorities_set_id,
finalized_triggered_authorities,
finalized_scheduled_change,
} => ChainInformationFinality::Grandpa {
after_finalized_block_authorities_set_id,
finalized_scheduled_change: finalized_scheduled_change.map(|(n, l)| (n, l.into())),
finalized_triggered_authorities: finalized_triggered_authorities.into(),
},
}
}
}
#[derive(Debug, Clone)]
pub struct ChainInformationRef<'a> {
pub finalized_block_header: header::HeaderRef<'a>,
pub consensus: ChainInformationConsensusRef<'a>,
pub finality: ChainInformationFinalityRef<'a>,
}
impl<'a> ChainInformationRef<'a> {
pub fn validate(&self) -> Result<(), ValidityError> {
if let ChainInformationConsensusRef::Babe {
finalized_next_epoch_transition,
finalized_block_epoch_information,
..
} = &self.consensus
{
if let Err(err) = finalized_next_epoch_transition.validate() {
return Err(ValidityError::InvalidBabe(err));
}
if finalized_next_epoch_transition.start_slot_number.is_some()
&& (finalized_next_epoch_transition.epoch_index == 0)
{
return Err(ValidityError::UnexpectedBabeSlotStartNumber);
}
if finalized_next_epoch_transition.start_slot_number.is_none()
&& (finalized_next_epoch_transition.epoch_index != 0)
{
return Err(ValidityError::MissingBabeSlotStartNumber);
}
if let Some(finalized_block_epoch_information) = &finalized_block_epoch_information {
if let Err(err) = finalized_block_epoch_information.validate() {
return Err(ValidityError::InvalidBabe(err));
}
if self.finalized_block_header.number == 0 {
return Err(ValidityError::UnexpectedBabeFinalizedEpoch);
}
if let Some(epoch_start_slot_number) =
finalized_block_epoch_information.start_slot_number
{
if let Some(babe_preruntime) =
self.finalized_block_header.digest.babe_pre_runtime()
{
if self.finalized_block_header.number == 0 {
return Err(ValidityError::ConsensusAlgorithmMismatch);
}
if babe_preruntime.slot_number() < epoch_start_slot_number {
return Err(ValidityError::HeaderBabeSlotInferiorToEpochStartSlot);
}
} else if self.finalized_block_header.number != 0 {
return Err(ValidityError::ConsensusAlgorithmMismatch);
}
if (self.finalized_block_header.digest.babe_seal().is_some()
!= (self.finalized_block_header.number != 0))
|| self.finalized_block_header.digest.has_any_aura()
{
return Err(ValidityError::ConsensusAlgorithmMismatch);
}
if let Some((epoch_change, _new_config)) =
self.finalized_block_header.digest.babe_epoch_information()
{
if epoch_change.authorities != finalized_next_epoch_transition.authorities
|| epoch_change.randomness != finalized_next_epoch_transition.randomness
{
return Err(ValidityError::BabeEpochInfoMismatch);
}
}
} else {
return Err(ValidityError::MissingBabeSlotStartNumber);
}
}
if finalized_block_epoch_information.is_none()
&& self.finalized_block_header.number != 0
{
return Err(ValidityError::NoBabeFinalizedEpoch);
}
}
if let ChainInformationConsensusRef::Aura { .. } = &self.consensus {
if (self
.finalized_block_header
.digest
.aura_pre_runtime()
.is_some()
!= (self.finalized_block_header.number != 0))
|| (self.finalized_block_header.digest.aura_seal().is_some()
!= (self.finalized_block_header.number != 0))
|| self.finalized_block_header.digest.has_any_babe()
{
return Err(ValidityError::ConsensusAlgorithmMismatch);
}
}
if let ChainInformationFinalityRef::Grandpa {
after_finalized_block_authorities_set_id,
finalized_scheduled_change,
..
} = &self.finality
{
if let Some(change) = finalized_scheduled_change.as_ref() {
if change.0 <= self.finalized_block_header.number {
return Err(ValidityError::ScheduledGrandPaChangeBeforeFinalized);
}
}
if self.finalized_block_header.number == 0
&& *after_finalized_block_authorities_set_id != 0
{
return Err(ValidityError::FinalizedZeroButNonZeroAuthoritiesSetId);
}
}
Ok(())
}
}
impl<'a> From<&'a ChainInformation> for ChainInformationRef<'a> {
fn from(info: &'a ChainInformation) -> ChainInformationRef<'a> {
ChainInformationRef {
finalized_block_header: (&*info.finalized_block_header).into(),
consensus: match &info.consensus {
ChainInformationConsensus::Unknown => ChainInformationConsensusRef::Unknown,
ChainInformationConsensus::Aura {
finalized_authorities_list,
slot_duration,
} => ChainInformationConsensusRef::Aura {
finalized_authorities_list: header::AuraAuthoritiesIter::from_slice(
finalized_authorities_list,
),
slot_duration: *slot_duration,
},
ChainInformationConsensus::Babe {
slots_per_epoch,
finalized_block_epoch_information,
finalized_next_epoch_transition,
} => ChainInformationConsensusRef::Babe {
slots_per_epoch: *slots_per_epoch,
finalized_block_epoch_information: finalized_block_epoch_information
.as_ref()
.map(|i| (&**i).into()),
finalized_next_epoch_transition: (&**finalized_next_epoch_transition).into(),
},
},
finality: (&info.finality).into(),
}
}
}
#[derive(Debug, Clone)]
pub enum ChainInformationConsensusRef<'a> {
Unknown,
Aura {
finalized_authorities_list: header::AuraAuthoritiesIter<'a>,
slot_duration: NonZero<u64>,
},
Babe {
slots_per_epoch: NonZero<u64>,
finalized_block_epoch_information: Option<BabeEpochInformationRef<'a>>,
finalized_next_epoch_transition: BabeEpochInformationRef<'a>,
},
}
#[derive(Debug, Clone)]
pub struct BabeEpochInformationRef<'a> {
pub epoch_index: u64,
pub start_slot_number: Option<u64>,
pub authorities: header::BabeAuthoritiesIter<'a>,
pub randomness: &'a [u8; 32],
pub c: (u64, u64),
pub allowed_slots: header::BabeAllowedSlots,
}
impl<'a> BabeEpochInformationRef<'a> {
pub fn validate(&self) -> Result<(), BabeValidityError> {
if self.c.0 > self.c.1 {
return Err(BabeValidityError::InvalidConstant);
}
Ok(())
}
}
impl<'a> From<&'a BabeEpochInformation> for BabeEpochInformationRef<'a> {
fn from(info: &'a BabeEpochInformation) -> BabeEpochInformationRef<'a> {
BabeEpochInformationRef {
epoch_index: info.epoch_index,
start_slot_number: info.start_slot_number,
authorities: header::BabeAuthoritiesIter::from_slice(&info.authorities),
randomness: &info.randomness,
c: info.c,
allowed_slots: info.allowed_slots,
}
}
}
#[derive(Debug, Clone)]
pub enum ChainInformationFinalityRef<'a> {
Outsourced,
Grandpa {
after_finalized_block_authorities_set_id: u64,
finalized_triggered_authorities: &'a [header::GrandpaAuthority],
finalized_scheduled_change: Option<(u64, &'a [header::GrandpaAuthority])>,
},
}
impl<'a> From<&'a ChainInformationFinality> for ChainInformationFinalityRef<'a> {
fn from(finality: &'a ChainInformationFinality) -> ChainInformationFinalityRef<'a> {
match finality {
ChainInformationFinality::Outsourced => ChainInformationFinalityRef::Outsourced,
ChainInformationFinality::Grandpa {
finalized_triggered_authorities,
after_finalized_block_authorities_set_id,
finalized_scheduled_change,
} => ChainInformationFinalityRef::Grandpa {
after_finalized_block_authorities_set_id: *after_finalized_block_authorities_set_id,
finalized_triggered_authorities,
finalized_scheduled_change: finalized_scheduled_change
.as_ref()
.map(|(n, l)| (*n, &l[..])),
},
}
}
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum ValidityError {
ConsensusAlgorithmMismatch,
UnexpectedBabeSlotStartNumber,
MissingBabeSlotStartNumber,
UnexpectedBabeFinalizedEpoch,
NoBabeFinalizedEpoch,
HeaderBabeSlotInferiorToEpochStartSlot,
BabeEpochInfoMismatch,
ScheduledGrandPaChangeBeforeFinalized,
FinalizedZeroButNonZeroAuthoritiesSetId,
#[display("Error in a Babe epoch information: {_0}")]
InvalidBabe(BabeValidityError),
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum BabeValidityError {
InvalidConstant,
}