use crate::error::Error;
use async_trait::async_trait;
use bp_header_chain::{
justification::{
verify_and_optimize_justification, GrandpaEquivocationsFinder, GrandpaJustification,
JustificationVerificationContext,
},
AuthoritySet, ConsensusLogReader, FinalityProof, FindEquivocations, GrandpaConsensusLogReader,
HeaderFinalityInfo, HeaderGrandpaInfo, StoredHeaderGrandpaInfo, SubmitFinalityProofCallExtras,
};
use bp_runtime::{BasicOperatingMode, HeaderIdProvider, OperatingMode};
use codec::{Decode, Encode};
use futures::stream::StreamExt;
use num_traits::{One, Zero};
use relay_substrate_client::{
BlockNumberOf, Chain, ChainWithGrandpa, Client, Error as SubstrateError, HashOf, HeaderOf,
Subscription,
};
use sp_consensus_grandpa::{AuthorityList as GrandpaAuthoritiesSet, GRANDPA_ENGINE_ID};
use sp_core::{storage::StorageKey, Bytes};
use sp_runtime::{scale_info::TypeInfo, traits::Header, ConsensusEngineId};
use std::{fmt::Debug, marker::PhantomData};
#[async_trait]
pub trait Engine<C: Chain>: Send {
const ID: ConsensusEngineId;
type ConsensusLogReader: ConsensusLogReader;
type FinalityProof: FinalityProof<HashOf<C>, BlockNumberOf<C>> + Decode + Encode;
type FinalityVerificationContext: Debug + Send;
type EquivocationProof: Clone + Debug + Send + Sync;
type EquivocationsFinder: FindEquivocations<
Self::FinalityProof,
Self::FinalityVerificationContext,
Self::EquivocationProof,
>;
type KeyOwnerProof: Send;
type InitializationData: Debug + Send + Sync + 'static;
type OperatingMode: OperatingMode + 'static;
fn is_initialized_key() -> StorageKey;
async fn is_initialized<TargetChain: Chain>(
target_client: &impl Client<TargetChain>,
) -> Result<bool, SubstrateError> {
Ok(target_client
.raw_storage_value(target_client.best_header_hash().await?, Self::is_initialized_key())
.await?
.is_some())
}
fn pallet_operating_mode_key() -> StorageKey;
async fn is_halted<TargetChain: Chain>(
target_client: &impl Client<TargetChain>,
) -> Result<bool, SubstrateError> {
Ok(target_client
.storage_value::<Self::OperatingMode>(
target_client.best_header_hash().await?,
Self::pallet_operating_mode_key(),
)
.await?
.map(|operating_mode| operating_mode.is_halted())
.unwrap_or(false))
}
async fn source_finality_proofs(
source_client: &impl Client<C>,
) -> Result<Subscription<Bytes>, SubstrateError>;
async fn verify_and_optimize_proof<TargetChain: Chain>(
target_client: &impl Client<TargetChain>,
header: &C::Header,
proof: &mut Self::FinalityProof,
) -> Result<Self::FinalityVerificationContext, SubstrateError>;
fn check_max_expected_call_limits(
header: &C::Header,
proof: &Self::FinalityProof,
) -> SubmitFinalityProofCallExtras;
async fn prepare_initialization_data(
client: impl Client<C>,
) -> Result<Self::InitializationData, Error<HashOf<C>, BlockNumberOf<C>>>;
async fn finality_verification_context<TargetChain: Chain>(
target_client: &impl Client<TargetChain>,
at: HashOf<TargetChain>,
) -> Result<Self::FinalityVerificationContext, SubstrateError>;
async fn synced_headers_finality_info<TargetChain: Chain>(
target_client: &impl Client<TargetChain>,
at: TargetChain::Hash,
) -> Result<
Vec<HeaderFinalityInfo<Self::FinalityProof, Self::FinalityVerificationContext>>,
SubstrateError,
>;
async fn generate_source_key_ownership_proof(
source_client: &impl Client<C>,
at: C::Hash,
equivocation: &Self::EquivocationProof,
) -> Result<Self::KeyOwnerProof, SubstrateError>;
}
pub struct Grandpa<C>(PhantomData<C>);
impl<C: ChainWithGrandpa> Grandpa<C> {
async fn source_header(
source_client: &impl Client<C>,
header_hash: C::Hash,
) -> Result<C::Header, Error<HashOf<C>, BlockNumberOf<C>>> {
source_client
.header_by_hash(header_hash)
.await
.map_err(|err| Error::RetrieveHeader(C::NAME, header_hash, err))
}
async fn source_authorities_set(
source_client: &impl Client<C>,
header_hash: C::Hash,
) -> Result<GrandpaAuthoritiesSet, Error<HashOf<C>, BlockNumberOf<C>>> {
const SUB_API_GRANDPA_AUTHORITIES: &str = "GrandpaApi_grandpa_authorities";
source_client
.state_call(header_hash, SUB_API_GRANDPA_AUTHORITIES.to_string(), ())
.await
.map_err(|err| Error::RetrieveAuthorities(C::NAME, header_hash, err))
}
}
#[async_trait]
impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
const ID: ConsensusEngineId = GRANDPA_ENGINE_ID;
type ConsensusLogReader = GrandpaConsensusLogReader<<C::Header as Header>::Number>;
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::WITH_CHAIN_GRANDPA_PALLET_NAME)
}
fn pallet_operating_mode_key() -> StorageKey {
bp_header_chain::storage_keys::pallet_operating_mode_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME)
}
async fn source_finality_proofs(
client: &impl Client<C>,
) -> Result<Subscription<Bytes>, SubstrateError> {
client.subscribe_grandpa_finality_justifications().await
}
async fn verify_and_optimize_proof<TargetChain: Chain>(
target_client: &impl Client<TargetChain>,
header: &C::Header,
proof: &mut Self::FinalityProof,
) -> Result<Self::FinalityVerificationContext, SubstrateError> {
let verification_context = Grandpa::<C>::finality_verification_context(
target_client,
target_client.best_header().await?.hash(),
)
.await?;
verify_and_optimize_justification(
(header.hash(), *header.number()),
&verification_context,
proof,
)
.map(|_| verification_context)
.map_err(|e| {
SubstrateError::Custom(format!(
"Failed to optimize {} GRANDPA jutification for header {:?}: {:?}",
C::NAME,
header.id(),
e,
))
})
}
fn check_max_expected_call_limits(
header: &C::Header,
proof: &Self::FinalityProof,
) -> SubmitFinalityProofCallExtras {
bp_header_chain::submit_finality_proof_limits_extras::<C>(header, proof)
}
async fn prepare_initialization_data(
source_client: impl Client<C>,
) -> Result<Self::InitializationData, Error<HashOf<C>, BlockNumberOf<C>>> {
let mut justifications = Self::source_finality_proofs(&source_client)
.await
.map_err(|err| Error::Subscribe(C::NAME, err))?;
let justification = justifications
.next()
.await
.ok_or(Error::ReadJustificationStreamEnded(C::NAME))?;
let justification: GrandpaJustification<C::Header> =
Decode::decode(&mut &justification.0[..])
.map_err(|err| Error::DecodeJustification(C::NAME, err))?;
let (initial_header_hash, initial_header_number) =
(justification.commit.target_hash, justification.commit.target_number);
let initial_header = Self::source_header(&source_client, initial_header_hash).await?;
log::trace!(target: "bridge", "Selected {} initial header: {}/{}",
C::NAME,
initial_header_number,
initial_header_hash,
);
let initial_authorities_set =
Self::source_authorities_set(&source_client, initial_header_hash).await?;
log::trace!(target: "bridge", "Selected {} initial authorities set: {:?}",
C::NAME,
initial_authorities_set,
);
let mut authorities_for_verification = initial_authorities_set.clone();
let scheduled_change = GrandpaConsensusLogReader::<BlockNumberOf<C>>::find_scheduled_change(
initial_header.digest(),
);
assert!(
scheduled_change.as_ref().map(|c| c.delay.is_zero()).unwrap_or(true),
"GRANDPA authorities change at {} scheduled to happen in {:?} blocks. We expect\
regular change to have zero delay",
initial_header_hash,
scheduled_change.as_ref().map(|c| c.delay),
);
let schedules_change = scheduled_change.is_some();
if schedules_change {
authorities_for_verification =
Self::source_authorities_set(&source_client, *initial_header.parent_hash()).await?;
log::trace!(
target: "bridge",
"Selected {} header is scheduling GRANDPA authorities set changes. Using previous set: {:?}",
C::NAME,
authorities_for_verification,
);
}
let mut initial_authorities_set_id = 0;
let mut min_possible_block_number = C::BlockNumber::zero();
loop {
log::trace!(
target: "bridge", "Trying {} GRANDPA authorities set id: {}",
C::NAME,
initial_authorities_set_id,
);
let is_valid_set_id = verify_and_optimize_justification(
(initial_header_hash, initial_header_number),
&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();
if is_valid_set_id {
break
}
initial_authorities_set_id += 1;
min_possible_block_number += One::one();
if min_possible_block_number > initial_header_number {
return Err(Error::GuessInitialAuthorities(C::NAME, initial_header_number))
}
}
Ok(bp_header_chain::InitializationData {
header: Box::new(initial_header),
authority_list: initial_authorities_set,
set_id: if schedules_change {
initial_authorities_set_id + 1
} else {
initial_authorities_set_id
},
operating_mode: BasicOperatingMode::Normal,
})
}
async fn finality_verification_context<TargetChain: Chain>(
target_client: &impl 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(at, current_authority_set_key)
.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: &impl Client<TargetChain>,
at: TargetChain::Hash,
) -> Result<Vec<HeaderGrandpaInfo<HeaderOf<C>>>, SubstrateError> {
let stored_headers_grandpa_info: Vec<StoredHeaderGrandpaInfo<HeaderOf<C>>> = target_client
.state_call(at, C::SYNCED_HEADERS_GRANDPA_INFO_METHOD.to_string(), ())
.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: &impl 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)
}
}