mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-17 17:11:02 +00:00
Refactor: extract most aura logic out to standalone module, make use of these (#13764)
* Extract most aura logic out to standalone module, make use of these * Update client/consensus/aura/src/standalone.rs improve docs Co-authored-by: Bastian Köcher <git@kchr.de> * add slot_duration_at * ".git/.scripts/commands/fmt/fmt.sh" --------- Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -19,7 +19,7 @@
|
|||||||
//! Module implementing the logic for verifying and importing AuRa blocks.
|
//! Module implementing the logic for verifying and importing AuRa blocks.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
aura_err, authorities, find_pre_digest, slot_author, AuthorityId, CompatibilityMode, Error,
|
authorities, standalone::SealVerificationError, AuthorityId, CompatibilityMode, Error,
|
||||||
LOG_TARGET,
|
LOG_TARGET,
|
||||||
};
|
};
|
||||||
use codec::{Codec, Decode, Encode};
|
use codec::{Codec, Decode, Encode};
|
||||||
@@ -36,7 +36,7 @@ use sp_api::{ApiExt, ProvideRuntimeApi};
|
|||||||
use sp_block_builder::BlockBuilder as BlockBuilderApi;
|
use sp_block_builder::BlockBuilder as BlockBuilderApi;
|
||||||
use sp_blockchain::HeaderBackend;
|
use sp_blockchain::HeaderBackend;
|
||||||
use sp_consensus::Error as ConsensusError;
|
use sp_consensus::Error as ConsensusError;
|
||||||
use sp_consensus_aura::{digests::CompatibleDigestItem, inherents::AuraInherentData, AuraApi};
|
use sp_consensus_aura::{inherents::AuraInherentData, AuraApi};
|
||||||
use sp_consensus_slots::Slot;
|
use sp_consensus_slots::Slot;
|
||||||
use sp_core::{crypto::Pair, ExecutionContext};
|
use sp_core::{crypto::Pair, ExecutionContext};
|
||||||
use sp_inherents::{CreateInherentDataProviders, InherentDataProvider as _};
|
use sp_inherents::{CreateInherentDataProviders, InherentDataProvider as _};
|
||||||
@@ -54,7 +54,7 @@ use std::{fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc};
|
|||||||
fn check_header<C, B: BlockT, P: Pair>(
|
fn check_header<C, B: BlockT, P: Pair>(
|
||||||
client: &C,
|
client: &C,
|
||||||
slot_now: Slot,
|
slot_now: Slot,
|
||||||
mut header: B::Header,
|
header: B::Header,
|
||||||
hash: B::Hash,
|
hash: B::Hash,
|
||||||
authorities: &[AuthorityId<P>],
|
authorities: &[AuthorityId<P>],
|
||||||
check_for_equivocation: CheckForEquivocation,
|
check_for_equivocation: CheckForEquivocation,
|
||||||
@@ -64,27 +64,16 @@ where
|
|||||||
C: sc_client_api::backend::AuxStore,
|
C: sc_client_api::backend::AuxStore,
|
||||||
P::Public: Encode + Decode + PartialEq + Clone,
|
P::Public: Encode + Decode + PartialEq + Clone,
|
||||||
{
|
{
|
||||||
let seal = header.digest_mut().pop().ok_or(Error::HeaderUnsealed(hash))?;
|
let check_result =
|
||||||
|
crate::standalone::check_header_slot_and_seal::<B, P>(slot_now, header, authorities);
|
||||||
|
|
||||||
let sig = seal.as_aura_seal().ok_or_else(|| aura_err(Error::HeaderBadSeal(hash)))?;
|
match check_result {
|
||||||
|
Ok((header, slot, seal)) => {
|
||||||
let slot = find_pre_digest::<B, P::Signature>(&header)?;
|
let expected_author = crate::standalone::slot_author::<P>(slot, &authorities);
|
||||||
|
let should_equiv_check = check_for_equivocation.check_for_equivocation();
|
||||||
if slot > slot_now {
|
if let (true, Some(expected)) = (should_equiv_check, expected_author) {
|
||||||
header.digest_mut().push(seal);
|
|
||||||
Ok(CheckedHeader::Deferred(header, slot))
|
|
||||||
} else {
|
|
||||||
// check the signature is valid under the expected authority and
|
|
||||||
// chain state.
|
|
||||||
let expected_author =
|
|
||||||
slot_author::<P>(slot, authorities).ok_or(Error::SlotAuthorNotFound)?;
|
|
||||||
|
|
||||||
let pre_hash = header.hash();
|
|
||||||
|
|
||||||
if P::verify(&sig, pre_hash.as_ref(), expected_author) {
|
|
||||||
if check_for_equivocation.check_for_equivocation() {
|
|
||||||
if let Some(equivocation_proof) =
|
if let Some(equivocation_proof) =
|
||||||
check_equivocation(client, slot_now, slot, &header, expected_author)
|
check_equivocation(client, slot_now, slot, &header, expected)
|
||||||
.map_err(Error::Client)?
|
.map_err(Error::Client)?
|
||||||
{
|
{
|
||||||
info!(
|
info!(
|
||||||
@@ -98,9 +87,14 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(CheckedHeader::Checked(header, (slot, seal)))
|
Ok(CheckedHeader::Checked(header, (slot, seal)))
|
||||||
} else {
|
},
|
||||||
Err(Error::BadSignature(hash))
|
Err(SealVerificationError::Deferred(header, slot)) =>
|
||||||
}
|
Ok(CheckedHeader::Deferred(header, slot)),
|
||||||
|
Err(SealVerificationError::Unsealed) => Err(Error::HeaderUnsealed(hash)),
|
||||||
|
Err(SealVerificationError::BadSeal) => Err(Error::HeaderBadSeal(hash)),
|
||||||
|
Err(SealVerificationError::BadSignature) => Err(Error::BadSignature(hash)),
|
||||||
|
Err(SealVerificationError::SlotAuthorNotFound) => Err(Error::SlotAuthorNotFound),
|
||||||
|
Err(SealVerificationError::InvalidPreDigest(e)) => Err(Error::from(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,10 @@
|
|||||||
use std::{fmt::Debug, hash::Hash, marker::PhantomData, pin::Pin, sync::Arc};
|
use std::{fmt::Debug, hash::Hash, marker::PhantomData, pin::Pin, sync::Arc};
|
||||||
|
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use log::{debug, trace};
|
|
||||||
|
|
||||||
use codec::{Codec, Decode, Encode};
|
use codec::{Codec, Decode, Encode};
|
||||||
|
|
||||||
use sc_client_api::{backend::AuxStore, BlockOf, UsageProvider};
|
use sc_client_api::{backend::AuxStore, BlockOf};
|
||||||
use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, StateAction};
|
use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, StateAction};
|
||||||
use sc_consensus_slots::{
|
use sc_consensus_slots::{
|
||||||
BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SimpleSlotWorkerToSlotWorker,
|
BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SimpleSlotWorkerToSlotWorker,
|
||||||
@@ -45,20 +44,19 @@ use sc_consensus_slots::{
|
|||||||
};
|
};
|
||||||
use sc_telemetry::TelemetryHandle;
|
use sc_telemetry::TelemetryHandle;
|
||||||
use sp_api::{Core, ProvideRuntimeApi};
|
use sp_api::{Core, ProvideRuntimeApi};
|
||||||
use sp_application_crypto::{AppCrypto, AppPublic};
|
use sp_application_crypto::AppPublic;
|
||||||
use sp_blockchain::{HeaderBackend, Result as CResult};
|
use sp_blockchain::HeaderBackend;
|
||||||
use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SelectChain};
|
use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SelectChain};
|
||||||
use sp_consensus_slots::Slot;
|
use sp_consensus_slots::Slot;
|
||||||
use sp_core::crypto::{ByteArray, Pair, Public};
|
use sp_core::crypto::{Pair, Public};
|
||||||
use sp_inherents::CreateInherentDataProviders;
|
use sp_inherents::CreateInherentDataProviders;
|
||||||
use sp_keystore::KeystorePtr;
|
use sp_keystore::KeystorePtr;
|
||||||
use sp_runtime::{
|
use sp_runtime::traits::{Block as BlockT, Header, Member, NumberFor};
|
||||||
traits::{Block as BlockT, Header, Member, NumberFor, Zero},
|
|
||||||
DigestItem,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod import_queue;
|
mod import_queue;
|
||||||
|
pub mod standalone;
|
||||||
|
|
||||||
|
pub use crate::standalone::{find_pre_digest, slot_duration};
|
||||||
pub use import_queue::{
|
pub use import_queue::{
|
||||||
build_verifier, import_queue, AuraVerifier, BuildVerifierParams, CheckForEquivocation,
|
build_verifier, import_queue, AuraVerifier, BuildVerifierParams, CheckForEquivocation,
|
||||||
ImportQueueParams,
|
ImportQueueParams,
|
||||||
@@ -112,39 +110,6 @@ impl<N> Default for CompatibilityMode<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the slot duration for Aura.
|
|
||||||
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>,
|
|
||||||
{
|
|
||||||
client
|
|
||||||
.runtime_api()
|
|
||||||
.slot_duration(client.usage_info().chain.best_hash)
|
|
||||||
.map_err(|err| err.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get slot author for given block along with authorities.
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parameters of [`start_aura`].
|
/// Parameters of [`start_aura`].
|
||||||
pub struct StartAuraParams<C, SC, I, PF, SO, L, CIDP, BS, N> {
|
pub struct StartAuraParams<C, SC, I, PF, SO, L, CIDP, BS, N> {
|
||||||
/// The duration of a slot.
|
/// The duration of a slot.
|
||||||
@@ -412,21 +377,11 @@ where
|
|||||||
slot: Slot,
|
slot: Slot,
|
||||||
authorities: &Self::AuxData,
|
authorities: &Self::AuxData,
|
||||||
) -> Option<Self::Claim> {
|
) -> Option<Self::Claim> {
|
||||||
let expected_author = slot_author::<P>(slot, authorities);
|
crate::standalone::claim_slot::<P>(slot, authorities, &self.keystore).await
|
||||||
expected_author.and_then(|p| {
|
|
||||||
if self
|
|
||||||
.keystore
|
|
||||||
.has_keys(&[(p.to_raw_vec(), sp_application_crypto::key_types::AURA)])
|
|
||||||
{
|
|
||||||
Some(p.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pre_digest_data(&self, slot: Slot, _claim: &Self::Claim) -> Vec<sp_runtime::DigestItem> {
|
fn pre_digest_data(&self, slot: Slot, _claim: &Self::Claim) -> Vec<sp_runtime::DigestItem> {
|
||||||
vec![<DigestItem as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)]
|
vec![crate::standalone::pre_digest::<P>(slot)]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_import_params(
|
async fn block_import_params(
|
||||||
@@ -441,28 +396,8 @@ where
|
|||||||
sc_consensus::BlockImportParams<B, <Self::BlockImport as BlockImport<B>>::Transaction>,
|
sc_consensus::BlockImportParams<B, <Self::BlockImport as BlockImport<B>>::Transaction>,
|
||||||
ConsensusError,
|
ConsensusError,
|
||||||
> {
|
> {
|
||||||
let signature = self
|
|
||||||
.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 =
|
let signature_digest_item =
|
||||||
<DigestItem as CompatibleDigestItem<P::Signature>>::aura_seal(signature);
|
crate::standalone::seal::<_, P>(header_hash, &public, &self.keystore)?;
|
||||||
|
|
||||||
let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
|
let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
|
||||||
import_block.post_digests.push(signature_digest_item);
|
import_block.post_digests.push(signature_digest_item);
|
||||||
@@ -526,11 +461,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn aura_err<B: BlockT>(error: Error<B>) -> Error<B> {
|
|
||||||
debug!(target: LOG_TARGET, "{}", error);
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Aura Errors
|
/// Aura Errors
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error<B: BlockT> {
|
pub enum Error<B: BlockT> {
|
||||||
@@ -569,22 +499,13 @@ impl<B: BlockT> From<Error<B>> for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get pre-digests from the header
|
impl<B: BlockT> From<crate::standalone::PreDigestLookupError> for Error<B> {
|
||||||
pub fn find_pre_digest<B: BlockT, Signature: Codec>(header: &B::Header) -> Result<Slot, Error<B>> {
|
fn from(e: crate::standalone::PreDigestLookupError) -> Self {
|
||||||
if header.number().is_zero() {
|
match e {
|
||||||
return Ok(0.into())
|
crate::standalone::PreDigestLookupError::MultipleHeaders => Error::MultipleHeaders,
|
||||||
}
|
crate::standalone::PreDigestLookupError::NoDigestFound => Error::NoDigestFound,
|
||||||
|
|
||||||
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(aura_err(Error::MultipleHeaders)),
|
|
||||||
(None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"),
|
|
||||||
(s, false) => pre_digest = s,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pre_digest.ok_or_else(|| aura_err(Error::NoDigestFound))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn authorities<A, B, C>(
|
fn authorities<A, B, C>(
|
||||||
@@ -637,7 +558,7 @@ mod tests {
|
|||||||
use sc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker};
|
use sc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker};
|
||||||
use sc_keystore::LocalKeystore;
|
use sc_keystore::LocalKeystore;
|
||||||
use sc_network_test::{Block as TestBlock, *};
|
use sc_network_test::{Block as TestBlock, *};
|
||||||
use sp_application_crypto::key_types::AURA;
|
use sp_application_crypto::{key_types::AURA, AppCrypto};
|
||||||
use sp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal};
|
use sp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal};
|
||||||
use sp_consensus_aura::sr25519::AuthorityPair;
|
use sp_consensus_aura::sr25519::AuthorityPair;
|
||||||
use sp_inherents::InherentData;
|
use sp_inherents::InherentData;
|
||||||
@@ -851,22 +772,6 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn authorities_call_works() {
|
|
||||||
let client = substrate_test_runtime_client::new();
|
|
||||||
|
|
||||||
assert_eq!(client.chain_info().best_number, 0);
|
|
||||||
assert_eq!(
|
|
||||||
authorities(&client, client.chain_info().best_hash, 1, &CompatibilityMode::None)
|
|
||||||
.unwrap(),
|
|
||||||
vec![
|
|
||||||
Keyring::Alice.public().into(),
|
|
||||||
Keyring::Bob.public().into(),
|
|
||||||
Keyring::Charlie.public().into()
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn current_node_authority_should_claim_slot() {
|
async fn current_node_authority_should_claim_slot() {
|
||||||
let net = AuraTestNet::new(4);
|
let net = AuraTestNet::new(4);
|
||||||
|
|||||||
@@ -0,0 +1,357 @@
|
|||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Standalone functions used within the implementation of Aura.
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get the slot duration for Aura by reading from a runtime API at the best block's state.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the slot duration for Aura by reading from a runtime API at a given block's state.
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the slot author for given block along with authorities.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to claim a slot using a keystore.
|
||||||
|
///
|
||||||
|
/// This returns `None` if the slot author is not locally controlled, and `Some` if it is,
|
||||||
|
/// with the public key of the slot 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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produce the pre-runtime digest containing the slot info.
|
||||||
|
///
|
||||||
|
/// This is intended to be put into the block header prior to runtime execution,
|
||||||
|
/// so the runtime can read the slot in this way.
|
||||||
|
pub fn pre_digest<P: Pair>(slot: Slot) -> sp_runtime::DigestItem
|
||||||
|
where
|
||||||
|
P::Signature: Codec,
|
||||||
|
{
|
||||||
|
<DigestItem as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produce the seal digest item by signing the hash of a block.
|
||||||
|
///
|
||||||
|
/// Note that after this is added to a block header, the hash of the block will change.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors in pre-digest lookup.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum PreDigestLookupError {
|
||||||
|
/// Multiple Aura pre-runtime headers
|
||||||
|
#[error("Multiple Aura pre-runtime headers")]
|
||||||
|
MultipleHeaders,
|
||||||
|
/// No Aura pre-runtime digest found
|
||||||
|
#[error("No Aura pre-runtime digest found")]
|
||||||
|
NoDigestFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a pre-digest from a block header.
|
||||||
|
///
|
||||||
|
/// This fails if there is no pre-digest or there are multiple.
|
||||||
|
///
|
||||||
|
/// Returns the `slot` stored in the pre-digest or an error if no pre-digest was found.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the current set of authorities from the runtime at a specific block.
|
||||||
|
///
|
||||||
|
/// The compatibility mode and context block number informs this function whether
|
||||||
|
/// to initialize the hypothetical block created by the runtime API as backwards compatibility
|
||||||
|
/// for older chains.
|
||||||
|
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 => {},
|
||||||
|
// Use `initialize_block` until we hit the block that should disable the mode.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the current set of authorities from a runtime at a specific block.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors in slot and seal verification.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum SealVerificationError<Header> {
|
||||||
|
/// Header is deferred to the future.
|
||||||
|
#[error("Header slot is in the future")]
|
||||||
|
Deferred(Header, Slot),
|
||||||
|
|
||||||
|
/// The header has no seal digest.
|
||||||
|
#[error("Header is unsealed.")]
|
||||||
|
Unsealed,
|
||||||
|
|
||||||
|
/// The header has a malformed seal.
|
||||||
|
#[error("Header has a malformed seal")]
|
||||||
|
BadSeal,
|
||||||
|
|
||||||
|
/// The header has a bad signature.
|
||||||
|
#[error("Header has a bad signature")]
|
||||||
|
BadSignature,
|
||||||
|
|
||||||
|
/// No slot author found.
|
||||||
|
#[error("No slot author for provided slot")]
|
||||||
|
SlotAuthorNotFound,
|
||||||
|
|
||||||
|
/// Header has no valid slot pre-digest.
|
||||||
|
#[error("Header has no valid slot pre-digest")]
|
||||||
|
InvalidPreDigest(PreDigestLookupError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check a header has been signed by the right key. If the slot is too far in the future, an error
|
||||||
|
/// will be returned. If it's successful, returns the pre-header (i.e. without the seal),
|
||||||
|
/// the slot, and the digest item containing the seal.
|
||||||
|
///
|
||||||
|
/// Note that this does not check for equivocations, and [`check_equivocation`] is recommended
|
||||||
|
/// for that purpose.
|
||||||
|
///
|
||||||
|
/// This digest item will always return `Some` when used with `as_aura_seal`.
|
||||||
|
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 {
|
||||||
|
// check the signature is valid under the expected authority and
|
||||||
|
// chain state.
|
||||||
|
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()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user