Batch transactions in complex relays (#1669)

* batch transactions in message relay: API prototype

* get rid of Box<dyn BatchTransaction> and actually submit it

* test batch transactions

* message_lane_loop_works_with_batch_transactions

* removed logger

* BatchConfirmationTransaction + BatchDeliveryTransaction

* more prototyping

* fmt

* continue with batch calls

* impl BatchCallBuilder for ()

* BatchDeliveryTransaction impl

* BundledBatchCallBuilder

* proper impl of BundledBatchCallBuilder + use it in RialtoParachain -> Millau

* impl prove_header in OnDemandHeadersRelay

* impl OnDemandParachainsRelay::prove_header (needs extensive tests)

* added a couple of TODOs

* return Result<Option<BatchTx>> when asking for more headers

* prove headers when reauire_* is called && return proper headers from required_header_id

* split parachains::prove_header and test select_headers_to_prove

* more traces and leave TODOs

* use finality stream in SubstrateFinalitySource::prove_block_finality

* prove parachain head at block, selected by headers relay

* const ANCIENT_BLOCK_THRESHOLD

* TODO -> proof

* clippy and spelling

* BatchCallBuilder::build_batch_call() returns Result

* read first proof from two streams

* FailedToFindFinalityProof -> FinalityProofNotFound

* changed select_headers_to_prove to version from PR review
This commit is contained in:
Svyatoslav Nikolsky
2022-12-16 15:04:36 +03:00
committed by Bastian Köcher
parent a732a04ed4
commit be27bd5e97
28 changed files with 1190 additions and 152 deletions
@@ -20,13 +20,18 @@ use crate::finality::{engine::Engine, FinalitySyncPipelineAdapter, SubstrateFina
use async_std::sync::{Arc, Mutex};
use async_trait::async_trait;
use bp_header_chain::FinalityProof;
use codec::Decode;
use finality_relay::SourceClient;
use futures::stream::{unfold, Stream, StreamExt};
use futures::{
select,
stream::{try_unfold, unfold, Stream, StreamExt, TryStreamExt},
};
use num_traits::One;
use relay_substrate_client::{
BlockNumberOf, BlockWithJustification, Chain, Client, Error, HeaderOf,
};
use relay_utils::relay_loop::Client as RelayClient;
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.
@@ -70,6 +75,111 @@ impl<P: SubstrateFinalitySyncPipeline> SubstrateFinalitySource<P> {
// target node may be missing proofs that are already available at the source
self.client.best_finalized_header_number().await
}
/// Return header and its justification of the given block or its descendant that
/// has a GRANDPA justification.
///
/// This method is optimized for cases when `block_number` is close to the best finalized
/// chain block.
pub async fn prove_block_finality(
&self,
block_number: BlockNumberOf<P::SourceChain>,
) -> Result<
(relay_substrate_client::SyncHeader<HeaderOf<P::SourceChain>>, SubstrateFinalityProof<P>),
Error,
> {
// first, subscribe to proofs
let next_persistent_proof =
self.persistent_proofs_stream(block_number + One::one()).await?.fuse();
let next_ephemeral_proof = self.ephemeral_proofs_stream(block_number).await?.fuse();
// in perfect world we'll need to return justfication for the requested `block_number`
let (header, maybe_proof) = self.header_and_finality_proof(block_number).await?;
if let Some(proof) = maybe_proof {
return Ok((header, proof))
}
// otherwise we don't care which header to return, so let's select first
futures::pin_mut!(next_persistent_proof, next_ephemeral_proof);
loop {
select! {
maybe_header_and_proof = next_persistent_proof.next() => match maybe_header_and_proof {
Some(header_and_proof) => return header_and_proof,
None => continue,
},
maybe_header_and_proof = next_ephemeral_proof.next() => match maybe_header_and_proof {
Some(header_and_proof) => return header_and_proof,
None => continue,
},
complete => return Err(Error::FinalityProofNotFound(block_number.unique_saturated_into()))
}
}
}
/// Returns stream of headers and their persistent proofs, starting from given block.
async fn persistent_proofs_stream(
&self,
block_number: BlockNumberOf<P::SourceChain>,
) -> Result<
impl Stream<
Item = Result<
(
relay_substrate_client::SyncHeader<HeaderOf<P::SourceChain>>,
SubstrateFinalityProof<P>,
),
Error,
>,
>,
Error,
> {
let client = self.client.clone();
let best_finalized_block_number = self.client.best_finalized_header_number().await?;
Ok(try_unfold((client, block_number), move |(client, current_block_number)| async move {
// if we've passed the `best_finalized_block_number`, we no longer need persistent
// justifications
if current_block_number > best_finalized_block_number {
return Ok(None)
}
let (header, maybe_proof) =
header_and_finality_proof::<P>(&client, current_block_number).await?;
let next_block_number = current_block_number + One::one();
let next_state = (client, next_block_number);
Ok(Some((maybe_proof.map(|proof| (header, proof)), next_state)))
})
.try_filter_map(|maybe_result| async { Ok(maybe_result) }))
}
/// Returns stream of headers and their ephemeral proofs, starting from given block.
async fn ephemeral_proofs_stream(
&self,
block_number: BlockNumberOf<P::SourceChain>,
) -> Result<
impl Stream<
Item = Result<
(
relay_substrate_client::SyncHeader<HeaderOf<P::SourceChain>>,
SubstrateFinalityProof<P>,
),
Error,
>,
>,
Error,
> {
let client = self.client.clone();
Ok(self.finality_proofs().await?.map(Ok).try_filter_map(move |proof| {
let client = client.clone();
async move {
if proof.target_header_number() < block_number {
return Ok(None)
}
let header = client.header_by_number(proof.target_header_number()).await?;
Ok(Some((header.into(), proof)))
}
}))
}
}
impl<P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalitySource<P> {
@@ -119,18 +229,7 @@ impl<P: SubstrateFinalitySyncPipeline> SourceClient<FinalitySyncPipelineAdapter<
),
Error,
> {
let header_hash = self.client.block_hash_by_number(number).await?;
let signed_block = self.client.get_block(Some(header_hash)).await?;
let justification = signed_block
.justification(P::FinalityEngine::ID)
.map(|raw_justification| {
SubstrateFinalityProof::<P>::decode(&mut raw_justification.as_slice())
})
.transpose()
.map_err(Error::ResponseParseFailed)?;
Ok((signed_block.header().into(), justification))
header_and_finality_proof::<P>(&self.client, number).await
}
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
@@ -173,3 +272,27 @@ impl<P: SubstrateFinalitySyncPipeline> SourceClient<FinalitySyncPipelineAdapter<
.boxed())
}
}
async fn header_and_finality_proof<P: SubstrateFinalitySyncPipeline>(
client: &Client<P::SourceChain>,
number: BlockNumberOf<P::SourceChain>,
) -> Result<
(
relay_substrate_client::SyncHeader<HeaderOf<P::SourceChain>>,
Option<SubstrateFinalityProof<P>>,
),
Error,
> {
let header_hash = client.block_hash_by_number(number).await?;
let signed_block = client.get_block(Some(header_hash)).await?;
let justification = signed_block
.justification(P::FinalityEngine::ID)
.map(|raw_justification| {
SubstrateFinalityProof::<P>::decode(&mut raw_justification.as_slice())
})
.transpose()
.map_err(Error::ResponseParseFailed)?;
Ok((signed_block.header().into(), justification))
}
@@ -18,6 +18,9 @@
#![warn(missing_docs)]
use relay_substrate_client::Error as SubstrateError;
use std::marker::PhantomData;
pub mod error;
pub mod finality;
pub mod messages_lane;
@@ -96,3 +99,52 @@ impl<AccountId> TaggedAccount<AccountId> {
}
}
}
/// Batch call builder.
pub trait BatchCallBuilder<Call> {
/// Associated error type.
type Error;
/// If `true`, then batch calls are supported at the chain.
const BATCH_CALL_SUPPORTED: bool;
/// Create batch call from given calls vector.
fn build_batch_call(_calls: Vec<Call>) -> Result<Call, Self::Error>;
}
impl<Call> BatchCallBuilder<Call> for () {
type Error = SubstrateError;
const BATCH_CALL_SUPPORTED: bool = false;
fn build_batch_call(_calls: Vec<Call>) -> Result<Call, SubstrateError> {
debug_assert!(
false,
"only called if `BATCH_CALL_SUPPORTED` is true;\
`BATCH_CALL_SUPPORTED` is false;\
qed"
);
Err(SubstrateError::Custom("<() as BatchCallBuilder>::build_batch_call() is called".into()))
}
}
/// Batch call builder for bundled runtimes.
pub struct BundledBatchCallBuilder<R>(PhantomData<R>);
impl<R> BatchCallBuilder<<R as frame_system::Config>::RuntimeCall> for BundledBatchCallBuilder<R>
where
R: pallet_utility::Config<RuntimeCall = <R as frame_system::Config>::RuntimeCall>,
<R as frame_system::Config>::RuntimeCall: From<pallet_utility::Call<R>>,
{
type Error = SubstrateError;
const BATCH_CALL_SUPPORTED: bool = true;
fn build_batch_call(
mut calls: Vec<<R as frame_system::Config>::RuntimeCall>,
) -> Result<<R as frame_system::Config>::RuntimeCall, SubstrateError> {
Ok(if calls.len() == 1 {
calls.remove(0)
} else {
pallet_utility::Call::batch_all { calls }.into()
})
}
}
@@ -20,7 +20,7 @@ use crate::{
messages_source::{SubstrateMessagesProof, SubstrateMessagesSource},
messages_target::{SubstrateMessagesDeliveryProof, SubstrateMessagesTarget},
on_demand::OnDemandRelay,
TransactionParams,
BatchCallBuilder, TransactionParams,
};
use async_std::sync::Arc;
@@ -35,7 +35,7 @@ use messages_relay::message_lane::MessageLane;
use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig};
use relay_substrate_client::{
transaction_stall_timeout, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain,
ChainWithMessages, ChainWithTransactions, Client, HashOf,
ChainWithMessages, ChainWithTransactions, Client, Error as SubstrateError, HashOf,
};
use relay_utils::{
metrics::{GlobalMetrics, MetricsParams, StandaloneMetric},
@@ -55,11 +55,16 @@ pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync {
type ReceiveMessagesProofCallBuilder: ReceiveMessagesProofCallBuilder<Self>;
/// How receive messages delivery proof call is built?
type ReceiveMessagesDeliveryProofCallBuilder: ReceiveMessagesDeliveryProofCallBuilder<Self>;
/// How batch calls are built at the source chain?
type SourceBatchCallBuilder: BatchCallBuilder<CallOf<Self::SourceChain>, Error = SubstrateError>;
/// How batch calls are built at the target chain?
type TargetBatchCallBuilder: BatchCallBuilder<CallOf<Self::TargetChain>, Error = SubstrateError>;
}
/// Adapter that allows all `SubstrateMessageLane` to act as `MessageLane`.
#[derive(Clone, Debug)]
pub(crate) struct MessageLaneAdapter<P: SubstrateMessageLane> {
pub struct MessageLaneAdapter<P: SubstrateMessageLane> {
_phantom: PhantomData<P>,
}
@@ -90,10 +95,10 @@ pub struct MessagesRelayParams<P: SubstrateMessageLane> {
pub target_transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
/// Optional on-demand source to target headers relay.
pub source_to_target_headers_relay:
Option<Arc<dyn OnDemandRelay<BlockNumberOf<P::SourceChain>>>>,
Option<Arc<dyn OnDemandRelay<P::SourceChain, P::TargetChain>>>,
/// Optional on-demand target to source headers relay.
pub target_to_source_headers_relay:
Option<Arc<dyn OnDemandRelay<BlockNumberOf<P::TargetChain>>>>,
Option<Arc<dyn OnDemandRelay<P::TargetChain, P::SourceChain>>>,
/// Identifier of lane that needs to be served.
pub lane_id: LaneId,
/// Metrics parameters.
@@ -24,7 +24,7 @@ use crate::{
},
messages_target::SubstrateMessagesDeliveryProof,
on_demand::OnDemandRelay,
TransactionParams,
BatchCallBuilder, TransactionParams,
};
use async_std::sync::Arc;
@@ -41,14 +41,14 @@ use frame_support::weights::Weight;
use messages_relay::{
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
message_lane_loop::{
ClientState, MessageDetails, MessageDetailsMap, MessageProofParameters, SourceClient,
SourceClientState,
BatchTransaction, ClientState, MessageDetails, MessageDetailsMap, MessageProofParameters,
SourceClient, SourceClientState,
},
};
use num_traits::Zero;
use relay_substrate_client::{
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages, Client,
Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain, ChainWithMessages,
Client, Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
TransactionTracker, UnsignedTransaction,
};
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
@@ -68,7 +68,7 @@ pub struct SubstrateMessagesSource<P: SubstrateMessageLane> {
target_client: Client<P::TargetChain>,
lane_id: LaneId,
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
target_to_source_headers_relay: Option<Arc<dyn OnDemandRelay<BlockNumberOf<P::TargetChain>>>>,
target_to_source_headers_relay: Option<Arc<dyn OnDemandRelay<P::TargetChain, P::SourceChain>>>,
}
impl<P: SubstrateMessageLane> SubstrateMessagesSource<P> {
@@ -79,7 +79,7 @@ impl<P: SubstrateMessageLane> SubstrateMessagesSource<P> {
lane_id: LaneId,
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
target_to_source_headers_relay: Option<
Arc<dyn OnDemandRelay<BlockNumberOf<P::TargetChain>>>,
Arc<dyn OnDemandRelay<P::TargetChain, P::SourceChain>>,
>,
) -> Self {
SubstrateMessagesSource {
@@ -140,6 +140,7 @@ impl<P: SubstrateMessageLane> SourceClient<MessageLaneAdapter<P>> for SubstrateM
where
AccountIdOf<P::SourceChain>: From<<AccountKeyPairOf<P::SourceChain> as Pair>::Public>,
{
type BatchTransaction = BatchConfirmationTransaction<P>;
type TransactionTracker = TransactionTracker<P::SourceChain, Client<P::SourceChain>>;
async fn state(&self) -> Result<SourceClientState<MessageLaneAdapter<P>>, SubstrateError> {
@@ -360,10 +361,93 @@ where
.await
}
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<MessageLaneAdapter<P>>) {
async fn require_target_header_on_source(
&self,
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<Option<Self::BatchTransaction>, SubstrateError> {
if let Some(ref target_to_source_headers_relay) = self.target_to_source_headers_relay {
if P::SourceBatchCallBuilder::BATCH_CALL_SUPPORTED {
return BatchConfirmationTransaction::<P>::new(self.clone(), id).await.map(Some)
}
target_to_source_headers_relay.require_more_headers(id.0).await;
}
Ok(None)
}
}
/// Batch transaction that brings target headers + and delivery confirmations to the source node.
pub struct BatchConfirmationTransaction<P: SubstrateMessageLane> {
messages_source: SubstrateMessagesSource<P>,
proved_header: TargetHeaderIdOf<MessageLaneAdapter<P>>,
prove_calls: Vec<CallOf<P::SourceChain>>,
}
impl<P: SubstrateMessageLane> BatchConfirmationTransaction<P> {
async fn new(
messages_source: SubstrateMessagesSource<P>,
required_target_header_on_source: TargetHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<Self, SubstrateError> {
let (proved_header, prove_calls) = messages_source
.target_to_source_headers_relay
.as_ref()
.expect("BatchConfirmationTransaction is only created when target_to_source_headers_relay is Some; qed")
.prove_header(required_target_header_on_source.0)
.await?;
Ok(Self { messages_source, proved_header, prove_calls })
}
}
#[async_trait]
impl<P: SubstrateMessageLane>
BatchTransaction<
TargetHeaderIdOf<MessageLaneAdapter<P>>,
<MessageLaneAdapter<P> as MessageLane>::MessagesReceivingProof,
TransactionTracker<P::SourceChain, Client<P::SourceChain>>,
SubstrateError,
> for BatchConfirmationTransaction<P>
where
AccountIdOf<P::SourceChain>: From<<AccountKeyPairOf<P::SourceChain> as Pair>::Public>,
{
fn required_header_id(&self) -> TargetHeaderIdOf<MessageLaneAdapter<P>> {
self.proved_header
}
async fn append_proof_and_send(
self,
proof: <MessageLaneAdapter<P> as MessageLane>::MessagesReceivingProof,
) -> Result<TransactionTracker<P::SourceChain, Client<P::SourceChain>>, SubstrateError> {
let mut calls = self.prove_calls;
calls.push(
P::ReceiveMessagesDeliveryProofCallBuilder::build_receive_messages_delivery_proof_call(
proof, false,
),
);
let batch_call = P::SourceBatchCallBuilder::build_batch_call(calls)?;
let (spec_version, transaction_version) =
self.messages_source.source_client.simple_runtime_version().await?;
self.messages_source
.source_client
.submit_and_watch_signed_extrinsic(
self.messages_source.transaction_params.signer.public().into(),
SignParam::<P::SourceChain> {
spec_version,
transaction_version,
genesis_hash: *self.messages_source.source_client.genesis_hash(),
signer: self.messages_source.transaction_params.signer.clone(),
},
move |best_block_id, transaction_nonce| {
Ok(UnsignedTransaction::new(batch_call.into(), transaction_nonce).era(
TransactionEra::new(
best_block_id,
self.messages_source.transaction_params.mortality,
),
))
},
)
.await
}
}
@@ -22,7 +22,7 @@ use crate::{
messages_lane::{MessageLaneAdapter, ReceiveMessagesProofCallBuilder, SubstrateMessageLane},
messages_source::{ensure_messages_pallet_active, read_client_state, SubstrateMessagesProof},
on_demand::OnDemandRelay,
TransactionParams,
BatchCallBuilder, TransactionParams,
};
use async_std::sync::Arc;
@@ -34,10 +34,10 @@ use bp_messages::{
use bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof;
use messages_relay::{
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
message_lane_loop::{NoncesSubmitArtifacts, TargetClient, TargetClientState},
message_lane_loop::{BatchTransaction, NoncesSubmitArtifacts, TargetClient, TargetClientState},
};
use relay_substrate_client::{
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages, Client,
AccountIdOf, AccountKeyPairOf, BalanceOf, CallOf, Chain, ChainWithMessages, Client,
Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
TransactionTracker, UnsignedTransaction,
};
@@ -56,7 +56,7 @@ pub struct SubstrateMessagesTarget<P: SubstrateMessageLane> {
lane_id: LaneId,
relayer_id_at_source: AccountIdOf<P::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
source_to_target_headers_relay: Option<Arc<dyn OnDemandRelay<BlockNumberOf<P::SourceChain>>>>,
source_to_target_headers_relay: Option<Arc<dyn OnDemandRelay<P::SourceChain, P::TargetChain>>>,
}
impl<P: SubstrateMessageLane> SubstrateMessagesTarget<P> {
@@ -68,7 +68,7 @@ impl<P: SubstrateMessageLane> SubstrateMessagesTarget<P> {
relayer_id_at_source: AccountIdOf<P::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
source_to_target_headers_relay: Option<
Arc<dyn OnDemandRelay<BlockNumberOf<P::SourceChain>>>,
Arc<dyn OnDemandRelay<P::SourceChain, P::TargetChain>>,
>,
) -> Self {
SubstrateMessagesTarget {
@@ -132,6 +132,7 @@ where
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as Pair>::Public>,
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>>,
{
type BatchTransaction = BatchDeliveryTransaction<P>;
type TransactionTracker = TransactionTracker<P::TargetChain, Client<P::TargetChain>>;
async fn state(&self) -> Result<TargetClientState<MessageLaneAdapter<P>>, SubstrateError> {
@@ -267,13 +268,115 @@ where
Ok(NoncesSubmitArtifacts { nonces, tx_tracker })
}
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<MessageLaneAdapter<P>>) {
async fn require_source_header_on_target(
&self,
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<Option<Self::BatchTransaction>, SubstrateError> {
if let Some(ref source_to_target_headers_relay) = self.source_to_target_headers_relay {
if P::TargetBatchCallBuilder::BATCH_CALL_SUPPORTED {
return BatchDeliveryTransaction::<P>::new(self.clone(), id).await.map(Some)
}
source_to_target_headers_relay.require_more_headers(id.0).await;
}
Ok(None)
}
}
/// Batch transaction that brings target headers + and delivery confirmations to the source node.
pub struct BatchDeliveryTransaction<P: SubstrateMessageLane> {
messages_target: SubstrateMessagesTarget<P>,
proved_header: SourceHeaderIdOf<MessageLaneAdapter<P>>,
prove_calls: Vec<CallOf<P::TargetChain>>,
}
impl<P: SubstrateMessageLane> BatchDeliveryTransaction<P> {
async fn new(
messages_target: SubstrateMessagesTarget<P>,
required_source_header_on_target: SourceHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<Self, SubstrateError> {
let (proved_header, prove_calls) = messages_target
.source_to_target_headers_relay
.as_ref()
.expect("BatchDeliveryTransaction is only created when source_to_target_headers_relay is Some; qed")
.prove_header(required_source_header_on_target.0)
.await?;
Ok(Self { messages_target, proved_header, prove_calls })
}
}
#[async_trait]
impl<P: SubstrateMessageLane>
BatchTransaction<
SourceHeaderIdOf<MessageLaneAdapter<P>>,
<MessageLaneAdapter<P> as MessageLane>::MessagesProof,
TransactionTracker<P::TargetChain, Client<P::TargetChain>>,
SubstrateError,
> for BatchDeliveryTransaction<P>
where
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as Pair>::Public>,
{
fn required_header_id(&self) -> SourceHeaderIdOf<MessageLaneAdapter<P>> {
self.proved_header
}
async fn append_proof_and_send(
self,
proof: <MessageLaneAdapter<P> as MessageLane>::MessagesProof,
) -> Result<TransactionTracker<P::TargetChain, Client<P::TargetChain>>, SubstrateError> {
let mut calls = self.prove_calls;
calls.push(make_messages_delivery_call::<P>(
self.messages_target.relayer_id_at_source,
proof.1.nonces_start..=proof.1.nonces_end,
proof,
false,
));
let batch_call = P::TargetBatchCallBuilder::build_batch_call(calls)?;
let (spec_version, transaction_version) =
self.messages_target.target_client.simple_runtime_version().await?;
self.messages_target
.target_client
.submit_and_watch_signed_extrinsic(
self.messages_target.transaction_params.signer.public().into(),
SignParam::<P::TargetChain> {
spec_version,
transaction_version,
genesis_hash: *self.messages_target.target_client.genesis_hash(),
signer: self.messages_target.transaction_params.signer.clone(),
},
move |best_block_id, transaction_nonce| {
Ok(UnsignedTransaction::new(batch_call.into(), transaction_nonce).era(
TransactionEra::new(
best_block_id,
self.messages_target.transaction_params.mortality,
),
))
},
)
.await
}
}
/// Make messages delivery call from given proof.
fn make_messages_delivery_call<P: SubstrateMessageLane>(
relayer_id_at_source: AccountIdOf<P::SourceChain>,
nonces: RangeInclusive<MessageNonce>,
proof: SubstrateMessagesProof<P::SourceChain>,
trace_call: bool,
) -> CallOf<P::TargetChain> {
let messages_count = nonces.end() - nonces.start() + 1;
let dispatch_weight = proof.0;
P::ReceiveMessagesProofCallBuilder::build_receive_messages_proof_call(
relayer_id_at_source,
proof,
messages_count as _,
dispatch_weight,
trace_call,
)
}
/// Make messages delivery transaction from given proof.
fn make_messages_delivery_transaction<P: SubstrateMessageLane>(
target_transaction_params: &TransactionParams<AccountKeyPairOf<P::TargetChain>>,
@@ -284,15 +387,7 @@ fn make_messages_delivery_transaction<P: SubstrateMessageLane>(
proof: SubstrateMessagesProof<P::SourceChain>,
trace_call: bool,
) -> Result<UnsignedTransaction<P::TargetChain>, SubstrateError> {
let messages_count = nonces.end() - nonces.start() + 1;
let dispatch_weight = proof.0;
let call = P::ReceiveMessagesProofCallBuilder::build_receive_messages_proof_call(
relayer_id_at_source,
proof,
messages_count as _,
dispatch_weight,
trace_call,
);
let call = make_messages_delivery_call::<P>(relayer_id_at_source, nonces, proof, trace_call);
Ok(UnsignedTransaction::new(call.into(), transaction_nonce)
.era(TransactionEra::new(target_best_block_id, target_transaction_params.mortality)))
}
@@ -16,15 +16,21 @@
//! On-demand Substrate -> Substrate header finality relay.
use crate::finality::SubmitFinalityProofCallBuilder;
use async_std::sync::{Arc, Mutex};
use async_trait::async_trait;
use bp_header_chain::ConsensusLogReader;
use bp_runtime::HeaderIdProvider;
use futures::{select, FutureExt};
use num_traits::{One, Zero};
use sp_runtime::traits::Header;
use finality_relay::{FinalitySyncParams, TargetClient as FinalityTargetClient};
use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client};
use relay_substrate_client::{
AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client, Error as SubstrateError,
HeaderIdOf,
};
use relay_utils::{
metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError,
STALL_TIMEOUT,
@@ -47,16 +53,18 @@ use crate::{
/// relay) needs it to continue its regular work. When enough headers are relayed, on-demand stops
/// syncing headers.
#[derive(Clone)]
pub struct OnDemandHeadersRelay<SourceChain: Chain> {
pub struct OnDemandHeadersRelay<P: SubstrateFinalitySyncPipeline> {
/// Relay task name.
relay_task_name: String,
/// Shared reference to maximal required finalized header number.
required_header_number: RequiredHeaderNumberRef<SourceChain>,
required_header_number: RequiredHeaderNumberRef<P::SourceChain>,
/// Client of the source chain.
source_client: Client<P::SourceChain>,
}
impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
impl<P: SubstrateFinalitySyncPipeline> OnDemandHeadersRelay<P> {
/// Create new on-demand headers relay.
pub fn new<P: SubstrateFinalitySyncPipeline<SourceChain = SourceChain>>(
pub fn new(
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
@@ -70,6 +78,7 @@ impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
let this = OnDemandHeadersRelay {
relay_task_name: on_demand_headers_relay_name::<P::SourceChain, P::TargetChain>(),
required_header_number: required_header_number.clone(),
source_client: source_client.clone(),
};
async_std::task::spawn(async move {
background_task::<P>(
@@ -87,23 +96,49 @@ impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
}
#[async_trait]
impl<SourceChain: Chain> OnDemandRelay<BlockNumberOf<SourceChain>>
for OnDemandHeadersRelay<SourceChain>
impl<P: SubstrateFinalitySyncPipeline> OnDemandRelay<P::SourceChain, P::TargetChain>
for OnDemandHeadersRelay<P>
{
async fn require_more_headers(&self, required_header: BlockNumberOf<SourceChain>) {
async fn require_more_headers(&self, required_header: BlockNumberOf<P::SourceChain>) {
let mut required_header_number = self.required_header_number.lock().await;
if required_header > *required_header_number {
log::trace!(
target: "bridge",
"[{}] More {} headers required. Going to sync up to the {}",
self.relay_task_name,
SourceChain::NAME,
P::SourceChain::NAME,
required_header,
);
*required_header_number = required_header;
}
}
async fn prove_header(
&self,
required_header: BlockNumberOf<P::SourceChain>,
) -> Result<(HeaderIdOf<P::SourceChain>, Vec<CallOf<P::TargetChain>>), SubstrateError> {
// first find proper header (either `required_header`) or its descendant
let finality_source = SubstrateFinalitySource::<P>::new(self.source_client.clone(), None);
let (header, proof) = finality_source.prove_block_finality(required_header).await?;
let header_id = header.id();
log::debug!(
target: "bridge",
"[{}] Requested to prove {} head {:?}. Selected to prove {} head {:?}",
self.relay_task_name,
P::SourceChain::NAME,
required_header,
P::SourceChain::NAME,
header_id,
);
// and then craft the submit-proof call
let call =
P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(header, proof);
Ok((header_id, vec![call]))
}
}
/// Background task that is responsible for starting headers relay.
@@ -18,18 +18,28 @@
//! on-demand pipelines.
use async_trait::async_trait;
use relay_substrate_client::{BlockNumberOf, CallOf, Chain, Error as SubstrateError, HeaderIdOf};
pub mod headers;
pub mod parachains;
/// On-demand headers relay that is relaying finalizing headers only when requested.
#[async_trait]
pub trait OnDemandRelay<SourceHeaderNumber>: Send + Sync {
pub trait OnDemandRelay<SourceChain: Chain, TargetChain: Chain>: Send + Sync {
/// Ask relay to relay source header with given number to the target chain.
///
/// Depending on implementation, on-demand relay may also relay `required_header` ancestors
/// (e.g. if they're mandatory), or its descendants. The request is considered complete if
/// the best avbailable header at the target chain has number that is larger than or equal
/// to the `required_header`.
async fn require_more_headers(&self, required_header: SourceHeaderNumber);
async fn require_more_headers(&self, required_header: BlockNumberOf<SourceChain>);
/// Ask relay to prove source `required_header` to the `TargetChain`.
///
/// Returns number of header that is proved (it may be the `required_header` or one of its
/// descendants) and calls for delivering the proof.
async fn prove_header(
&self,
required_header: BlockNumberOf<SourceChain>,
) -> Result<(HeaderIdOf<SourceChain>, Vec<CallOf<TargetChain>>), SubstrateError>;
}
@@ -21,7 +21,7 @@ use crate::{
on_demand::OnDemandRelay,
parachains::{
source::ParachainsSource, target::ParachainsTarget, ParachainsPipelineAdapter,
SubstrateParachainsPipeline,
SubmitParachainHeadsCallBuilder, SubstrateParachainsPipeline,
},
TransactionParams,
};
@@ -31,18 +31,21 @@ use async_std::{
sync::{Arc, Mutex},
};
use async_trait::async_trait;
use bp_polkadot_core::parachains::ParaHash;
use bp_polkadot_core::parachains::{ParaHash, ParaId};
use bp_runtime::HeaderIdProvider;
use futures::{select, FutureExt};
use num_traits::Zero;
use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
use parachains_relay::parachains_loop::{AvailableHeader, ParachainSyncParams, TargetClient};
use parachains_relay::parachains_loop::{
AvailableHeader, ParachainSyncParams, SourceClient, TargetClient,
};
use relay_substrate_client::{
AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf,
ParachainBase,
AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client, Error as SubstrateError,
HashOf, HeaderIdOf, ParachainBase, ANCIENT_BLOCK_THRESHOLD,
};
use relay_utils::{
metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, HeaderId,
metrics::MetricsParams, relay_loop::Client as RelayClient, BlockNumberBase, FailedClient,
HeaderId, UniqueSaturatedInto,
};
use std::fmt::Debug;
@@ -52,25 +55,32 @@ use std::fmt::Debug;
/// (e.g. messages relay) needs it to continue its regular work. When enough parachain headers
/// are relayed, on-demand stops syncing headers.
#[derive(Clone)]
pub struct OnDemandParachainsRelay<SourceParachain: Chain> {
pub struct OnDemandParachainsRelay<P: SubstrateParachainsPipeline> {
/// Relay task name.
relay_task_name: String,
/// Channel used to communicate with background task and ask for relay of parachain heads.
required_header_number_sender: Sender<BlockNumberOf<SourceParachain>>,
required_header_number_sender: Sender<BlockNumberOf<P::SourceParachain>>,
/// Source relay chain client.
source_relay_client: Client<P::SourceRelayChain>,
/// Target chain client.
target_client: Client<P::TargetChain>,
/// On-demand relay chain relay.
on_demand_source_relay_to_target_headers:
Arc<dyn OnDemandRelay<P::SourceRelayChain, P::TargetChain>>,
}
impl<SourceParachain: Chain> OnDemandParachainsRelay<SourceParachain> {
impl<P: SubstrateParachainsPipeline> OnDemandParachainsRelay<P> {
/// Create new on-demand parachains relay.
///
/// Note that the argument is the source relay chain client, not the parachain client.
/// That's because parachain finality is determined by the relay chain and we don't
/// need to connect to the parachain itself here.
pub fn new<P: SubstrateParachainsPipeline<SourceParachain = SourceParachain>>(
pub fn new(
source_relay_client: Client<P::SourceRelayChain>,
target_client: Client<P::TargetChain>,
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
on_demand_source_relay_to_target_headers: Arc<
dyn OnDemandRelay<BlockNumberOf<P::SourceRelayChain>>,
dyn OnDemandRelay<P::SourceRelayChain, P::TargetChain>,
>,
) -> Self
where
@@ -82,8 +92,13 @@ impl<SourceParachain: Chain> OnDemandParachainsRelay<SourceParachain> {
{
let (required_header_number_sender, required_header_number_receiver) = unbounded();
let this = OnDemandParachainsRelay {
relay_task_name: on_demand_parachains_relay_name::<SourceParachain, P::TargetChain>(),
relay_task_name: on_demand_parachains_relay_name::<P::SourceParachain, P::TargetChain>(
),
required_header_number_sender,
source_relay_client: source_relay_client.clone(),
target_client: target_client.clone(),
on_demand_source_relay_to_target_headers: on_demand_source_relay_to_target_headers
.clone(),
};
async_std::task::spawn(async move {
background_task::<P>(
@@ -101,23 +116,109 @@ impl<SourceParachain: Chain> OnDemandParachainsRelay<SourceParachain> {
}
#[async_trait]
impl<SourceParachain> OnDemandRelay<BlockNumberOf<SourceParachain>>
for OnDemandParachainsRelay<SourceParachain>
impl<P: SubstrateParachainsPipeline> OnDemandRelay<P::SourceParachain, P::TargetChain>
for OnDemandParachainsRelay<P>
where
SourceParachain: Chain,
P::SourceParachain: Chain<Hash = ParaHash>,
{
async fn require_more_headers(&self, required_header: BlockNumberOf<SourceParachain>) {
async fn require_more_headers(&self, required_header: BlockNumberOf<P::SourceParachain>) {
if let Err(e) = self.required_header_number_sender.send(required_header).await {
log::trace!(
target: "bridge",
"[{}] Failed to request {} header {:?}: {:?}",
self.relay_task_name,
SourceParachain::NAME,
P::SourceParachain::NAME,
required_header,
e,
);
}
}
/// Ask relay to prove source `required_header` to the `TargetChain`.
async fn prove_header(
&self,
required_parachain_header: BlockNumberOf<P::SourceParachain>,
) -> Result<(HeaderIdOf<P::SourceParachain>, Vec<CallOf<P::TargetChain>>), SubstrateError> {
// select headers to prove
let parachains_source = ParachainsSource::<P>::new(
self.source_relay_client.clone(),
Arc::new(Mutex::new(AvailableHeader::Missing)),
);
let env = (self, &parachains_source);
let (need_to_prove_relay_block, selected_relay_block, selected_parachain_block) =
select_headers_to_prove(env, required_parachain_header).await?;
log::debug!(
target: "bridge",
"[{}] Requested to prove {} head {:?}. Selected to prove {} head {:?} and {} head {:?}",
self.relay_task_name,
P::SourceParachain::NAME,
required_parachain_header,
P::SourceParachain::NAME,
selected_parachain_block,
P::SourceRelayChain::NAME,
if need_to_prove_relay_block {
Some(selected_relay_block)
} else {
None
},
);
// now let's prove relay chain block (if needed)
let mut calls = Vec::new();
let mut proved_relay_block = selected_relay_block;
if need_to_prove_relay_block {
let (relay_block, relay_prove_call) = self
.on_demand_source_relay_to_target_headers
.prove_header(selected_relay_block.number())
.await?;
proved_relay_block = relay_block;
calls.extend(relay_prove_call);
}
// despite what we've selected before (in `select_headers_to_prove` call), if headers relay
// have chose the different header (e.g. because there's no GRANDPA jusstification for it),
// we need to prove parachain head available at this header
let para_id = ParaId(P::SourceParachain::PARACHAIN_ID);
let mut proved_parachain_block = selected_parachain_block;
if proved_relay_block != selected_relay_block {
proved_parachain_block = parachains_source
.on_chain_para_head_id(proved_relay_block, para_id)
.await?
// this could happen e.g. if parachain has been offboarded?
.ok_or_else(|| {
SubstrateError::MissingRequiredParachainHead(
para_id,
proved_relay_block.number().unique_saturated_into(),
)
})?;
log::debug!(
target: "bridge",
"[{}] Selected to prove {} head {:?} and {} head {:?}. Instead proved {} head {:?} and {} head {:?}",
self.relay_task_name,
P::SourceParachain::NAME,
selected_parachain_block,
P::SourceRelayChain::NAME,
selected_relay_block,
P::SourceParachain::NAME,
proved_parachain_block,
P::SourceRelayChain::NAME,
proved_relay_block,
);
}
// and finally - prove parachain head
let (para_proof, para_hashes) =
parachains_source.prove_parachain_heads(proved_relay_block, &[para_id]).await?;
calls.push(P::SubmitParachainHeadsCallBuilder::build_submit_parachain_heads_call(
proved_relay_block,
para_hashes.into_iter().map(|h| (para_id, h)).collect(),
para_proof,
));
Ok((proved_parachain_block, calls))
}
}
/// Background task that is responsible for starting parachain headers relay.
@@ -126,7 +227,7 @@ async fn background_task<P: SubstrateParachainsPipeline>(
target_client: Client<P::TargetChain>,
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
on_demand_source_relay_to_target_headers: Arc<
dyn OnDemandRelay<BlockNumberOf<P::SourceRelayChain>>,
dyn OnDemandRelay<P::SourceRelayChain, P::TargetChain>,
>,
required_parachain_header_number_receiver: Receiver<BlockNumberOf<P::SourceParachain>>,
) where
@@ -487,6 +588,125 @@ where
RelayState::RelayingParaHeader(para_header_at_source)
}
/// Environment for the `select_headers_to_prove` call.
#[async_trait]
trait SelectHeadersToProveEnvironment<RBN, RBH, PBN, PBH> {
/// Returns associated parachain id.
fn parachain_id(&self) -> ParaId;
/// Returns best finalized relay block.
async fn best_finalized_relay_block_at_source(
&self,
) -> Result<HeaderId<RBH, RBN>, SubstrateError>;
/// Returns best finalized relay block that is known at `P::TargetChain`.
async fn best_finalized_relay_block_at_target(
&self,
) -> Result<HeaderId<RBH, RBN>, SubstrateError>;
/// Returns best finalized parachain block at given source relay chain block.
async fn best_finalized_para_block_at_source(
&self,
at_relay_block: HeaderId<RBH, RBN>,
) -> Result<Option<HeaderId<PBH, PBN>>, SubstrateError>;
}
#[async_trait]
impl<'a, P: SubstrateParachainsPipeline>
SelectHeadersToProveEnvironment<
BlockNumberOf<P::SourceRelayChain>,
HashOf<P::SourceRelayChain>,
BlockNumberOf<P::SourceParachain>,
HashOf<P::SourceParachain>,
> for (&'a OnDemandParachainsRelay<P>, &'a ParachainsSource<P>)
{
fn parachain_id(&self) -> ParaId {
ParaId(P::SourceParachain::PARACHAIN_ID)
}
async fn best_finalized_relay_block_at_source(
&self,
) -> Result<HeaderIdOf<P::SourceRelayChain>, SubstrateError> {
Ok(self.0.source_relay_client.best_finalized_header().await?.id())
}
async fn best_finalized_relay_block_at_target(
&self,
) -> Result<HeaderIdOf<P::SourceRelayChain>, SubstrateError> {
Ok(crate::messages_source::read_client_state::<P::TargetChain, P::SourceRelayChain>(
&self.0.target_client,
None,
P::SourceRelayChain::BEST_FINALIZED_HEADER_ID_METHOD,
)
.await?
.best_finalized_peer_at_best_self)
}
async fn best_finalized_para_block_at_source(
&self,
at_relay_block: HeaderIdOf<P::SourceRelayChain>,
) -> Result<Option<HeaderIdOf<P::SourceParachain>>, SubstrateError> {
self.1.on_chain_para_head_id(at_relay_block, self.parachain_id()).await
}
}
/// Given request to prove `required_parachain_header`, select actual headers that need to be
/// proved.
async fn select_headers_to_prove<RBN, RBH, PBN, PBH>(
env: impl SelectHeadersToProveEnvironment<RBN, RBH, PBN, PBH>,
required_parachain_header: PBN,
) -> Result<(bool, HeaderId<RBH, RBN>, HeaderId<PBH, PBN>), SubstrateError>
where
RBH: Copy,
RBN: BlockNumberBase,
PBH: Copy,
PBN: BlockNumberBase,
{
// parachains proof also requires relay header proof. Let's first select relay block
// number that we'll be dealing with
let best_finalized_relay_block_at_source = env.best_finalized_relay_block_at_source().await?;
let best_finalized_relay_block_at_target = env.best_finalized_relay_block_at_target().await?;
// if we can't prove `required_header` even using `best_finalized_relay_block_at_source`, we
// can't do anything here
// (this shall not actually happen, given current code, because we only require finalized
// headers)
let best_possible_parachain_block = env
.best_finalized_para_block_at_source(best_finalized_relay_block_at_source)
.await?
.filter(|best_possible_parachain_block| {
best_possible_parachain_block.number() >= required_parachain_header
})
.ok_or(SubstrateError::MissingRequiredParachainHead(
env.parachain_id(),
required_parachain_header.unique_saturated_into(),
))?;
// now let's check if `required_header` may be proved using
// `best_finalized_relay_block_at_target`
let selection = env
.best_finalized_para_block_at_source(best_finalized_relay_block_at_target)
.await?
.filter(|best_finalized_para_block_at_target| {
best_finalized_para_block_at_target.number() >= required_parachain_header
})
.map(|best_finalized_para_block_at_target| {
(false, best_finalized_relay_block_at_target, best_finalized_para_block_at_target)
})
// we don't require source node to be archive, so we can't craft storage proofs using
// ancient headers. So if the `best_finalized_relay_block_at_target` is too ancient, we
// can't craft storage proofs using it
.filter(|(_, selected_relay_block, _)| {
let difference = best_finalized_relay_block_at_source
.number()
.saturating_sub(selected_relay_block.number());
difference <= RBN::from(ANCIENT_BLOCK_THRESHOLD)
});
Ok(selection.unwrap_or((
true,
best_finalized_relay_block_at_source,
best_possible_parachain_block,
)))
}
#[cfg(test)]
mod tests {
use super::*;
@@ -705,4 +925,80 @@ mod tests {
RelayState::RelayingRelayHeader(800),
);
}
// tuple is:
//
// - best_finalized_relay_block_at_source
// - best_finalized_relay_block_at_target
// - best_finalized_para_block_at_source at best_finalized_relay_block_at_source
// - best_finalized_para_block_at_source at best_finalized_relay_block_at_target
#[async_trait]
impl SelectHeadersToProveEnvironment<u32, u32, u32, u32> for (u32, u32, u32, u32) {
fn parachain_id(&self) -> ParaId {
ParaId(0)
}
async fn best_finalized_relay_block_at_source(
&self,
) -> Result<HeaderId<u32, u32>, SubstrateError> {
Ok(HeaderId(self.0, self.0))
}
async fn best_finalized_relay_block_at_target(
&self,
) -> Result<HeaderId<u32, u32>, SubstrateError> {
Ok(HeaderId(self.1, self.1))
}
async fn best_finalized_para_block_at_source(
&self,
at_relay_block: HeaderId<u32, u32>,
) -> Result<Option<HeaderId<u32, u32>>, SubstrateError> {
if at_relay_block.0 == self.0 {
Ok(Some(HeaderId(self.2, self.2)))
} else if at_relay_block.0 == self.1 {
Ok(Some(HeaderId(self.3, self.3)))
} else {
Ok(None)
}
}
}
#[async_std::test]
async fn select_headers_to_prove_returns_err_if_required_para_block_is_missing_at_source() {
assert!(matches!(
select_headers_to_prove((20_u32, 10_u32, 200_u32, 100_u32), 300_u32,).await,
Err(SubstrateError::MissingRequiredParachainHead(ParaId(0), 300_u64)),
));
}
#[async_std::test]
async fn select_headers_to_prove_fails_to_use_existing_ancient_relay_block() {
assert_eq!(
select_headers_to_prove((220_u32, 10_u32, 200_u32, 100_u32), 100_u32,)
.await
.map_err(drop),
Ok((true, HeaderId(220, 220), HeaderId(200, 200))),
);
}
#[async_std::test]
async fn select_headers_to_prove_is_able_to_use_existing_recent_relay_block() {
assert_eq!(
select_headers_to_prove((40_u32, 10_u32, 200_u32, 100_u32), 100_u32,)
.await
.map_err(drop),
Ok((false, HeaderId(10, 10), HeaderId(100, 100))),
);
}
#[async_std::test]
async fn select_headers_to_prove_uses_new_relay_block() {
assert_eq!(
select_headers_to_prove((20_u32, 10_u32, 200_u32, 100_u32), 200_u32,)
.await
.map_err(drop),
Ok((true, HeaderId(20, 20), HeaderId(200, 200))),
);
}
}