mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 23:51:05 +00:00
* Split FinalitySyncPipeline and SourceClient * Move some logic to finality_base * Add empty equivocation detection clients * Add equivocation reporting logic to the source client * Use convenience trait for SubstrateFinalitySyncPipeline * Define JustificationVerificationContext for GRANDPA * Equivocation source client: finality_verification_context() * Equivocation source client: synced_headers_finality_info() * reuse HeaderFinalityInfo * Define EquivocationsFinder * Fix spellcheck * Address review comments * Avoid equivocations lookup errors
This commit is contained in:
committed by
Bastian Köcher
parent
9bfad80664
commit
48cae06a77
@@ -23,10 +23,11 @@ mod verification;
|
||||
|
||||
use crate::ChainWithGrandpa;
|
||||
pub use verification::{
|
||||
equivocation::{EquivocationsCollector, Error as EquivocationsCollectorError},
|
||||
equivocation::{EquivocationsCollector, GrandpaEquivocationsFinder},
|
||||
optimizer::verify_and_optimize_justification,
|
||||
strict::verify_justification,
|
||||
AncestryChain, Error as JustificationVerificationError, PrecommitError,
|
||||
AncestryChain, Error as JustificationVerificationError, JustificationVerificationContext,
|
||||
PrecommitError,
|
||||
};
|
||||
|
||||
use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId};
|
||||
|
||||
@@ -16,33 +16,26 @@
|
||||
|
||||
//! Logic for extracting equivocations from multiple GRANDPA Finality Proofs.
|
||||
|
||||
use crate::justification::{
|
||||
verification::{
|
||||
Error as JustificationVerificationError, JustificationVerifier, PrecommitError,
|
||||
SignedPrecommit,
|
||||
use crate::{
|
||||
justification::{
|
||||
verification::{
|
||||
Error as JustificationVerificationError, IterationFlow,
|
||||
JustificationVerificationContext, JustificationVerifier, PrecommitError,
|
||||
SignedPrecommit,
|
||||
},
|
||||
GrandpaJustification,
|
||||
},
|
||||
GrandpaJustification,
|
||||
ChainWithGrandpa, FindEquivocations,
|
||||
};
|
||||
|
||||
use crate::justification::verification::IterationFlow;
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use frame_support::RuntimeDebug;
|
||||
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit, SetId};
|
||||
use bp_runtime::{BlockNumberOf, HashOf, HeaderOf};
|
||||
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit};
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use sp_std::{
|
||||
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Justification is targeting unexpected round.
|
||||
InvalidRound,
|
||||
/// Justification verification error.
|
||||
JustificationVerification(JustificationVerificationError),
|
||||
}
|
||||
|
||||
enum AuthorityVotes<Header: HeaderT> {
|
||||
SingleVote(SignedPrecommit<Header>),
|
||||
Equivocation(
|
||||
@@ -53,8 +46,7 @@ enum AuthorityVotes<Header: HeaderT> {
|
||||
/// Structure that can extract equivocations from multiple GRANDPA justifications.
|
||||
pub struct EquivocationsCollector<'a, Header: HeaderT> {
|
||||
round: u64,
|
||||
authorities_set_id: SetId,
|
||||
authorities_set: &'a VoterSet<AuthorityId>,
|
||||
context: &'a JustificationVerificationContext,
|
||||
|
||||
votes: BTreeMap<AuthorityId, AuthorityVotes<Header>>,
|
||||
}
|
||||
@@ -62,38 +54,34 @@ pub struct EquivocationsCollector<'a, Header: HeaderT> {
|
||||
impl<'a, Header: HeaderT> EquivocationsCollector<'a, Header> {
|
||||
/// Create a new instance of `EquivocationsCollector`.
|
||||
pub fn new(
|
||||
authorities_set_id: SetId,
|
||||
authorities_set: &'a VoterSet<AuthorityId>,
|
||||
context: &'a JustificationVerificationContext,
|
||||
base_justification: &GrandpaJustification<Header>,
|
||||
) -> Result<Self, Error> {
|
||||
let mut checker = Self {
|
||||
round: base_justification.round,
|
||||
authorities_set_id,
|
||||
authorities_set,
|
||||
votes: BTreeMap::new(),
|
||||
};
|
||||
) -> Result<Self, JustificationVerificationError> {
|
||||
let mut checker = Self { round: base_justification.round, context, votes: BTreeMap::new() };
|
||||
|
||||
checker.verify_justification(
|
||||
(base_justification.commit.target_hash, base_justification.commit.target_number),
|
||||
checker.context,
|
||||
base_justification,
|
||||
)?;
|
||||
|
||||
checker.parse_justification(base_justification)?;
|
||||
Ok(checker)
|
||||
}
|
||||
|
||||
/// Parse an additional justification for equivocations.
|
||||
pub fn parse_justification(
|
||||
&mut self,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
// The justification should target the same round as the base justification.
|
||||
if self.round != justification.round {
|
||||
return Err(Error::InvalidRound)
|
||||
/// Parse additional justifications for equivocations.
|
||||
pub fn parse_justifications(&mut self, justifications: &[GrandpaJustification<Header>]) {
|
||||
let round = self.round;
|
||||
for justification in
|
||||
justifications.iter().filter(|justification| round == justification.round)
|
||||
{
|
||||
// We ignore the Errors received here since we don't care if the proofs are valid.
|
||||
// We only care about collecting equivocations.
|
||||
let _ = self.verify_justification(
|
||||
(justification.commit.target_hash, justification.commit.target_number),
|
||||
self.context,
|
||||
justification,
|
||||
);
|
||||
}
|
||||
|
||||
self.verify_justification(
|
||||
(justification.commit.target_hash, justification.commit.target_number),
|
||||
self.authorities_set_id,
|
||||
self.authorities_set,
|
||||
justification,
|
||||
)
|
||||
.map_err(Error::JustificationVerification)
|
||||
}
|
||||
|
||||
/// Extract the equivocation proofs that have been collected.
|
||||
@@ -102,7 +90,7 @@ impl<'a, Header: HeaderT> EquivocationsCollector<'a, Header> {
|
||||
for (_authority, vote) in self.votes {
|
||||
if let AuthorityVotes::Equivocation(equivocation) = vote {
|
||||
equivocations.push(EquivocationProof::new(
|
||||
self.authorities_set_id,
|
||||
self.context.authority_set_id,
|
||||
sp_consensus_grandpa::Equivocation::Precommit(equivocation),
|
||||
));
|
||||
}
|
||||
@@ -177,3 +165,29 @@ impl<'a, Header: HeaderT> JustificationVerifier<Header> for EquivocationsCollect
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for finding equivocations in GRANDPA proofs.
|
||||
pub struct GrandpaEquivocationsFinder<C>(sp_std::marker::PhantomData<C>);
|
||||
|
||||
impl<C: ChainWithGrandpa>
|
||||
FindEquivocations<
|
||||
GrandpaJustification<HeaderOf<C>>,
|
||||
JustificationVerificationContext,
|
||||
EquivocationProof<HashOf<C>, BlockNumberOf<C>>,
|
||||
> for GrandpaEquivocationsFinder<C>
|
||||
{
|
||||
type Error = JustificationVerificationError;
|
||||
|
||||
fn find_equivocations(
|
||||
verification_context: &JustificationVerificationContext,
|
||||
synced_proof: &GrandpaJustification<HeaderOf<C>>,
|
||||
source_proofs: &[GrandpaJustification<HeaderOf<C>>],
|
||||
) -> Result<Vec<EquivocationProof<HashOf<C>, BlockNumberOf<C>>>, Self::Error> {
|
||||
let mut equivocations_collector =
|
||||
EquivocationsCollector::new(verification_context, synced_proof)?;
|
||||
|
||||
equivocations_collector.parse_justifications(source_proofs);
|
||||
|
||||
Ok(equivocations_collector.into_equivocation_proofs())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ pub mod equivocation;
|
||||
pub mod optimizer;
|
||||
pub mod strict;
|
||||
|
||||
use crate::justification::GrandpaJustification;
|
||||
use crate::{justification::GrandpaJustification, AuthoritySet};
|
||||
|
||||
use bp_runtime::HeaderId;
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
@@ -114,6 +114,8 @@ impl<Header: HeaderT> AncestryChain<Header> {
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Could not convert `AuthorityList` to `VoterSet`.
|
||||
InvalidAuthorityList,
|
||||
/// Justification is finalizing unexpected header.
|
||||
InvalidJustificationTarget,
|
||||
/// Error validating a precommit
|
||||
@@ -141,6 +143,24 @@ pub enum PrecommitError {
|
||||
UnrelatedAncestryVote,
|
||||
}
|
||||
|
||||
/// The context needed for validating GRANDPA finality proofs.
|
||||
pub struct JustificationVerificationContext {
|
||||
/// The authority set used to verify the justification.
|
||||
pub voter_set: VoterSet<AuthorityId>,
|
||||
/// The ID of the authority set used to verify the justification.
|
||||
pub authority_set_id: SetId,
|
||||
}
|
||||
|
||||
impl TryFrom<AuthoritySet> for JustificationVerificationContext {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(authority_set: AuthoritySet) -> Result<Self, Self::Error> {
|
||||
let voter_set =
|
||||
VoterSet::new(authority_set.authorities).ok_or(Error::InvalidAuthorityList)?;
|
||||
Ok(JustificationVerificationContext { voter_set, authority_set_id: authority_set.set_id })
|
||||
}
|
||||
}
|
||||
|
||||
enum IterationFlow {
|
||||
Run,
|
||||
Skip,
|
||||
@@ -185,8 +205,7 @@ trait JustificationVerifier<Header: HeaderT> {
|
||||
fn verify_justification(
|
||||
&mut self,
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
authorities_set_id: SetId,
|
||||
authorities_set: &VoterSet<AuthorityId>,
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
// ensure that it is justification for the expected header
|
||||
@@ -196,7 +215,7 @@ trait JustificationVerifier<Header: HeaderT> {
|
||||
return Err(Error::InvalidJustificationTarget)
|
||||
}
|
||||
|
||||
let threshold = authorities_set.threshold().get();
|
||||
let threshold = context.voter_set.threshold().get();
|
||||
let mut chain = AncestryChain::new(justification);
|
||||
let mut signature_buffer = Vec::new();
|
||||
let mut cumulative_weight = 0u64;
|
||||
@@ -211,7 +230,7 @@ trait JustificationVerifier<Header: HeaderT> {
|
||||
}
|
||||
|
||||
// authority must be in the set
|
||||
let authority_info = match authorities_set.get(&signed.id) {
|
||||
let authority_info = match context.voter_set.get(&signed.id) {
|
||||
Some(authority_info) => {
|
||||
// The implementer may want to do extra checks here.
|
||||
// For example to see if the authority has already voted in the same round.
|
||||
@@ -248,7 +267,7 @@ trait JustificationVerifier<Header: HeaderT> {
|
||||
&signed.id,
|
||||
&signed.signature,
|
||||
justification.round,
|
||||
authorities_set_id,
|
||||
context.authority_set_id,
|
||||
&mut signature_buffer,
|
||||
) {
|
||||
self.process_invalid_signature_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
|
||||
@@ -21,9 +21,10 @@ use crate::justification::{
|
||||
GrandpaJustification,
|
||||
};
|
||||
|
||||
use crate::justification::verification::{IterationFlow, SignedPrecommit};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use sp_consensus_grandpa::{AuthorityId, SetId};
|
||||
use crate::justification::verification::{
|
||||
IterationFlow, JustificationVerificationContext, SignedPrecommit,
|
||||
};
|
||||
use sp_consensus_grandpa::AuthorityId;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use sp_std::{collections::btree_set::BTreeSet, prelude::*};
|
||||
|
||||
@@ -111,8 +112,7 @@ impl<Header: HeaderT> JustificationVerifier<Header> for JustificationOptimizer<H
|
||||
/// Verify and optimize given justification by removing unknown and duplicate votes.
|
||||
pub fn verify_and_optimize_justification<Header: HeaderT>(
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
authorities_set_id: SetId,
|
||||
authorities_set: &VoterSet<AuthorityId>,
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &mut GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
let mut optimizer = JustificationOptimizer {
|
||||
@@ -120,12 +120,7 @@ pub fn verify_and_optimize_justification<Header: HeaderT>(
|
||||
extra_precommits: vec![],
|
||||
redundant_votes_ancestries: Default::default(),
|
||||
};
|
||||
optimizer.verify_justification(
|
||||
finalized_target,
|
||||
authorities_set_id,
|
||||
authorities_set,
|
||||
justification,
|
||||
)?;
|
||||
optimizer.verify_justification(finalized_target, context, justification)?;
|
||||
optimizer.optimize(justification);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -21,9 +21,10 @@ use crate::justification::{
|
||||
GrandpaJustification,
|
||||
};
|
||||
|
||||
use crate::justification::verification::{IterationFlow, SignedPrecommit};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use sp_consensus_grandpa::{AuthorityId, SetId};
|
||||
use crate::justification::verification::{
|
||||
IterationFlow, JustificationVerificationContext, SignedPrecommit,
|
||||
};
|
||||
use sp_consensus_grandpa::AuthorityId;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use sp_std::collections::btree_set::BTreeSet;
|
||||
|
||||
@@ -92,15 +93,9 @@ impl<Header: HeaderT> JustificationVerifier<Header> for StrictJustificationVerif
|
||||
/// Verify that justification, that is generated by given authority set, finalizes given header.
|
||||
pub fn verify_justification<Header: HeaderT>(
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
authorities_set_id: SetId,
|
||||
authorities_set: &VoterSet<AuthorityId>,
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
let mut verifier = StrictJustificationVerifier { votes: BTreeSet::new() };
|
||||
verifier.verify_justification(
|
||||
finalized_target,
|
||||
authorities_set_id,
|
||||
authorities_set,
|
||||
justification,
|
||||
)
|
||||
verifier.verify_justification(finalized_target, context, justification)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use crate::justification::{
|
||||
GrandpaJustification, JustificationVerificationContext, JustificationVerificationError,
|
||||
};
|
||||
use bp_runtime::{
|
||||
BasicOperatingMode, Chain, HashOf, HasherOf, HeaderOf, RawStorageProof, StorageProofChecker,
|
||||
StorageProofError, UnderlyingChainProvider,
|
||||
@@ -30,7 +33,7 @@ use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_consensus_grandpa::{AuthorityList, ConsensusLog, SetId, GRANDPA_ENGINE_ID};
|
||||
use sp_runtime::{traits::Header as HeaderT, Digest, RuntimeDebug};
|
||||
use sp_std::boxed::Box;
|
||||
use sp_std::{boxed::Box, vec::Vec};
|
||||
|
||||
pub mod justification;
|
||||
pub mod storage_keys;
|
||||
@@ -172,13 +175,48 @@ impl<Number: Codec> ConsensusLogReader for GrandpaConsensusLogReader<Number> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Grandpa-related info associated to a header.
|
||||
/// The finality-related info associated to a header.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)]
|
||||
pub struct HeaderGrandpaInfo<Header: HeaderT> {
|
||||
/// The header justification
|
||||
pub justification: justification::GrandpaJustification<Header>,
|
||||
/// The authority set introduced by the header.
|
||||
pub authority_set: Option<AuthoritySet>,
|
||||
pub struct HeaderFinalityInfo<FinalityProof, FinalityVerificationContext> {
|
||||
/// The header finality proof.
|
||||
pub finality_proof: FinalityProof,
|
||||
/// The new verification context introduced by the header.
|
||||
pub new_verification_context: Option<FinalityVerificationContext>,
|
||||
}
|
||||
|
||||
/// Grandpa-related info associated to a header. This info can be saved to events.
|
||||
pub type StoredHeaderGrandpaInfo<Header> =
|
||||
HeaderFinalityInfo<GrandpaJustification<Header>, AuthoritySet>;
|
||||
|
||||
/// Processed Grandpa-related info associated to a header.
|
||||
pub type HeaderGrandpaInfo<Header> =
|
||||
HeaderFinalityInfo<GrandpaJustification<Header>, JustificationVerificationContext>;
|
||||
|
||||
impl<Header: HeaderT> TryFrom<StoredHeaderGrandpaInfo<Header>> for HeaderGrandpaInfo<Header> {
|
||||
type Error = JustificationVerificationError;
|
||||
|
||||
fn try_from(grandpa_info: StoredHeaderGrandpaInfo<Header>) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
finality_proof: grandpa_info.finality_proof,
|
||||
new_verification_context: match grandpa_info.new_verification_context {
|
||||
Some(authority_set) => Some(authority_set.try_into()?),
|
||||
None => None,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for finding equivocations in finality proofs.
|
||||
pub trait FindEquivocations<FinalityProof, FinalityVerificationContext, EquivocationProof> {
|
||||
/// The type returned when encountering an error while looking for equivocations.
|
||||
type Error;
|
||||
|
||||
/// Find equivocations.
|
||||
fn find_equivocations(
|
||||
verification_context: &FinalityVerificationContext,
|
||||
synced_proof: &FinalityProof,
|
||||
source_proofs: &[FinalityProof],
|
||||
) -> Result<Vec<EquivocationProof>, Self::Error>;
|
||||
}
|
||||
|
||||
/// A minimized version of `pallet-bridge-grandpa::Call` that can be used without a runtime.
|
||||
@@ -244,16 +282,17 @@ pub trait ChainWithGrandpa: Chain {
|
||||
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32;
|
||||
}
|
||||
|
||||
/// A trait that provides the type of the underlying `ChainWithGrandpa`.
|
||||
pub trait UnderlyingChainWithGrandpaProvider: UnderlyingChainProvider {
|
||||
/// Underlying `ChainWithGrandpa` type.
|
||||
type ChainWithGrandpa: ChainWithGrandpa;
|
||||
}
|
||||
|
||||
impl<T> UnderlyingChainWithGrandpaProvider for T
|
||||
impl<T> ChainWithGrandpa for T
|
||||
where
|
||||
T: UnderlyingChainProvider,
|
||||
T: Chain + UnderlyingChainProvider,
|
||||
T::Chain: ChainWithGrandpa,
|
||||
{
|
||||
type ChainWithGrandpa = T::Chain;
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str =
|
||||
<T::Chain as ChainWithGrandpa>::WITH_CHAIN_GRANDPA_PALLET_NAME;
|
||||
const MAX_AUTHORITIES_COUNT: u32 = <T::Chain as ChainWithGrandpa>::MAX_AUTHORITIES_COUNT;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 =
|
||||
<T::Chain as ChainWithGrandpa>::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY;
|
||||
const MAX_HEADER_SIZE: u32 = <T::Chain as ChainWithGrandpa>::MAX_HEADER_SIZE;
|
||||
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 =
|
||||
<T::Chain as ChainWithGrandpa>::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user