use std::fmt::Debug;
use log::trace;
use codec::Codec;
use sc_client_api::{backend::AuxStore, UsageProvider};
use sp_api::{Core, ProvideRuntimeApi};
use sp_application_crypto::{AppCrypto, AppPublic};
use sp_blockchain::Result as CResult;
use sp_consensus::Error as ConsensusError;
use sp_consensus_slots::Slot;
use sp_core::crypto::{ByteArray, Pair};
use sp_keystore::KeystorePtr;
use sp_runtime::{
traits::{Block as BlockT, Header, NumberFor, Zero},
DigestItem,
};
pub use sc_consensus_slots::check_equivocation;
use super::{
AuraApi, AuthorityId, CompatibilityMode, CompatibleDigestItem, SlotDuration, LOG_TARGET,
};
pub fn slot_duration<A, B, C>(client: &C) -> CResult<SlotDuration>
where
A: Codec,
B: BlockT,
C: AuxStore + ProvideRuntimeApi<B> + UsageProvider<B>,
C::Api: AuraApi<B, A>,
{
slot_duration_at(client, client.usage_info().chain.best_hash)
}
pub fn slot_duration_at<A, B, C>(client: &C, block_hash: B::Hash) -> CResult<SlotDuration>
where
A: Codec,
B: BlockT,
C: AuxStore + ProvideRuntimeApi<B>,
C::Api: AuraApi<B, A>,
{
client.runtime_api().slot_duration(block_hash).map_err(|err| err.into())
}
pub fn slot_author<P: Pair>(slot: Slot, authorities: &[AuthorityId<P>]) -> Option<&AuthorityId<P>> {
if authorities.is_empty() {
return None
}
let idx = *slot % (authorities.len() as u64);
assert!(
idx <= usize::MAX as u64,
"It is impossible to have a vector with length beyond the address space; qed",
);
let current_author = authorities.get(idx as usize).expect(
"authorities not empty; index constrained to list length;this is a valid index; qed",
);
Some(current_author)
}
pub async fn claim_slot<P: Pair>(
slot: Slot,
authorities: &[AuthorityId<P>],
keystore: &KeystorePtr,
) -> Option<P::Public> {
let expected_author = slot_author::<P>(slot, authorities);
expected_author.and_then(|p| {
if keystore.has_keys(&[(p.to_raw_vec(), sp_application_crypto::key_types::AURA)]) {
Some(p.clone())
} else {
None
}
})
}
pub fn pre_digest<P: Pair>(slot: Slot) -> sp_runtime::DigestItem
where
P::Signature: Codec,
{
<DigestItem as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)
}
pub fn seal<Hash, P>(
header_hash: &Hash,
public: &P::Public,
keystore: &KeystorePtr,
) -> Result<sp_runtime::DigestItem, ConsensusError>
where
Hash: AsRef<[u8]>,
P: Pair,
P::Signature: Codec + TryFrom<Vec<u8>>,
P::Public: AppPublic,
{
let signature = keystore
.sign_with(
<AuthorityId<P> as AppCrypto>::ID,
<AuthorityId<P> as AppCrypto>::CRYPTO_ID,
public.as_slice(),
header_hash.as_ref(),
)
.map_err(|e| ConsensusError::CannotSign(format!("{}. Key: {:?}", e, public)))?
.ok_or_else(|| {
ConsensusError::CannotSign(format!("Could not find key in keystore. Key: {:?}", public))
})?;
let signature = signature
.clone()
.try_into()
.map_err(|_| ConsensusError::InvalidSignature(signature, public.to_raw_vec()))?;
let signature_digest_item =
<DigestItem as CompatibleDigestItem<P::Signature>>::aura_seal(signature);
Ok(signature_digest_item)
}
#[derive(Debug, thiserror::Error)]
pub enum PreDigestLookupError {
#[error("Multiple Aura pre-runtime headers")]
MultipleHeaders,
#[error("No Aura pre-runtime digest found")]
NoDigestFound,
}
pub fn find_pre_digest<B: BlockT, Signature: Codec>(
header: &B::Header,
) -> Result<Slot, PreDigestLookupError> {
if header.number().is_zero() {
return Ok(0.into())
}
let mut pre_digest: Option<Slot> = None;
for log in header.digest().logs() {
trace!(target: LOG_TARGET, "Checking log {:?}", log);
match (CompatibleDigestItem::<Signature>::as_aura_pre_digest(log), pre_digest.is_some()) {
(Some(_), true) => return Err(PreDigestLookupError::MultipleHeaders),
(None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"),
(s, false) => pre_digest = s,
}
}
pre_digest.ok_or_else(|| PreDigestLookupError::NoDigestFound)
}
pub fn fetch_authorities_with_compatibility_mode<A, B, C>(
client: &C,
parent_hash: B::Hash,
context_block_number: NumberFor<B>,
compatibility_mode: &CompatibilityMode<NumberFor<B>>,
) -> Result<Vec<A>, ConsensusError>
where
A: Codec + Debug,
B: BlockT,
C: ProvideRuntimeApi<B>,
C::Api: AuraApi<B, A>,
{
let runtime_api = client.runtime_api();
match compatibility_mode {
CompatibilityMode::None => {},
CompatibilityMode::UseInitializeBlock { until } =>
if *until > context_block_number {
runtime_api
.initialize_block(
parent_hash,
&B::Header::new(
context_block_number,
Default::default(),
Default::default(),
parent_hash,
Default::default(),
),
)
.map_err(|_| ConsensusError::InvalidAuthoritiesSet)?;
},
}
runtime_api
.authorities(parent_hash)
.ok()
.ok_or(ConsensusError::InvalidAuthoritiesSet)
}
pub fn fetch_authorities<A, B, C>(
client: &C,
parent_hash: B::Hash,
) -> Result<Vec<A>, ConsensusError>
where
A: Codec + Debug,
B: BlockT,
C: ProvideRuntimeApi<B>,
C::Api: AuraApi<B, A>,
{
client
.runtime_api()
.authorities(parent_hash)
.ok()
.ok_or(ConsensusError::InvalidAuthoritiesSet)
}
#[derive(Debug, thiserror::Error)]
pub enum SealVerificationError<Header> {
#[error("Header slot is in the future")]
Deferred(Header, Slot),
#[error("Header is unsealed.")]
Unsealed,
#[error("Header has a malformed seal")]
BadSeal,
#[error("Header has a bad signature")]
BadSignature,
#[error("No slot author for provided slot")]
SlotAuthorNotFound,
#[error("Header has no valid slot pre-digest")]
InvalidPreDigest(PreDigestLookupError),
}
pub fn check_header_slot_and_seal<B: BlockT, P: Pair>(
slot_now: Slot,
mut header: B::Header,
authorities: &[AuthorityId<P>],
) -> Result<(B::Header, Slot, DigestItem), SealVerificationError<B::Header>>
where
P::Signature: Codec,
P::Public: Codec + PartialEq + Clone,
{
let seal = header.digest_mut().pop().ok_or(SealVerificationError::Unsealed)?;
let sig = seal.as_aura_seal().ok_or(SealVerificationError::BadSeal)?;
let slot = find_pre_digest::<B, P::Signature>(&header)
.map_err(SealVerificationError::InvalidPreDigest)?;
if slot > slot_now {
header.digest_mut().push(seal);
return Err(SealVerificationError::Deferred(header, slot))
} else {
let expected_author =
slot_author::<P>(slot, authorities).ok_or(SealVerificationError::SlotAuthorNotFound)?;
let pre_hash = header.hash();
if P::verify(&sig, pre_hash.as_ref(), expected_author) {
Ok((header, slot, seal))
} else {
Err(SealVerificationError::BadSignature)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_keyring::sr25519::Keyring;
#[test]
fn authorities_call_works() {
let client = substrate_test_runtime_client::new();
assert_eq!(client.chain_info().best_number, 0);
assert_eq!(
fetch_authorities_with_compatibility_mode(
&client,
client.chain_info().best_hash,
1,
&CompatibilityMode::None
)
.unwrap(),
vec![
Keyring::Alice.public().into(),
Keyring::Bob.public().into(),
Keyring::Charlie.public().into()
]
);
assert_eq!(
fetch_authorities(&client, client.chain_info().best_hash).unwrap(),
vec![
Keyring::Alice.public().into(),
Keyring::Bob.public().into(),
Keyring::Charlie.public().into()
]
);
}
}