mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-10 02:57:58 +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
@@ -884,7 +884,7 @@ impl_runtime_apis! {
|
||||
}
|
||||
|
||||
fn synced_headers_grandpa_info(
|
||||
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_rialto::Header>> {
|
||||
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_rialto::Header>> {
|
||||
BridgeRialtoGrandpa::synced_headers_grandpa_info()
|
||||
}
|
||||
}
|
||||
@@ -895,7 +895,7 @@ impl_runtime_apis! {
|
||||
}
|
||||
|
||||
fn synced_headers_grandpa_info(
|
||||
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_westend::Header>> {
|
||||
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_westend::Header>> {
|
||||
BridgeWestendGrandpa::synced_headers_grandpa_info()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,7 +748,7 @@ impl_runtime_apis! {
|
||||
}
|
||||
|
||||
fn synced_headers_grandpa_info(
|
||||
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_millau::Header>> {
|
||||
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_millau::Header>> {
|
||||
BridgeMillauGrandpa::synced_headers_grandpa_info()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,7 +696,7 @@ impl_runtime_apis! {
|
||||
}
|
||||
|
||||
fn synced_headers_grandpa_info(
|
||||
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_millau::Header>> {
|
||||
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_millau::Header>> {
|
||||
BridgeMillauGrandpa::synced_headers_grandpa_info()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ pub use storage_types::StoredAuthoritySet;
|
||||
|
||||
use bp_header_chain::{
|
||||
justification::GrandpaJustification, AuthoritySet, ChainWithGrandpa, GrandpaConsensusLogReader,
|
||||
HeaderChain, HeaderGrandpaInfo, InitializationData, StoredHeaderData, StoredHeaderDataBuilder,
|
||||
HeaderChain, InitializationData, StoredHeaderData, StoredHeaderDataBuilder,
|
||||
StoredHeaderGrandpaInfo,
|
||||
};
|
||||
use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use frame_support::{dispatch::PostDispatchInfo, ensure, DefaultNoBound};
|
||||
use sp_runtime::{
|
||||
traits::{Header as HeaderT, Zero},
|
||||
@@ -241,9 +241,9 @@ pub mod pallet {
|
||||
Self::deposit_event(Event::UpdatedBestFinalizedHeader {
|
||||
number,
|
||||
hash,
|
||||
grandpa_info: HeaderGrandpaInfo {
|
||||
justification,
|
||||
authority_set: maybe_new_authority_set,
|
||||
grandpa_info: StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification,
|
||||
new_verification_context: maybe_new_authority_set,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -411,7 +411,7 @@ pub mod pallet {
|
||||
number: BridgedBlockNumber<T, I>,
|
||||
hash: BridgedBlockHash<T, I>,
|
||||
/// The Grandpa info associated to the new best finalized header.
|
||||
grandpa_info: HeaderGrandpaInfo<BridgedHeader<T, I>>,
|
||||
grandpa_info: StoredHeaderGrandpaInfo<BridgedHeader<T, I>>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -505,14 +505,9 @@ pub mod pallet {
|
||||
) -> Result<(), sp_runtime::DispatchError> {
|
||||
use bp_header_chain::justification::verify_justification;
|
||||
|
||||
let voter_set =
|
||||
VoterSet::new(authority_set.authorities).ok_or(<Error<T, I>>::InvalidAuthoritySet)?;
|
||||
let set_id = authority_set.set_id;
|
||||
|
||||
Ok(verify_justification::<BridgedHeader<T, I>>(
|
||||
(hash, number),
|
||||
set_id,
|
||||
&voter_set,
|
||||
&authority_set.try_into().map_err(|_| <Error<T, I>>::InvalidAuthoritySet)?,
|
||||
justification,
|
||||
)
|
||||
.map_err(|e| {
|
||||
@@ -617,7 +612,7 @@ where
|
||||
<T as frame_system::Config>::RuntimeEvent: TryInto<Event<T, I>>,
|
||||
{
|
||||
/// Get the GRANDPA justifications accepted in the current block.
|
||||
pub fn synced_headers_grandpa_info() -> Vec<HeaderGrandpaInfo<BridgedHeader<T, I>>> {
|
||||
pub fn synced_headers_grandpa_info() -> Vec<StoredHeaderGrandpaInfo<BridgedHeader<T, I>>> {
|
||||
frame_system::Pallet::<T>::read_events_no_consensus()
|
||||
.filter_map(|event| {
|
||||
if let Event::<T, I>::UpdatedBestFinalizedHeader { grandpa_info, .. } =
|
||||
@@ -934,9 +929,9 @@ mod tests {
|
||||
event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
|
||||
number: *header.number(),
|
||||
hash: header.hash(),
|
||||
grandpa_info: HeaderGrandpaInfo {
|
||||
justification: justification.clone(),
|
||||
authority_set: None,
|
||||
grandpa_info: StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification.clone(),
|
||||
new_verification_context: None,
|
||||
},
|
||||
}),
|
||||
topics: vec![],
|
||||
@@ -944,7 +939,10 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Pallet::<TestRuntime>::synced_headers_grandpa_info(),
|
||||
vec![HeaderGrandpaInfo { justification, authority_set: None }]
|
||||
vec![StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification,
|
||||
new_verification_context: None
|
||||
}]
|
||||
);
|
||||
})
|
||||
}
|
||||
@@ -1075,9 +1073,11 @@ mod tests {
|
||||
event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
|
||||
number: *header.number(),
|
||||
hash: header.hash(),
|
||||
grandpa_info: HeaderGrandpaInfo {
|
||||
justification: justification.clone(),
|
||||
authority_set: Some(<CurrentAuthoritySet<TestRuntime>>::get().into()),
|
||||
grandpa_info: StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification.clone(),
|
||||
new_verification_context: Some(
|
||||
<CurrentAuthoritySet<TestRuntime>>::get().into()
|
||||
),
|
||||
},
|
||||
}),
|
||||
topics: vec![],
|
||||
@@ -1085,9 +1085,11 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Pallet::<TestRuntime>::synced_headers_grandpa_info(),
|
||||
vec![HeaderGrandpaInfo {
|
||||
justification,
|
||||
authority_set: Some(<CurrentAuthoritySet<TestRuntime>>::get().into()),
|
||||
vec![StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification,
|
||||
new_verification_context: Some(
|
||||
<CurrentAuthoritySet<TestRuntime>>::get().into()
|
||||
),
|
||||
}]
|
||||
);
|
||||
})
|
||||
|
||||
@@ -697,7 +697,7 @@ pub(crate) mod tests {
|
||||
use bp_test_utils::prepare_parachain_heads_proof;
|
||||
use codec::Encode;
|
||||
|
||||
use bp_header_chain::{justification::GrandpaJustification, HeaderGrandpaInfo};
|
||||
use bp_header_chain::{justification::GrandpaJustification, StoredHeaderGrandpaInfo};
|
||||
use bp_parachains::{
|
||||
BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider,
|
||||
};
|
||||
@@ -1036,9 +1036,9 @@ pub(crate) mod tests {
|
||||
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
|
||||
number: 1,
|
||||
hash: relay_1_hash,
|
||||
grandpa_info: HeaderGrandpaInfo {
|
||||
justification,
|
||||
authority_set: None,
|
||||
grandpa_info: StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification,
|
||||
new_verification_context: None,
|
||||
},
|
||||
}
|
||||
),
|
||||
@@ -1177,9 +1177,9 @@ pub(crate) mod tests {
|
||||
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
|
||||
number: 1,
|
||||
hash: relay_1_hash,
|
||||
grandpa_info: HeaderGrandpaInfo {
|
||||
justification,
|
||||
authority_set: None,
|
||||
grandpa_info: StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification,
|
||||
new_verification_context: None,
|
||||
}
|
||||
}
|
||||
),
|
||||
@@ -1230,9 +1230,9 @@ pub(crate) mod tests {
|
||||
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
|
||||
number: 1,
|
||||
hash: relay_1_hash,
|
||||
grandpa_info: HeaderGrandpaInfo {
|
||||
justification: justification.clone(),
|
||||
authority_set: None,
|
||||
grandpa_info: StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification.clone(),
|
||||
new_verification_context: None,
|
||||
}
|
||||
}
|
||||
),
|
||||
@@ -1271,9 +1271,9 @@ pub(crate) mod tests {
|
||||
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
|
||||
number: 1,
|
||||
hash: relay_1_hash,
|
||||
grandpa_info: HeaderGrandpaInfo {
|
||||
justification,
|
||||
authority_set: None,
|
||||
grandpa_info: StoredHeaderGrandpaInfo {
|
||||
finality_proof: justification,
|
||||
new_verification_context: None,
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -22,14 +22,15 @@
|
||||
//! but their purpose is different.
|
||||
|
||||
use bp_header_chain::justification::{
|
||||
verify_justification, GrandpaJustification, JustificationVerificationError, PrecommitError,
|
||||
verify_justification, GrandpaJustification, JustificationVerificationContext,
|
||||
JustificationVerificationError, PrecommitError,
|
||||
};
|
||||
use bp_test_utils::{
|
||||
header_id, make_justification_for_header, signed_precommit, test_header, Account,
|
||||
JustificationGeneratorParams, ALICE, BOB, CHARLIE, DAVE, EVE, FERDIE, TEST_GRANDPA_SET_ID,
|
||||
};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use sp_consensus_grandpa::{AuthorityId, AuthorityWeight};
|
||||
use sp_consensus_grandpa::{AuthorityId, AuthorityWeight, SetId};
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
|
||||
type TestHeader = sp_runtime::testing::Header;
|
||||
@@ -81,6 +82,11 @@ fn full_voter_set() -> VoterSet<AuthorityId> {
|
||||
VoterSet::new(full_accounts_set().iter().map(|(id, w)| (AuthorityId::from(*id), *w))).unwrap()
|
||||
}
|
||||
|
||||
pub fn full_verification_context(set_id: SetId) -> JustificationVerificationContext {
|
||||
let voter_set = full_voter_set();
|
||||
JustificationVerificationContext { voter_set, authority_set_id: set_id }
|
||||
}
|
||||
|
||||
/// Get a minimal set of accounts.
|
||||
fn minimal_accounts_set() -> Vec<(Account, AuthorityWeight)> {
|
||||
// there are 5 accounts in the full set => we need 2/3 + 1 accounts, which results in 4 accounts
|
||||
@@ -115,8 +121,7 @@ fn same_result_when_precommit_target_has_lower_number_than_commit_target() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)),
|
||||
@@ -148,8 +153,7 @@ fn same_result_when_precommit_target_is_not_descendant_of_commit_target() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)),
|
||||
@@ -182,8 +186,7 @@ fn same_result_when_there_are_not_enough_cumulative_weight_to_finalize_commit_ta
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::TooLowCumulativeWeight),
|
||||
@@ -220,8 +223,7 @@ fn different_result_when_justification_contains_duplicate_vote() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
|
||||
@@ -261,8 +263,7 @@ fn different_results_when_authority_equivocates_once_in_a_round() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
|
||||
@@ -314,8 +315,7 @@ fn different_results_when_authority_equivocates_twice_in_a_round() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
|
||||
@@ -353,8 +353,7 @@ fn different_results_when_there_are_more_than_enough_votes() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::RedundantAuthorityVote)),
|
||||
@@ -394,8 +393,7 @@ fn different_results_when_there_is_a_vote_of_unknown_authority() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::UnknownAuthorityVote)),
|
||||
|
||||
@@ -25,19 +25,18 @@ type TestHeader = sp_runtime::testing::Header;
|
||||
|
||||
#[test]
|
||||
fn duplicate_votes_are_not_considered_equivocations() {
|
||||
let voter_set = voter_set();
|
||||
let verification_context = verification_context(TEST_GRANDPA_SET_ID);
|
||||
let base_justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
|
||||
let mut collector =
|
||||
EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap();
|
||||
collector.parse_justification(&base_justification.clone()).unwrap();
|
||||
EquivocationsCollector::new(&verification_context, &base_justification).unwrap();
|
||||
collector.parse_justifications(&[base_justification.clone()]);
|
||||
|
||||
assert_eq!(collector.into_equivocation_proofs().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equivocations_are_detected_in_base_justification_redundant_votes() {
|
||||
let voter_set = voter_set();
|
||||
let mut base_justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
|
||||
let first_vote = base_justification.commit.precommits[0].clone();
|
||||
@@ -49,8 +48,9 @@ fn equivocations_are_detected_in_base_justification_redundant_votes() {
|
||||
);
|
||||
base_justification.commit.precommits.push(equivocation.clone());
|
||||
|
||||
let verification_context = verification_context(TEST_GRANDPA_SET_ID);
|
||||
let collector =
|
||||
EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap();
|
||||
EquivocationsCollector::new(&verification_context, &base_justification).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
collector.into_equivocation_proofs(),
|
||||
@@ -80,7 +80,6 @@ fn equivocations_are_detected_in_base_justification_redundant_votes() {
|
||||
|
||||
#[test]
|
||||
fn equivocations_are_detected_in_extra_justification_redundant_votes() {
|
||||
let voter_set = voter_set();
|
||||
let base_justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
let first_vote = base_justification.commit.precommits[0].clone();
|
||||
|
||||
@@ -93,9 +92,10 @@ fn equivocations_are_detected_in_extra_justification_redundant_votes() {
|
||||
);
|
||||
extra_justification.commit.precommits.push(equivocation.clone());
|
||||
|
||||
let verification_context = verification_context(TEST_GRANDPA_SET_ID);
|
||||
let mut collector =
|
||||
EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap();
|
||||
collector.parse_justification(&extra_justification).unwrap();
|
||||
EquivocationsCollector::new(&verification_context, &base_justification).unwrap();
|
||||
collector.parse_justifications(&[extra_justification]);
|
||||
|
||||
assert_eq!(
|
||||
collector.into_equivocation_proofs(),
|
||||
|
||||
@@ -30,8 +30,7 @@ fn optimizer_does_noting_with_minimal_justification() {
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -53,8 +52,7 @@ fn unknown_authority_votes_are_removed_by_optimizer() {
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -74,8 +72,7 @@ fn duplicate_authority_votes_are_removed_by_optimizer() {
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -105,8 +102,7 @@ fn invalid_authority_signatures_are_removed_by_optimizer() {
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -128,8 +124,7 @@ fn redundant_authority_votes_are_removed_by_optimizer() {
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -154,8 +149,7 @@ fn unrelated_ancestry_votes_are_removed_by_optimizer() {
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(2),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -172,8 +166,7 @@ fn redundant_votes_ancestries_are_removed_by_optimizer() {
|
||||
let num_votes_ancestries_before = justification.votes_ancestries.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
//! Tests for Grandpa strict justification verifier code.
|
||||
|
||||
use bp_header_chain::justification::{
|
||||
required_justification_precommits, verify_justification, JustificationVerificationError,
|
||||
PrecommitError,
|
||||
required_justification_precommits, verify_justification, JustificationVerificationContext,
|
||||
JustificationVerificationError, PrecommitError,
|
||||
};
|
||||
use bp_test_utils::*;
|
||||
|
||||
@@ -40,8 +40,7 @@ fn valid_justification_accepted() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Ok(()),
|
||||
@@ -65,8 +64,7 @@ fn valid_justification_accepted_with_single_fork() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&make_justification_for_header::<TestHeader>(params)
|
||||
),
|
||||
Ok(()),
|
||||
@@ -100,8 +98,7 @@ fn valid_justification_accepted_with_arbitrary_number_of_authorities() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set,
|
||||
&JustificationVerificationContext { voter_set, authority_set_id: TEST_GRANDPA_SET_ID },
|
||||
&make_justification_for_header::<TestHeader>(params)
|
||||
),
|
||||
Ok(()),
|
||||
@@ -113,8 +110,7 @@ fn justification_with_invalid_target_rejected() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(2),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&make_default_justification::<TestHeader>(&test_header(1)),
|
||||
),
|
||||
Err(JustificationVerificationError::InvalidJustificationTarget),
|
||||
@@ -129,8 +125,7 @@ fn justification_with_invalid_commit_rejected() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::TooLowCumulativeWeight),
|
||||
@@ -146,8 +141,7 @@ fn justification_with_invalid_authority_signature_rejected() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::InvalidAuthoritySignature)),
|
||||
@@ -162,8 +156,7 @@ fn justification_with_invalid_precommit_ancestry() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::RedundantVotesAncestries),
|
||||
@@ -187,8 +180,7 @@ fn justification_is_invalid_if_we_dont_meet_threshold() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&make_justification_for_header::<TestHeader>(params)
|
||||
),
|
||||
Err(JustificationVerificationError::TooLowCumulativeWeight),
|
||||
|
||||
@@ -321,7 +321,7 @@ macro_rules! decl_bridge_finality_runtime_apis {
|
||||
}
|
||||
};
|
||||
($chain: ident, grandpa) => {
|
||||
decl_bridge_finality_runtime_apis!($chain, grandpa => bp_header_chain::HeaderGrandpaInfo<Header>);
|
||||
decl_bridge_finality_runtime_apis!($chain, grandpa => bp_header_chain::StoredHeaderGrandpaInfo<Header>);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
//! Utilities for working with test accounts.
|
||||
|
||||
use bp_header_chain::{justification::JustificationVerificationContext, AuthoritySet};
|
||||
use codec::Encode;
|
||||
use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight};
|
||||
use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
@@ -78,6 +79,11 @@ pub fn voter_set() -> VoterSet<AuthorityId> {
|
||||
VoterSet::new(authority_list()).unwrap()
|
||||
}
|
||||
|
||||
/// Get a valid justification verification context for a GRANDPA round.
|
||||
pub fn verification_context(set_id: SetId) -> JustificationVerificationContext {
|
||||
AuthoritySet { authorities: authority_list(), set_id }.try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Convenience function to get a list of Grandpa authorities.
|
||||
pub fn authority_list() -> AuthorityList {
|
||||
test_keyring().iter().map(|(id, w)| (AuthorityId::from(*id), *w)).collect()
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use relay_substrate_client::{AccountIdOf, AccountKeyPairOf};
|
||||
use sp_core::Pair;
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
@@ -76,10 +74,7 @@ pub enum RelayHeadersBridge {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
trait HeadersRelayer: RelayToRelayHeadersCliBridge
|
||||
where
|
||||
AccountIdOf<Self::Target>: From<<AccountKeyPairOf<Self::Target> as Pair>::Public>,
|
||||
{
|
||||
trait HeadersRelayer: RelayToRelayHeadersCliBridge {
|
||||
/// Relay headers.
|
||||
async fn relay_headers(data: RelayHeaders) -> anyhow::Result<()> {
|
||||
let source_client = data.source.into_client::<Self::Source>().await?;
|
||||
|
||||
@@ -16,16 +16,17 @@
|
||||
|
||||
use crate::calls::UtilityCall;
|
||||
|
||||
use bp_header_chain::UnderlyingChainWithGrandpaProvider;
|
||||
use bp_header_chain::ChainWithGrandpa as ChainWithGrandpaBase;
|
||||
use bp_messages::MessageNonce;
|
||||
use bp_runtime::{
|
||||
Chain as ChainBase, ChainId, EncodedOrDecodedCall, HashOf, Parachain as ParachainBase,
|
||||
TransactionEra, TransactionEraOf, UnderlyingChainProvider,
|
||||
};
|
||||
use codec::{Codec, Encode};
|
||||
use codec::{Codec, Decode, Encode};
|
||||
use jsonrpsee::core::{DeserializeOwned, Serialize};
|
||||
use num_traits::Zero;
|
||||
use sc_transaction_pool_api::TransactionStatus;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{
|
||||
generic::SignedBlock,
|
||||
@@ -78,7 +79,7 @@ pub trait RelayChain: Chain {
|
||||
///
|
||||
/// Keep in mind that parachains are relying on relay chain GRANDPA, so they should not implement
|
||||
/// this trait.
|
||||
pub trait ChainWithGrandpa: Chain + UnderlyingChainWithGrandpaProvider {
|
||||
pub trait ChainWithGrandpa: Chain + ChainWithGrandpaBase {
|
||||
/// Name of the runtime API method that is returning the GRANDPA info associated with the
|
||||
/// headers accepted by the `submit_finality_proofs` extrinsic in the queried block.
|
||||
///
|
||||
@@ -87,7 +88,7 @@ pub trait ChainWithGrandpa: Chain + UnderlyingChainWithGrandpaProvider {
|
||||
const SYNCED_HEADERS_GRANDPA_INFO_METHOD: &'static str;
|
||||
|
||||
/// The type of the key owner proof used by the grandpa engine.
|
||||
type KeyOwnerProof;
|
||||
type KeyOwnerProof: Decode + TypeInfo + Send;
|
||||
}
|
||||
|
||||
/// Substrate-based parachain from minimal relay-client point of view.
|
||||
|
||||
@@ -22,8 +22,8 @@ use crate::{
|
||||
SubstrateAuthorClient, SubstrateChainClient, SubstrateFinalityClient,
|
||||
SubstrateFrameSystemClient, SubstrateStateClient, SubstrateSystemClient,
|
||||
},
|
||||
transaction_stall_timeout, AccountKeyPairOf, ConnectionParams, Error, HashOf, HeaderIdOf,
|
||||
Result, SignParam, TransactionTracker, UnsignedTransaction,
|
||||
transaction_stall_timeout, AccountKeyPairOf, ChainWithGrandpa, ConnectionParams, Error, HashOf,
|
||||
HeaderIdOf, Result, SignParam, TransactionTracker, UnsignedTransaction,
|
||||
};
|
||||
|
||||
use async_std::sync::{Arc, Mutex, RwLock};
|
||||
@@ -715,15 +715,16 @@ impl<C: Chain> Client<C> {
|
||||
Ok(Subscription(Mutex::new(receiver)))
|
||||
}
|
||||
|
||||
// TODO: remove warning after implementing
|
||||
// https://github.com/paritytech/parity-bridges-common/issues/39
|
||||
#[allow(dead_code)]
|
||||
async fn generate_grandpa_key_ownership_proof(
|
||||
/// Generates a proof of key ownership for the given authority in the given set.
|
||||
pub async fn generate_grandpa_key_ownership_proof(
|
||||
&self,
|
||||
at: HashOf<C>,
|
||||
set_id: sp_consensus_grandpa::SetId,
|
||||
authority_id: sp_consensus_grandpa::AuthorityId,
|
||||
) -> Result<Option<sp_consensus_grandpa::OpaqueKeyOwnershipProof>> {
|
||||
) -> Result<Option<sp_consensus_grandpa::OpaqueKeyOwnershipProof>>
|
||||
where
|
||||
C: ChainWithGrandpa,
|
||||
{
|
||||
self.typed_state_call(
|
||||
SUB_API_GRANDPA_GENERATE_KEY_OWNERSHIP_PROOF.into(),
|
||||
(set_id, authority_id),
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "equivocation-detector"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
description = "Equivocation detector"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1"
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
finality-relay = { path = "../finality" }
|
||||
relay-utils = { path = "../utils" }
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::{FindEquivocations, HeaderFinalityInfo};
|
||||
use finality_relay::{FinalityPipeline, SourceClientBase};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, TransactionTracker};
|
||||
|
||||
pub trait EquivocationDetectionPipeline: FinalityPipeline {
|
||||
/// Block number of the target chain.
|
||||
type TargetNumber: relay_utils::BlockNumberBase;
|
||||
/// The context needed for validating finality proofs.
|
||||
type FinalityVerificationContext;
|
||||
/// The type of the equivocation proof.
|
||||
type EquivocationProof;
|
||||
/// The equivocations finder.
|
||||
type EquivocationsFinder: FindEquivocations<
|
||||
Self::FinalityProof,
|
||||
Self::FinalityVerificationContext,
|
||||
Self::EquivocationProof,
|
||||
>;
|
||||
}
|
||||
|
||||
/// Source client used in equivocation detection loop.
|
||||
#[async_trait]
|
||||
pub trait SourceClient<P: EquivocationDetectionPipeline>: SourceClientBase<P> {
|
||||
/// Transaction tracker to track submitted transactions.
|
||||
type TransactionTracker: TransactionTracker;
|
||||
|
||||
/// Report equivocation.
|
||||
async fn report_equivocation(
|
||||
&self,
|
||||
at: P::Hash,
|
||||
equivocation: P::EquivocationProof,
|
||||
) -> Result<Self::TransactionTracker, Self::Error>;
|
||||
}
|
||||
|
||||
/// Target client used in equivocation detection loop.
|
||||
#[async_trait]
|
||||
pub trait TargetClient<P: EquivocationDetectionPipeline>: RelayClient {
|
||||
/// Get the data stored by the target at the specified block for validating source finality
|
||||
/// proofs.
|
||||
async fn finality_verification_context(
|
||||
&self,
|
||||
at: P::TargetNumber,
|
||||
) -> Result<P::FinalityVerificationContext, Self::Error>;
|
||||
|
||||
/// Get the finality info associated to the source headers synced with the target chain at the
|
||||
/// specified block.
|
||||
async fn synced_headers_finality_info(
|
||||
&self,
|
||||
at: P::TargetNumber,
|
||||
) -> Result<
|
||||
Vec<HeaderFinalityInfo<P::FinalityProof, P::FinalityVerificationContext>>,
|
||||
Self::Error,
|
||||
>;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::FinalityProof;
|
||||
use futures::Stream;
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Base finality pipeline.
|
||||
pub trait FinalityPipeline: 'static + Clone + Debug + Send + Sync {
|
||||
/// Name of the finality proofs source.
|
||||
const SOURCE_NAME: &'static str;
|
||||
/// Name of the finality proofs target.
|
||||
const TARGET_NAME: &'static str;
|
||||
|
||||
/// Synced headers are identified by this hash.
|
||||
type Hash: Eq + Clone + Copy + Send + Sync + Debug;
|
||||
/// Synced headers are identified by this number.
|
||||
type Number: relay_utils::BlockNumberBase;
|
||||
/// Finality proof type.
|
||||
type FinalityProof: FinalityProof<Self::Number>;
|
||||
}
|
||||
|
||||
/// Source client used in finality related loops.
|
||||
#[async_trait]
|
||||
pub trait SourceClientBase<P: FinalityPipeline>: RelayClient {
|
||||
/// Stream of new finality proofs. The stream is allowed to miss proofs for some
|
||||
/// headers, even if those headers are mandatory.
|
||||
type FinalityProofsStream: Stream<Item = P::FinalityProof> + Send + Unpin;
|
||||
|
||||
/// Subscribe to new finality proofs.
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Self::Error>;
|
||||
}
|
||||
|
||||
/// Target client used in finality related loops.
|
||||
#[async_trait]
|
||||
pub trait TargetClientBase<P: FinalityPipeline>: RelayClient {}
|
||||
@@ -20,11 +20,13 @@
|
||||
//! assume that the persistent proof either exists, or will eventually become available.
|
||||
|
||||
use crate::{
|
||||
sync_loop_metrics::SyncLoopMetrics, FinalityProof, FinalitySyncPipeline, SourceHeader,
|
||||
sync_loop_metrics::SyncLoopMetrics, FinalityPipeline, FinalitySyncPipeline, SourceClientBase,
|
||||
SourceHeader,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use backoff::backoff::Backoff;
|
||||
use bp_header_chain::FinalityProof;
|
||||
use futures::{select, Future, FutureExt, Stream, StreamExt};
|
||||
use num_traits::{One, Saturating};
|
||||
use relay_utils::{
|
||||
@@ -66,11 +68,7 @@ pub struct FinalitySyncParams {
|
||||
|
||||
/// Source client used in finality synchronization loop.
|
||||
#[async_trait]
|
||||
pub trait SourceClient<P: FinalitySyncPipeline>: RelayClient {
|
||||
/// Stream of new finality proofs. The stream is allowed to miss proofs for some
|
||||
/// headers, even if those headers are mandatory.
|
||||
type FinalityProofsStream: Stream<Item = P::FinalityProof> + Send;
|
||||
|
||||
pub trait SourceClient<P: FinalitySyncPipeline>: SourceClientBase<P> {
|
||||
/// Get best finalized block number.
|
||||
async fn best_finalized_block_number(&self) -> Result<P::Number, Self::Error>;
|
||||
|
||||
@@ -79,9 +77,6 @@ pub trait SourceClient<P: FinalitySyncPipeline>: RelayClient {
|
||||
&self,
|
||||
number: P::Number,
|
||||
) -> Result<(P::Header, Option<P::FinalityProof>), Self::Error>;
|
||||
|
||||
/// Subscribe to new finality proofs.
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Self::Error>;
|
||||
}
|
||||
|
||||
/// Target client used in finality synchronization loop.
|
||||
@@ -143,10 +138,10 @@ pub async fn run<P: FinalitySyncPipeline>(
|
||||
pub(crate) type UnjustifiedHeaders<H> = Vec<H>;
|
||||
/// Finality proofs container. Ordered by target header number.
|
||||
pub(crate) type FinalityProofs<P> =
|
||||
Vec<(<P as FinalitySyncPipeline>::Number, <P as FinalitySyncPipeline>::FinalityProof)>;
|
||||
Vec<(<P as FinalityPipeline>::Number, <P as FinalityPipeline>::FinalityProof)>;
|
||||
/// Reference to finality proofs container.
|
||||
pub(crate) type FinalityProofsRef<'a, P> =
|
||||
&'a [(<P as FinalitySyncPipeline>::Number, <P as FinalitySyncPipeline>::FinalityProof)];
|
||||
&'a [(<P as FinalityPipeline>::Number, <P as FinalityPipeline>::FinalityProof)];
|
||||
|
||||
/// Error that may happen inside finality synchronization loop.
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -26,11 +26,11 @@ use crate::{
|
||||
SourceClient, TargetClient,
|
||||
},
|
||||
sync_loop_metrics::SyncLoopMetrics,
|
||||
FinalityProof, FinalitySyncPipeline, SourceHeader,
|
||||
FinalityPipeline, FinalitySyncPipeline, SourceClientBase, SourceHeader,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::GrandpaConsensusLogReader;
|
||||
use bp_header_chain::{FinalityProof, GrandpaConsensusLogReader};
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use parking_lot::Mutex;
|
||||
use relay_utils::{
|
||||
@@ -80,15 +80,18 @@ impl MaybeConnectionError for TestError {
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestFinalitySyncPipeline;
|
||||
|
||||
impl FinalitySyncPipeline for TestFinalitySyncPipeline {
|
||||
impl FinalityPipeline for TestFinalitySyncPipeline {
|
||||
const SOURCE_NAME: &'static str = "TestSource";
|
||||
const TARGET_NAME: &'static str = "TestTarget";
|
||||
|
||||
type Hash = TestHash;
|
||||
type Number = TestNumber;
|
||||
type FinalityProof = TestFinalityProof;
|
||||
}
|
||||
|
||||
impl FinalitySyncPipeline for TestFinalitySyncPipeline {
|
||||
type ConsensusLogReader = GrandpaConsensusLogReader<TestNumber>;
|
||||
type Header = TestSourceHeader;
|
||||
type FinalityProof = TestFinalityProof;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -146,9 +149,18 @@ impl RelayClient for TestSourceClient {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SourceClient<TestFinalitySyncPipeline> for TestSourceClient {
|
||||
impl SourceClientBase<TestFinalitySyncPipeline> for TestSourceClient {
|
||||
type FinalityProofsStream = Pin<Box<dyn Stream<Item = TestFinalityProof> + 'static + Send>>;
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, TestError> {
|
||||
let mut data = self.data.lock();
|
||||
(self.on_method_call)(&mut data);
|
||||
Ok(futures::stream::iter(data.source_proofs.clone()).boxed())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SourceClient<TestFinalitySyncPipeline> for TestSourceClient {
|
||||
async fn best_finalized_block_number(&self) -> Result<TestNumber, TestError> {
|
||||
let mut data = self.data.lock();
|
||||
(self.on_method_call)(&mut data);
|
||||
@@ -163,12 +175,6 @@ impl SourceClient<TestFinalitySyncPipeline> for TestSourceClient {
|
||||
(self.on_method_call)(&mut data);
|
||||
data.source_headers.get(&number).cloned().ok_or(TestError::NonConnection)
|
||||
}
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, TestError> {
|
||||
let mut data = self.data.lock();
|
||||
(self.on_method_call)(&mut data);
|
||||
Ok(futures::stream::iter(data.source_proofs.clone()).boxed())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -20,34 +20,25 @@
|
||||
//! to submit all source headers to the target node.
|
||||
|
||||
pub use crate::{
|
||||
base::{FinalityPipeline, SourceClientBase},
|
||||
finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient},
|
||||
sync_loop_metrics::SyncLoopMetrics,
|
||||
};
|
||||
|
||||
use bp_header_chain::{ConsensusLogReader, FinalityProof};
|
||||
use bp_header_chain::ConsensusLogReader;
|
||||
use std::fmt::Debug;
|
||||
|
||||
mod base;
|
||||
mod finality_loop;
|
||||
mod finality_loop_tests;
|
||||
mod sync_loop_metrics;
|
||||
|
||||
/// Finality proofs synchronization pipeline.
|
||||
pub trait FinalitySyncPipeline: 'static + Clone + Debug + Send + Sync {
|
||||
/// Name of the finality proofs source.
|
||||
const SOURCE_NAME: &'static str;
|
||||
/// Name of the finality proofs target.
|
||||
const TARGET_NAME: &'static str;
|
||||
|
||||
/// Headers we're syncing are identified by this hash.
|
||||
type Hash: Eq + Clone + Copy + Send + Sync + Debug;
|
||||
/// Headers we're syncing are identified by this number.
|
||||
type Number: relay_utils::BlockNumberBase;
|
||||
pub trait FinalitySyncPipeline: FinalityPipeline {
|
||||
/// A reader that can extract the consensus log from the header digest and interpret it.
|
||||
type ConsensusLogReader: ConsensusLogReader;
|
||||
/// Type of header that we're syncing.
|
||||
type Header: SourceHeader<Self::Hash, Self::Number, Self::ConsensusLogReader>;
|
||||
/// Finality proof type.
|
||||
type FinalityProof: FinalityProof<Self::Number>;
|
||||
}
|
||||
|
||||
/// Header that we're receiving from source node.
|
||||
|
||||
@@ -24,6 +24,7 @@ bp-polkadot-core = { path = "../../primitives/polkadot-core" }
|
||||
bp-relayers = { path = "../../primitives/relayers" }
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
|
||||
equivocation-detector = { path = "../equivocation" }
|
||||
finality-grandpa = { version = "0.16.2" }
|
||||
finality-relay = { path = "../finality" }
|
||||
parachains-relay = { path = "../parachains" }
|
||||
|
||||
@@ -17,30 +17,96 @@
|
||||
//! Types and functions intended to ease adding of new Substrate -> Substrate
|
||||
//! equivocation detection pipelines.
|
||||
|
||||
use crate::finality_base::SubstrateFinalityPipeline;
|
||||
mod source;
|
||||
mod target;
|
||||
|
||||
use crate::finality_base::{engine::Engine, SubstrateFinalityPipeline, SubstrateFinalityProof};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_runtime::{AccountIdOf, BlockNumberOf, HashOf};
|
||||
use equivocation_detector::EquivocationDetectionPipeline;
|
||||
use finality_relay::FinalityPipeline;
|
||||
use pallet_grandpa::{Call as GrandpaCall, Config as GrandpaConfig};
|
||||
use relay_substrate_client::{AccountKeyPairOf, CallOf, Chain, ChainWithTransactions};
|
||||
use sp_core::Pair;
|
||||
use sp_runtime::traits::{Block, Header};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::finality_base::engine::Engine;
|
||||
use async_trait::async_trait;
|
||||
use bp_runtime::{BlockNumberOf, HashOf};
|
||||
use pallet_grandpa::{Call as GrandpaCall, Config as GrandpaConfig};
|
||||
use relay_substrate_client::CallOf;
|
||||
use sp_runtime::traits::{Block, Header};
|
||||
/// Convenience trait that adds bounds to `SubstrateEquivocationDetectionPipeline`.
|
||||
pub trait BaseSubstrateEquivocationDetectionPipeline:
|
||||
SubstrateFinalityPipeline<SourceChain = Self::BoundedSourceChain>
|
||||
{
|
||||
/// Bounded `SubstrateFinalityPipeline::SourceChain`.
|
||||
type BoundedSourceChain: ChainWithTransactions<AccountId = Self::BoundedSourceChainAccountId>;
|
||||
|
||||
/// Bounded `AccountIdOf<SubstrateFinalityPipeline::SourceChain>`.
|
||||
type BoundedSourceChainAccountId: From<<AccountKeyPairOf<Self::BoundedSourceChain> as Pair>::Public>
|
||||
+ Send;
|
||||
}
|
||||
|
||||
impl<T> BaseSubstrateEquivocationDetectionPipeline for T
|
||||
where
|
||||
T: SubstrateFinalityPipeline,
|
||||
T::SourceChain: ChainWithTransactions,
|
||||
AccountIdOf<T::SourceChain>: From<<AccountKeyPairOf<Self::SourceChain> as Pair>::Public>,
|
||||
{
|
||||
type BoundedSourceChain = T::SourceChain;
|
||||
type BoundedSourceChainAccountId = AccountIdOf<T::SourceChain>;
|
||||
}
|
||||
|
||||
/// Substrate -> Substrate equivocation detection pipeline.
|
||||
#[async_trait]
|
||||
pub trait SubstrateEquivocationDetectionPipeline: SubstrateFinalityPipeline {
|
||||
pub trait SubstrateEquivocationDetectionPipeline:
|
||||
BaseSubstrateEquivocationDetectionPipeline
|
||||
{
|
||||
/// How the `report_equivocation` call is built ?
|
||||
type ReportEquivocationCallBuilder: ReportEquivocationCallBuilder<Self>;
|
||||
}
|
||||
|
||||
type FinalityProoffOf<P> = <<P as SubstrateFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as SubstrateFinalityPipeline>::SourceChain,
|
||||
>>::FinalityProof;
|
||||
type FinalityVerificationContextfOf<P> =
|
||||
<<P as SubstrateFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as SubstrateFinalityPipeline>::SourceChain,
|
||||
>>::FinalityVerificationContext;
|
||||
type EquivocationProofOf<P> = <<P as SubstrateFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as SubstrateFinalityPipeline>::SourceChain,
|
||||
>>::EquivocationProof;
|
||||
type EquivocationsFinderOf<P> = <<P as SubstrateFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as SubstrateFinalityPipeline>::SourceChain,
|
||||
>>::EquivocationsFinder;
|
||||
type KeyOwnerProofOf<P> = <<P as SubstrateFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as SubstrateFinalityPipeline>::SourceChain,
|
||||
>>::KeyOwnerProof;
|
||||
|
||||
/// Adapter that allows a `SubstrateEquivocationDetectionPipeline` to act as an
|
||||
/// `EquivocationDetectionPipeline`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EquivocationDetectionPipelineAdapter<P: SubstrateEquivocationDetectionPipeline> {
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: SubstrateEquivocationDetectionPipeline> FinalityPipeline
|
||||
for EquivocationDetectionPipelineAdapter<P>
|
||||
{
|
||||
const SOURCE_NAME: &'static str = P::SourceChain::NAME;
|
||||
const TARGET_NAME: &'static str = P::TargetChain::NAME;
|
||||
|
||||
type Hash = HashOf<P::SourceChain>;
|
||||
type Number = BlockNumberOf<P::SourceChain>;
|
||||
type FinalityProof = SubstrateFinalityProof<P>;
|
||||
}
|
||||
|
||||
impl<P: SubstrateEquivocationDetectionPipeline> EquivocationDetectionPipeline
|
||||
for EquivocationDetectionPipelineAdapter<P>
|
||||
{
|
||||
type TargetNumber = BlockNumberOf<P::TargetChain>;
|
||||
type FinalityVerificationContext = FinalityVerificationContextfOf<P>;
|
||||
type EquivocationProof = EquivocationProofOf<P>;
|
||||
type EquivocationsFinder = EquivocationsFinderOf<P>;
|
||||
}
|
||||
|
||||
/// Different ways of building `report_equivocation` calls.
|
||||
pub trait ReportEquivocationCallBuilder<P: SubstrateEquivocationDetectionPipeline> {
|
||||
/// Build a `report_equivocation` call to be executed on the source chain.
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Default generic implementation of equivocation source for basic Substrate client.
|
||||
|
||||
use crate::{
|
||||
equivocation::{
|
||||
EquivocationDetectionPipelineAdapter, EquivocationProofOf, ReportEquivocationCallBuilder,
|
||||
SubstrateEquivocationDetectionPipeline,
|
||||
},
|
||||
finality_base::{engine::Engine, finality_proofs, SubstrateFinalityProofsStream},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_runtime::{HashOf, TransactionEra};
|
||||
use equivocation_detector::SourceClient;
|
||||
use finality_relay::SourceClientBase;
|
||||
use relay_substrate_client::{
|
||||
AccountKeyPairOf, Client, Error, TransactionTracker, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
|
||||
/// Substrate node as equivocation source.
|
||||
pub struct SubstrateEquivocationSource<P: SubstrateEquivocationDetectionPipeline> {
|
||||
client: Client<P::SourceChain>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
|
||||
}
|
||||
|
||||
impl<P: SubstrateEquivocationDetectionPipeline> SubstrateEquivocationSource<P> {}
|
||||
|
||||
impl<P: SubstrateEquivocationDetectionPipeline> Clone for SubstrateEquivocationSource<P> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { client: self.client.clone(), transaction_params: self.transaction_params.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateEquivocationDetectionPipeline> RelayClient for SubstrateEquivocationSource<P> {
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Error> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateEquivocationDetectionPipeline>
|
||||
SourceClientBase<EquivocationDetectionPipelineAdapter<P>> for SubstrateEquivocationSource<P>
|
||||
{
|
||||
type FinalityProofsStream = SubstrateFinalityProofsStream<P>;
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
|
||||
finality_proofs::<P>(&self.client).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateEquivocationDetectionPipeline>
|
||||
SourceClient<EquivocationDetectionPipelineAdapter<P>> for SubstrateEquivocationSource<P>
|
||||
{
|
||||
type TransactionTracker = TransactionTracker<P::SourceChain, Client<P::SourceChain>>;
|
||||
|
||||
async fn report_equivocation(
|
||||
&self,
|
||||
at: HashOf<P::SourceChain>,
|
||||
equivocation: EquivocationProofOf<P>,
|
||||
) -> Result<Self::TransactionTracker, Self::Error> {
|
||||
let key_owner_proof =
|
||||
P::FinalityEngine::generate_source_key_ownership_proof(&self.client, at, &equivocation)
|
||||
.await?;
|
||||
|
||||
let mortality = self.transaction_params.mortality;
|
||||
let call = P::ReportEquivocationCallBuilder::build_report_equivocation_call(
|
||||
equivocation,
|
||||
key_owner_proof,
|
||||
);
|
||||
self.client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
&self.transaction_params.signer,
|
||||
move |best_block_id, transaction_nonce| {
|
||||
Ok(UnsignedTransaction::new(call.into(), transaction_nonce)
|
||||
.era(TransactionEra::new(best_block_id, mortality)))
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Default generic implementation of equivocation source for basic Substrate client.
|
||||
|
||||
use crate::{
|
||||
equivocation::{EquivocationDetectionPipelineAdapter, SubstrateEquivocationDetectionPipeline},
|
||||
finality_base::engine::Engine,
|
||||
};
|
||||
|
||||
use crate::equivocation::{FinalityProoffOf, FinalityVerificationContextfOf};
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::HeaderFinalityInfo;
|
||||
use bp_runtime::BlockNumberOf;
|
||||
use equivocation_detector::TargetClient;
|
||||
use relay_substrate_client::{Client, Error};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use sp_runtime::traits::Header;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Substrate node as equivocation source.
|
||||
pub struct SubstrateEquivocationTarget<P: SubstrateEquivocationDetectionPipeline> {
|
||||
client: Client<P::TargetChain>,
|
||||
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: SubstrateEquivocationDetectionPipeline> SubstrateEquivocationTarget<P> {}
|
||||
|
||||
impl<P: SubstrateEquivocationDetectionPipeline> Clone for SubstrateEquivocationTarget<P> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { client: self.client.clone(), _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateEquivocationDetectionPipeline> RelayClient for SubstrateEquivocationTarget<P> {
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Error> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateEquivocationDetectionPipeline>
|
||||
TargetClient<EquivocationDetectionPipelineAdapter<P>> for SubstrateEquivocationTarget<P>
|
||||
{
|
||||
async fn finality_verification_context(
|
||||
&self,
|
||||
at: BlockNumberOf<P::TargetChain>,
|
||||
) -> Result<FinalityVerificationContextfOf<P>, Self::Error> {
|
||||
P::FinalityEngine::finality_verification_context(
|
||||
&self.client,
|
||||
self.client.header_by_number(at).await?.hash(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn synced_headers_finality_info(
|
||||
&self,
|
||||
at: BlockNumberOf<P::TargetChain>,
|
||||
) -> Result<
|
||||
Vec<HeaderFinalityInfo<FinalityProoffOf<P>, FinalityVerificationContextfOf<P>>>,
|
||||
Self::Error,
|
||||
> {
|
||||
P::FinalityEngine::synced_headers_finality_info(
|
||||
&self.client,
|
||||
self.client.header_by_number(at).await?.hash(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -18,21 +18,18 @@
|
||||
//! finality proofs synchronization pipelines.
|
||||
|
||||
use crate::{
|
||||
finality::{
|
||||
source::{SubstrateFinalityProof, SubstrateFinalitySource},
|
||||
target::SubstrateFinalityTarget,
|
||||
},
|
||||
finality::{source::SubstrateFinalitySource, target::SubstrateFinalityTarget},
|
||||
finality_base::{engine::Engine, SubstrateFinalityPipeline, SubstrateFinalityProof},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use crate::finality_base::{engine::Engine, SubstrateFinalityPipeline};
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use finality_relay::FinalitySyncPipeline;
|
||||
use finality_relay::{FinalityPipeline, FinalitySyncPipeline};
|
||||
use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig};
|
||||
use relay_substrate_client::{
|
||||
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client,
|
||||
HashOf, HeaderOf, SyncHeader,
|
||||
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain,
|
||||
ChainWithTransactions, Client, HashOf, HeaderOf, SyncHeader,
|
||||
};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use sp_core::Pair;
|
||||
@@ -48,9 +45,31 @@ pub mod target;
|
||||
/// Substrate+GRANDPA based chains (good to know).
|
||||
pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
|
||||
|
||||
/// Convenience trait that adds bounds to `SubstrateFinalitySyncPipeline`.
|
||||
pub trait BaseSubstrateFinalitySyncPipeline:
|
||||
SubstrateFinalityPipeline<TargetChain = Self::BoundedTargetChain>
|
||||
{
|
||||
/// Bounded `SubstrateFinalityPipeline::TargetChain`.
|
||||
type BoundedTargetChain: ChainWithTransactions<AccountId = Self::BoundedTargetChainAccountId>;
|
||||
|
||||
/// Bounded `AccountIdOf<SubstrateFinalityPipeline::TargetChain>`.
|
||||
type BoundedTargetChainAccountId: From<<AccountKeyPairOf<Self::BoundedTargetChain> as Pair>::Public>
|
||||
+ Send;
|
||||
}
|
||||
|
||||
impl<T> BaseSubstrateFinalitySyncPipeline for T
|
||||
where
|
||||
T: SubstrateFinalityPipeline,
|
||||
T::TargetChain: ChainWithTransactions,
|
||||
AccountIdOf<T::TargetChain>: From<<AccountKeyPairOf<Self::TargetChain> as Pair>::Public>,
|
||||
{
|
||||
type BoundedTargetChain = T::TargetChain;
|
||||
type BoundedTargetChainAccountId = AccountIdOf<T::TargetChain>;
|
||||
}
|
||||
|
||||
/// Substrate -> Substrate finality proofs synchronization pipeline.
|
||||
#[async_trait]
|
||||
pub trait SubstrateFinalitySyncPipeline: SubstrateFinalityPipeline {
|
||||
pub trait SubstrateFinalitySyncPipeline: BaseSubstrateFinalitySyncPipeline {
|
||||
/// How submit finality proof call is built?
|
||||
type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder<Self>;
|
||||
|
||||
@@ -70,15 +89,18 @@ pub struct FinalitySyncPipelineAdapter<P: SubstrateFinalitySyncPipeline> {
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: SubstrateFinalitySyncPipeline> FinalitySyncPipeline for FinalitySyncPipelineAdapter<P> {
|
||||
impl<P: SubstrateFinalitySyncPipeline> FinalityPipeline for FinalitySyncPipelineAdapter<P> {
|
||||
const SOURCE_NAME: &'static str = P::SourceChain::NAME;
|
||||
const TARGET_NAME: &'static str = P::TargetChain::NAME;
|
||||
|
||||
type Hash = HashOf<P::SourceChain>;
|
||||
type Number = BlockNumberOf<P::SourceChain>;
|
||||
type FinalityProof = SubstrateFinalityProof<P>;
|
||||
}
|
||||
|
||||
impl<P: SubstrateFinalitySyncPipeline> FinalitySyncPipeline for FinalitySyncPipelineAdapter<P> {
|
||||
type ConsensusLogReader = <P::FinalityEngine as Engine<P::SourceChain>>::ConsensusLogReader;
|
||||
type Header = SyncHeader<HeaderOf<P::SourceChain>>;
|
||||
type FinalityProof = SubstrateFinalityProof<P>;
|
||||
}
|
||||
|
||||
/// Different ways of building `submit_finality_proof` calls.
|
||||
@@ -165,10 +187,7 @@ pub async fn run<P: SubstrateFinalitySyncPipeline>(
|
||||
only_mandatory_headers: bool,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
metrics_params: MetricsParams,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as Pair>::Public>,
|
||||
{
|
||||
) -> anyhow::Result<()> {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting {} -> {} finality proof relay",
|
||||
|
||||
@@ -18,38 +18,27 @@
|
||||
|
||||
use crate::{
|
||||
finality::{FinalitySyncPipelineAdapter, SubstrateFinalitySyncPipeline},
|
||||
finality_base::engine::Engine,
|
||||
finality_base::{
|
||||
engine::Engine, finality_proofs, SubstrateFinalityProof, SubstrateFinalityProofsStream,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::finality_base::SubstrateFinalityPipeline;
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::FinalityProof;
|
||||
use codec::Decode;
|
||||
use finality_relay::SourceClient;
|
||||
use finality_relay::{SourceClient, SourceClientBase};
|
||||
use futures::{
|
||||
select,
|
||||
stream::{try_unfold, unfold, Stream, StreamExt, TryStreamExt},
|
||||
stream::{try_unfold, Stream, StreamExt, TryStreamExt},
|
||||
};
|
||||
use num_traits::One;
|
||||
use relay_substrate_client::{
|
||||
BlockNumberOf, BlockWithJustification, Chain, Client, Error, HeaderOf,
|
||||
};
|
||||
use relay_substrate_client::{BlockNumberOf, BlockWithJustification, Client, Error, HeaderOf};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, UniqueSaturatedInto};
|
||||
use std::pin::Pin;
|
||||
|
||||
/// Shared updatable reference to the maximal header number that we want to sync from the source.
|
||||
pub type RequiredHeaderNumberRef<C> = Arc<Mutex<<C as bp_runtime::Chain>::BlockNumber>>;
|
||||
|
||||
/// Substrate finality proofs stream.
|
||||
pub type SubstrateFinalityProofsStream<P> =
|
||||
Pin<Box<dyn Stream<Item = SubstrateFinalityProof<P>> + Send>>;
|
||||
|
||||
/// Substrate finality proof. Specific to the used `FinalityEngine`.
|
||||
pub type SubstrateFinalityProof<P> = <<P as SubstrateFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as SubstrateFinalityPipeline>::SourceChain,
|
||||
>>::FinalityProof;
|
||||
|
||||
/// Substrate node as finality source.
|
||||
pub struct SubstrateFinalitySource<P: SubstrateFinalitySyncPipeline> {
|
||||
client: Client<P::SourceChain>,
|
||||
@@ -204,11 +193,20 @@ impl<P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalitySource<P
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateFinalitySyncPipeline> SourceClient<FinalitySyncPipelineAdapter<P>>
|
||||
impl<P: SubstrateFinalitySyncPipeline> SourceClientBase<FinalitySyncPipelineAdapter<P>>
|
||||
for SubstrateFinalitySource<P>
|
||||
{
|
||||
type FinalityProofsStream = SubstrateFinalityProofsStream<P>;
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
|
||||
finality_proofs::<P>(&self.client).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateFinalitySyncPipeline> SourceClient<FinalitySyncPipelineAdapter<P>>
|
||||
for SubstrateFinalitySource<P>
|
||||
{
|
||||
async fn best_finalized_block_number(&self) -> Result<BlockNumberOf<P::SourceChain>, Error> {
|
||||
let mut finalized_header_number = self.on_chain_best_finalized_block_number().await?;
|
||||
// never return block number larger than requested. This way we'll never sync headers
|
||||
@@ -234,46 +232,6 @@ impl<P: SubstrateFinalitySyncPipeline> SourceClient<FinalitySyncPipelineAdapter<
|
||||
> {
|
||||
header_and_finality_proof::<P>(&self.client, number).await
|
||||
}
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
|
||||
Ok(unfold(
|
||||
P::FinalityEngine::source_finality_proofs(&self.client).await?,
|
||||
move |subscription| async move {
|
||||
loop {
|
||||
let log_error = |err| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to read justification target from the {} justifications stream: {:?}",
|
||||
P::SourceChain::NAME,
|
||||
err,
|
||||
);
|
||||
};
|
||||
|
||||
let next_justification = subscription
|
||||
.next()
|
||||
.await
|
||||
.map_err(|err| log_error(err.to_string()))
|
||||
.ok()??;
|
||||
|
||||
let decoded_justification =
|
||||
<P::FinalityEngine as Engine<P::SourceChain>>::FinalityProof::decode(
|
||||
&mut &next_justification[..],
|
||||
);
|
||||
|
||||
let justification = match decoded_justification {
|
||||
Ok(j) => j,
|
||||
Err(err) => {
|
||||
log_error(format!("decode failed with error {err:?}"));
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
return Some((justification, subscription))
|
||||
}
|
||||
},
|
||||
)
|
||||
.boxed())
|
||||
}
|
||||
}
|
||||
|
||||
async fn header_and_finality_proof<P: SubstrateFinalitySyncPipeline>(
|
||||
|
||||
@@ -18,21 +18,19 @@
|
||||
|
||||
use crate::{
|
||||
finality::{
|
||||
source::SubstrateFinalityProof, FinalitySyncPipelineAdapter,
|
||||
SubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline,
|
||||
FinalitySyncPipelineAdapter, SubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline,
|
||||
},
|
||||
finality_base::engine::Engine,
|
||||
finality_base::{engine::Engine, SubstrateFinalityProof},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use finality_relay::TargetClient;
|
||||
use relay_substrate_client::{
|
||||
AccountIdOf, AccountKeyPairOf, Client, Error, HeaderIdOf, HeaderOf, SyncHeader, TransactionEra,
|
||||
AccountKeyPairOf, Client, Error, HeaderIdOf, HeaderOf, SyncHeader, TransactionEra,
|
||||
TransactionTracker, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use sp_core::Pair;
|
||||
|
||||
/// Substrate client as Substrate finality target.
|
||||
pub struct SubstrateFinalityTarget<P: SubstrateFinalitySyncPipeline> {
|
||||
@@ -86,8 +84,6 @@ impl<P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalityTarget<P
|
||||
#[async_trait]
|
||||
impl<P: SubstrateFinalitySyncPipeline> TargetClient<FinalitySyncPipelineAdapter<P>>
|
||||
for SubstrateFinalityTarget<P>
|
||||
where
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as Pair>::Public>,
|
||||
{
|
||||
type TransactionTracker = TransactionTracker<P::TargetChain, Client<P::TargetChain>>;
|
||||
|
||||
@@ -116,7 +112,7 @@ where
|
||||
P::FinalityEngine::optimize_proof(&self.client, &header, &mut proof).await?;
|
||||
|
||||
// now we may submit optimized finality proof
|
||||
let transaction_params = self.transaction_params.clone();
|
||||
let mortality = self.transaction_params.mortality;
|
||||
let call =
|
||||
P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(header, proof);
|
||||
self.client
|
||||
@@ -124,7 +120,7 @@ where
|
||||
&self.transaction_params.signer,
|
||||
move |best_block_id, transaction_nonce| {
|
||||
Ok(UnsignedTransaction::new(call.into(), transaction_nonce)
|
||||
.era(TransactionEra::new(best_block_id, transaction_params.mortality)))
|
||||
.era(TransactionEra::new(best_block_id, mortality)))
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -19,13 +19,15 @@
|
||||
use crate::error::Error;
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::{
|
||||
justification::{verify_and_optimize_justification, GrandpaJustification},
|
||||
ChainWithGrandpa as ChainWithGrandpaBase, ConsensusLogReader, FinalityProof,
|
||||
GrandpaConsensusLogReader,
|
||||
justification::{
|
||||
verify_and_optimize_justification, GrandpaEquivocationsFinder, GrandpaJustification,
|
||||
JustificationVerificationContext,
|
||||
},
|
||||
AuthoritySet, ConsensusLogReader, FinalityProof, FindEquivocations, GrandpaConsensusLogReader,
|
||||
HeaderFinalityInfo, HeaderGrandpaInfo, StoredHeaderGrandpaInfo,
|
||||
};
|
||||
use bp_runtime::{BasicOperatingMode, HeaderIdProvider, OperatingMode};
|
||||
use codec::{Decode, Encode};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use num_traits::{One, Zero};
|
||||
use relay_substrate_client::{
|
||||
BlockNumberOf, Chain, ChainWithGrandpa, Client, Error as SubstrateError, HashOf, HeaderOf,
|
||||
@@ -33,7 +35,7 @@ use relay_substrate_client::{
|
||||
};
|
||||
use sp_consensus_grandpa::{AuthorityList as GrandpaAuthoritiesSet, GRANDPA_ENGINE_ID};
|
||||
use sp_core::{storage::StorageKey, Bytes};
|
||||
use sp_runtime::{traits::Header, ConsensusEngineId};
|
||||
use sp_runtime::{scale_info::TypeInfo, traits::Header, ConsensusEngineId};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Finality engine, used by the Substrate chain.
|
||||
@@ -47,10 +49,18 @@ pub trait Engine<C: Chain>: Send {
|
||||
type FinalityClient: SubstrateFinalityClient<C>;
|
||||
/// Type of finality proofs, used by consensus engine.
|
||||
type FinalityProof: FinalityProof<BlockNumberOf<C>> + Decode + Encode;
|
||||
/// The context needed for verifying finality proofs.
|
||||
type FinalityVerificationContext;
|
||||
/// The type of the equivocation proof used by the consensus engine.
|
||||
type EquivocationProof;
|
||||
type EquivocationProof: Send + Sync;
|
||||
/// The equivocations finder.
|
||||
type EquivocationsFinder: FindEquivocations<
|
||||
Self::FinalityProof,
|
||||
Self::FinalityVerificationContext,
|
||||
Self::EquivocationProof,
|
||||
>;
|
||||
/// The type of the key owner proof used by the consensus engine.
|
||||
type KeyOwnerProof;
|
||||
type KeyOwnerProof: Send;
|
||||
/// Type of bridge pallet initialization data.
|
||||
type InitializationData: std::fmt::Debug + Send + Sync + 'static;
|
||||
/// Type of bridge pallet operating mode.
|
||||
@@ -105,6 +115,29 @@ pub trait Engine<C: Chain>: Send {
|
||||
async fn prepare_initialization_data(
|
||||
client: Client<C>,
|
||||
) -> Result<Self::InitializationData, Error<HashOf<C>, BlockNumberOf<C>>>;
|
||||
|
||||
/// Get the context needed for validating a finality proof.
|
||||
async fn finality_verification_context<TargetChain: Chain>(
|
||||
target_client: &Client<TargetChain>,
|
||||
at: HashOf<TargetChain>,
|
||||
) -> Result<Self::FinalityVerificationContext, SubstrateError>;
|
||||
|
||||
/// Returns the finality info associated to the source headers synced with the target
|
||||
/// at the provided block.
|
||||
async fn synced_headers_finality_info<TargetChain: Chain>(
|
||||
target_client: &Client<TargetChain>,
|
||||
at: TargetChain::Hash,
|
||||
) -> Result<
|
||||
Vec<HeaderFinalityInfo<Self::FinalityProof, Self::FinalityVerificationContext>>,
|
||||
SubstrateError,
|
||||
>;
|
||||
|
||||
/// Generate key ownership proof for the provided equivocation.
|
||||
async fn generate_source_key_ownership_proof(
|
||||
source_client: &Client<C>,
|
||||
at: C::Hash,
|
||||
equivocation: &Self::EquivocationProof,
|
||||
) -> Result<Self::KeyOwnerProof, SubstrateError>;
|
||||
}
|
||||
|
||||
/// GRANDPA finality engine.
|
||||
@@ -142,21 +175,19 @@ impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
|
||||
type ConsensusLogReader = GrandpaConsensusLogReader<<C::Header as Header>::Number>;
|
||||
type FinalityClient = SubstrateGrandpaFinalityClient;
|
||||
type FinalityProof = GrandpaJustification<HeaderOf<C>>;
|
||||
type FinalityVerificationContext = JustificationVerificationContext;
|
||||
type EquivocationProof = sp_consensus_grandpa::EquivocationProof<HashOf<C>, BlockNumberOf<C>>;
|
||||
type EquivocationsFinder = GrandpaEquivocationsFinder<C>;
|
||||
type KeyOwnerProof = C::KeyOwnerProof;
|
||||
type InitializationData = bp_header_chain::InitializationData<C::Header>;
|
||||
type OperatingMode = BasicOperatingMode;
|
||||
|
||||
fn is_initialized_key() -> StorageKey {
|
||||
bp_header_chain::storage_keys::best_finalized_key(
|
||||
C::ChainWithGrandpa::WITH_CHAIN_GRANDPA_PALLET_NAME,
|
||||
)
|
||||
bp_header_chain::storage_keys::best_finalized_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME)
|
||||
}
|
||||
|
||||
fn pallet_operating_mode_key() -> StorageKey {
|
||||
bp_header_chain::storage_keys::pallet_operating_mode_key(
|
||||
C::ChainWithGrandpa::WITH_CHAIN_GRANDPA_PALLET_NAME,
|
||||
)
|
||||
bp_header_chain::storage_keys::pallet_operating_mode_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME)
|
||||
}
|
||||
|
||||
async fn optimize_proof<TargetChain: Chain>(
|
||||
@@ -164,31 +195,18 @@ impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
|
||||
header: &C::Header,
|
||||
proof: &mut Self::FinalityProof,
|
||||
) -> Result<(), SubstrateError> {
|
||||
let current_authority_set_key = bp_header_chain::storage_keys::current_authority_set_key(
|
||||
C::ChainWithGrandpa::WITH_CHAIN_GRANDPA_PALLET_NAME,
|
||||
);
|
||||
let (authority_set, authority_set_id): (
|
||||
sp_consensus_grandpa::AuthorityList,
|
||||
sp_consensus_grandpa::SetId,
|
||||
) = target_client
|
||||
.storage_value(current_authority_set_key, None)
|
||||
.await?
|
||||
.map(Ok)
|
||||
.unwrap_or(Err(SubstrateError::Custom(format!(
|
||||
"{} `CurrentAuthoritySet` is missing from the {} storage",
|
||||
C::NAME,
|
||||
TargetChain::NAME,
|
||||
))))?;
|
||||
let authority_set =
|
||||
finality_grandpa::voter_set::VoterSet::new(authority_set).expect("TODO");
|
||||
let verification_context = Grandpa::<C>::finality_verification_context(
|
||||
target_client,
|
||||
target_client.best_header().await?.hash(),
|
||||
)
|
||||
.await?;
|
||||
// we're risking with race here - we have decided to submit justification some time ago and
|
||||
// actual authorities set (which we have read now) may have changed, so this
|
||||
// `optimize_justification` may fail. But if target chain is configured properly, it'll fail
|
||||
// anyway, after we submit transaction and failing earlier is better. So - it is fine
|
||||
verify_and_optimize_justification(
|
||||
(header.hash(), *header.number()),
|
||||
authority_set_id,
|
||||
&authority_set,
|
||||
&verification_context,
|
||||
proof,
|
||||
)
|
||||
.map_err(|e| {
|
||||
@@ -275,8 +293,6 @@ impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
|
||||
// Now let's try to guess authorities set id by verifying justification.
|
||||
let mut initial_authorities_set_id = 0;
|
||||
let mut min_possible_block_number = C::BlockNumber::zero();
|
||||
let authorities_for_verification = VoterSet::new(authorities_for_verification.clone())
|
||||
.ok_or(Error::ReadInvalidAuthorities(C::NAME, authorities_for_verification))?;
|
||||
loop {
|
||||
log::trace!(
|
||||
target: "bridge", "Trying {} GRANDPA authorities set id: {}",
|
||||
@@ -286,8 +302,14 @@ impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
|
||||
|
||||
let is_valid_set_id = verify_and_optimize_justification(
|
||||
(initial_header_hash, initial_header_number),
|
||||
initial_authorities_set_id,
|
||||
&authorities_for_verification,
|
||||
&AuthoritySet {
|
||||
authorities: authorities_for_verification.clone(),
|
||||
set_id: initial_authorities_set_id,
|
||||
}
|
||||
.try_into()
|
||||
.map_err(|_| {
|
||||
Error::ReadInvalidAuthorities(C::NAME, authorities_for_verification.clone())
|
||||
})?,
|
||||
&mut justification.clone(),
|
||||
)
|
||||
.is_ok();
|
||||
@@ -317,4 +339,82 @@ impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
})
|
||||
}
|
||||
|
||||
async fn finality_verification_context<TargetChain: Chain>(
|
||||
target_client: &Client<TargetChain>,
|
||||
at: HashOf<TargetChain>,
|
||||
) -> Result<Self::FinalityVerificationContext, SubstrateError> {
|
||||
let current_authority_set_key = bp_header_chain::storage_keys::current_authority_set_key(
|
||||
C::WITH_CHAIN_GRANDPA_PALLET_NAME,
|
||||
);
|
||||
let authority_set: AuthoritySet = target_client
|
||||
.storage_value(current_authority_set_key, Some(at))
|
||||
.await?
|
||||
.map(Ok)
|
||||
.unwrap_or(Err(SubstrateError::Custom(format!(
|
||||
"{} `CurrentAuthoritySet` is missing from the {} storage",
|
||||
C::NAME,
|
||||
TargetChain::NAME,
|
||||
))))?;
|
||||
|
||||
authority_set.try_into().map_err(|e| {
|
||||
SubstrateError::Custom(format!(
|
||||
"{} `CurrentAuthoritySet` from the {} storage is invalid: {e:?}",
|
||||
C::NAME,
|
||||
TargetChain::NAME,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
async fn synced_headers_finality_info<TargetChain: Chain>(
|
||||
target_client: &Client<TargetChain>,
|
||||
at: TargetChain::Hash,
|
||||
) -> Result<Vec<HeaderGrandpaInfo<HeaderOf<C>>>, SubstrateError> {
|
||||
let stored_headers_grandpa_info: Vec<StoredHeaderGrandpaInfo<HeaderOf<C>>> = target_client
|
||||
.typed_state_call(C::SYNCED_HEADERS_GRANDPA_INFO_METHOD.to_string(), (), Some(at))
|
||||
.await?;
|
||||
|
||||
let mut headers_grandpa_info = vec![];
|
||||
for stored_header_grandpa_info in stored_headers_grandpa_info {
|
||||
headers_grandpa_info.push(stored_header_grandpa_info.try_into().map_err(|e| {
|
||||
SubstrateError::Custom(format!(
|
||||
"{} `AuthoritySet` synced to {} is invalid: {e:?} ",
|
||||
C::NAME,
|
||||
TargetChain::NAME,
|
||||
))
|
||||
})?);
|
||||
}
|
||||
|
||||
Ok(headers_grandpa_info)
|
||||
}
|
||||
|
||||
async fn generate_source_key_ownership_proof(
|
||||
source_client: &Client<C>,
|
||||
at: C::Hash,
|
||||
equivocation: &Self::EquivocationProof,
|
||||
) -> Result<Self::KeyOwnerProof, SubstrateError> {
|
||||
let set_id = equivocation.set_id();
|
||||
let offender = equivocation.offender();
|
||||
|
||||
let opaque_key_owner_proof = source_client
|
||||
.generate_grandpa_key_ownership_proof(at, set_id, offender.clone())
|
||||
.await?
|
||||
.ok_or(SubstrateError::Custom(format!(
|
||||
"Couldn't get GRANDPA key ownership proof from {} at block: {at} \
|
||||
for offender: {:?}, set_id: {set_id} ",
|
||||
C::NAME,
|
||||
offender.clone(),
|
||||
)))?;
|
||||
|
||||
let key_owner_proof =
|
||||
opaque_key_owner_proof.decode().ok_or(SubstrateError::Custom(format!(
|
||||
"Couldn't decode GRANDPA `OpaqueKeyOwnnershipProof` from {} at block: {at}
|
||||
to `{:?}` for offender: {:?}, set_id: {set_id}, at block: {at}",
|
||||
C::NAME,
|
||||
<C::KeyOwnerProof as TypeInfo>::type_info().path,
|
||||
offender.clone(),
|
||||
)))?;
|
||||
|
||||
Ok(key_owner_proof)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
|
||||
pub mod engine;
|
||||
|
||||
use crate::finality_base::engine::Engine;
|
||||
use async_trait::async_trait;
|
||||
use relay_substrate_client::{Chain, ChainWithTransactions};
|
||||
use std::fmt::Debug;
|
||||
use codec::Decode;
|
||||
use futures::{stream::unfold, Stream, StreamExt};
|
||||
use relay_substrate_client::{Chain, Client, Error};
|
||||
use std::{fmt::Debug, pin::Pin};
|
||||
|
||||
/// Substrate -> Substrate finality related pipeline.
|
||||
#[async_trait]
|
||||
@@ -29,7 +32,56 @@ pub trait SubstrateFinalityPipeline: 'static + Clone + Debug + Send + Sync {
|
||||
/// Headers of this chain are submitted to the `TargetChain`.
|
||||
type SourceChain: Chain;
|
||||
/// Headers of the `SourceChain` are submitted to this chain.
|
||||
type TargetChain: ChainWithTransactions;
|
||||
type TargetChain: Chain;
|
||||
/// Finality engine.
|
||||
type FinalityEngine: engine::Engine<Self::SourceChain>;
|
||||
type FinalityEngine: Engine<Self::SourceChain>;
|
||||
}
|
||||
|
||||
/// Substrate finality proof. Specific to the used `FinalityEngine`.
|
||||
pub type SubstrateFinalityProof<P> = <<P as SubstrateFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as SubstrateFinalityPipeline>::SourceChain,
|
||||
>>::FinalityProof;
|
||||
|
||||
/// Substrate finality proofs stream.
|
||||
pub type SubstrateFinalityProofsStream<P> =
|
||||
Pin<Box<dyn Stream<Item = SubstrateFinalityProof<P>> + Send>>;
|
||||
|
||||
/// Subscribe to new finality proofs.
|
||||
pub async fn finality_proofs<P: SubstrateFinalityPipeline>(
|
||||
client: &Client<P::SourceChain>,
|
||||
) -> Result<SubstrateFinalityProofsStream<P>, Error> {
|
||||
Ok(unfold(
|
||||
P::FinalityEngine::source_finality_proofs(client).await?,
|
||||
move |subscription| async move {
|
||||
loop {
|
||||
let log_error = |err| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to read justification target from the {} justifications stream: {:?}",
|
||||
P::SourceChain::NAME,
|
||||
err,
|
||||
);
|
||||
};
|
||||
|
||||
let next_justification =
|
||||
subscription.next().await.map_err(|err| log_error(err.to_string())).ok()??;
|
||||
|
||||
let decoded_justification =
|
||||
<P::FinalityEngine as Engine<P::SourceChain>>::FinalityProof::decode(
|
||||
&mut &next_justification[..],
|
||||
);
|
||||
|
||||
let justification = match decoded_justification {
|
||||
Ok(j) => j,
|
||||
Err(err) => {
|
||||
log_error(format!("decode failed with error {err:?}"));
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
return Some((justification, subscription))
|
||||
}
|
||||
},
|
||||
)
|
||||
.boxed())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user