Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Refactor: extract most aura logic out to standalone module, make use of these #13764

Merged
merged 5 commits into from
Mar 31, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 20 additions & 26 deletions client/consensus/aura/src/import_queue.rs
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@
//! Module implementing the logic for verifying and importing AuRa blocks.

use crate::{
aura_err, authorities, find_pre_digest, slot_author, AuthorityId, CompatibilityMode, Error,
authorities, standalone::SealVerificationError, AuthorityId, CompatibilityMode, Error,
LOG_TARGET,
};
use codec::{Codec, Decode, Encode};
@@ -36,7 +36,7 @@ use sp_api::{ApiExt, ProvideRuntimeApi};
use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sp_blockchain::HeaderBackend;
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_core::{crypto::Pair, ExecutionContext};
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>(
client: &C,
slot_now: Slot,
mut header: B::Header,
header: B::Header,
hash: B::Hash,
authorities: &[AuthorityId<P>],
check_for_equivocation: CheckForEquivocation,
@@ -64,27 +64,16 @@ where
C: sc_client_api::backend::AuxStore,
P::Public: Encode + Decode + PartialEq + Clone,
{
let seal = header.digest_mut().pop().ok_or(Error::HeaderUnsealed(hash))?;

let sig = seal.as_aura_seal().ok_or_else(|| aura_err(Error::HeaderBadSeal(hash)))?;

let slot = find_pre_digest::<B, P::Signature>(&header)?;

if slot > slot_now {
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() {
let check_result =
crate::standalone::check_header_slot_and_seal::<B, P>(slot_now, header, authorities);

match check_result {
Ok((header, slot, seal)) => {
let expected_author = crate::standalone::slot_author::<P>(slot, &authorities);
let should_equiv_check = check_for_equivocation.check_for_equivocation();
if let (true, Some(expected)) = (should_equiv_check, expected_author) {
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)?
{
info!(
@@ -98,9 +87,14 @@ where
}

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)),
Comment on lines +91 to +97
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add SealVerificationError as variant to Error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would break the API, so I don't think it's a good idea.

SealVerificationError is basically just a copy of the Error variants that could be produced within seal verification.

}
}

127 changes: 16 additions & 111 deletions client/consensus/aura/src/lib.rs
Original file line number Diff line number Diff line change
@@ -33,32 +33,30 @@
use std::{fmt::Debug, hash::Hash, marker::PhantomData, pin::Pin, sync::Arc};

use futures::prelude::*;
use log::{debug, trace};

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_slots::{
BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SimpleSlotWorkerToSlotWorker,
SlotInfo, StorageChanges,
};
use sc_telemetry::TelemetryHandle;
use sp_api::{Core, ProvideRuntimeApi};
use sp_application_crypto::{AppCrypto, AppPublic};
use sp_blockchain::{HeaderBackend, Result as CResult};
use sp_application_crypto::AppPublic;
use sp_blockchain::HeaderBackend;
use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SelectChain};
use sp_consensus_slots::Slot;
use sp_core::crypto::{ByteArray, Pair, Public};
use sp_core::crypto::{Pair, Public};
use sp_inherents::CreateInherentDataProviders;
use sp_keystore::KeystorePtr;
use sp_runtime::{
traits::{Block as BlockT, Header, Member, NumberFor, Zero},
DigestItem,
};
use sp_runtime::traits::{Block as BlockT, Header, Member, NumberFor};

mod import_queue;
pub mod standalone;

pub use crate::standalone::{find_pre_digest, slot_duration};
pub use import_queue::{
build_verifier, import_queue, AuraVerifier, BuildVerifierParams, CheckForEquivocation,
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`].
pub struct StartAuraParams<C, SC, I, PF, SO, L, CIDP, BS, N> {
/// The duration of a slot.
@@ -412,21 +377,11 @@ where
slot: Slot,
authorities: &Self::AuxData,
) -> Option<Self::Claim> {
let expected_author = slot_author::<P>(slot, authorities);
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
}
})
crate::standalone::claim_slot::<P>(slot, authorities, &self.keystore).await
}

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(
@@ -441,28 +396,8 @@ where
sc_consensus::BlockImportParams<B, <Self::BlockImport as BlockImport<B>>::Transaction>,
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 =
<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);
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
#[derive(Debug, thiserror::Error)]
pub enum Error<B: BlockT> {
@@ -569,22 +499,13 @@ impl<B: BlockT> From<Error<B>> for String {
}
}

/// Get pre-digests from the header
pub fn find_pre_digest<B: BlockT, Signature: Codec>(header: &B::Header) -> Result<Slot, Error<B>> {
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(aura_err(Error::MultipleHeaders)),
(None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"),
(s, false) => pre_digest = s,
impl<B: BlockT> From<crate::standalone::PreDigestLookupError> for Error<B> {
fn from(e: crate::standalone::PreDigestLookupError) -> Self {
match e {
crate::standalone::PreDigestLookupError::MultipleHeaders => Error::MultipleHeaders,
crate::standalone::PreDigestLookupError::NoDigestFound => Error::NoDigestFound,
}
}
pre_digest.ok_or_else(|| aura_err(Error::NoDigestFound))
}

fn authorities<A, B, C>(
@@ -637,7 +558,7 @@ mod tests {
use sc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker};
use sc_keystore::LocalKeystore;
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_aura::sr25519::AuthorityPair;
use sp_inherents::InherentData;
@@ -851,22 +772,6 @@ mod tests {
.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]
async fn current_node_authority_should_claim_slot() {
let net = AuraTestNet::new(4);
357 changes: 357 additions & 0 deletions client/consensus/aura/src/standalone.rs
Original file line number Diff line number Diff line change
@@ -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()
]
);
}
}