Add equivocation detector crate and implement clients (#2348) (#2353)

* 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:
Serban Iorga
2023-08-16 08:20:09 +03:00
committed by Bastian Köcher
parent 9bfad80664
commit 48cae06a77
35 changed files with 931 additions and 375 deletions
@@ -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())
}