use crate::{
chain::chain_information,
header,
verify::{aura, babe},
};
use alloc::vec::Vec;
use core::{num::NonZero, time::Duration};
pub struct Config<'a> {
pub parent_block_header: header::HeaderRef<'a>,
pub block_header: header::HeaderRef<'a>,
pub block_number_bytes: usize,
pub consensus: ConfigConsensus<'a>,
pub finality: ConfigFinality,
pub allow_unknown_consensus_engines: bool,
}
pub enum ConfigConsensus<'a> {
Aura {
current_authorities: header::AuraAuthoritiesIter<'a>,
slot_duration: NonZero<u64>,
now_from_unix_epoch: Duration,
allow_equal_slot_number: bool,
},
Babe {
slots_per_epoch: NonZero<u64>,
parent_block_epoch: Option<chain_information::BabeEpochInformationRef<'a>>,
parent_block_next_epoch: chain_information::BabeEpochInformationRef<'a>,
now_from_unix_epoch: Duration,
},
}
pub enum ConfigFinality {
Outsourced,
Grandpa,
}
pub enum Success {
Aura {
authorities_change: Option<Vec<header::AuraAuthority>>,
},
Babe {
slot_number: u64,
is_primary_slot: bool,
epoch_transition_target: Option<chain_information::BabeEpochInformation>,
},
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum Error {
NonSequentialBlockNumber,
BadParentHash,
#[display("Block header contains an unrecognized consensus engine: {engine:?}")]
UnknownConsensusEngine { engine: [u8; 4] },
MultipleConsensusEngines,
FinalityEngineMismatch,
#[display("{_0}")]
AuraVerification(aura::VerifyError),
#[display("{_0}")]
BabeVerification(babe::VerifyError),
GrandpaChangesOverlap,
}
impl Error {
pub fn is_invalid_chain_configuration(&self) -> bool {
matches!(
self,
Error::BabeVerification(babe::VerifyError::InvalidChainConfiguration(_))
)
}
}
pub fn verify(config: Config) -> Result<Success, Error> {
if config.parent_block_header.hash(config.block_number_bytes)
!= *config.block_header.parent_hash
{
return Err(Error::BadParentHash);
}
if config
.parent_block_header
.number
.checked_add(1)
.map_or(true, |v| v != config.block_header.number)
{
return Err(Error::NonSequentialBlockNumber);
}
if !config.allow_unknown_consensus_engines {
if let Some(engine) = config
.block_header
.digest
.logs()
.find_map(|item| match item {
header::DigestItemRef::UnknownConsensus { engine, .. }
| header::DigestItemRef::UnknownSeal { engine, .. }
| header::DigestItemRef::UnknownPreRuntime { engine, .. } => Some(engine),
_ => None,
})
{
return Err(Error::UnknownConsensusEngine { engine });
}
}
match config.finality {
ConfigFinality::Grandpa => {}
ConfigFinality::Outsourced => {
if config.block_header.digest.has_any_grandpa() {
return Err(Error::FinalityEngineMismatch);
}
}
}
match config.consensus {
ConfigConsensus::Aura {
current_authorities,
slot_duration,
now_from_unix_epoch,
allow_equal_slot_number,
} => {
if config.block_header.digest.has_any_babe() {
return Err(Error::MultipleConsensusEngines);
}
let result = aura::verify_header(aura::VerifyConfig {
header: config.block_header.clone(),
block_number_bytes: config.block_number_bytes,
parent_block_header: config.parent_block_header,
now_from_unix_epoch,
current_authorities,
slot_duration,
allow_equal_slot_number,
});
match result {
Ok(s) => Ok(Success::Aura {
authorities_change: s.authorities_change,
}),
Err(err) => Err(Error::AuraVerification(err)),
}
}
ConfigConsensus::Babe {
parent_block_epoch,
parent_block_next_epoch,
slots_per_epoch,
now_from_unix_epoch,
} => {
if config.block_header.digest.has_any_aura() {
return Err(Error::MultipleConsensusEngines);
}
let result = babe::verify_header(babe::VerifyConfig {
header: config.block_header.clone(),
block_number_bytes: config.block_number_bytes,
parent_block_header: config.parent_block_header,
parent_block_epoch,
parent_block_next_epoch,
slots_per_epoch,
now_from_unix_epoch,
});
match result {
Ok(s) => Ok(Success::Babe {
epoch_transition_target: s.epoch_transition_target,
is_primary_slot: s.is_primary_slot,
slot_number: s.slot_number,
}),
Err(err) => Err(Error::BabeVerification(err)),
}
}
}
}