referrerpolicy=no-referrer-when-downgrade

substrate_relay_helper/finality/
mod.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Types and functions intended to ease adding of new Substrate -> Substrate
18//! finality proofs synchronization pipelines.
19
20use crate::{
21	finality::{source::SubstrateFinalitySource, target::SubstrateFinalityTarget},
22	finality_base::{engine::Engine, SubstrateFinalityPipeline, SubstrateFinalityProof},
23	TransactionParams,
24};
25
26use async_trait::async_trait;
27use bp_header_chain::justification::{GrandpaJustification, JustificationVerificationContext};
28use finality_relay::{
29	FinalityPipeline, FinalitySyncPipeline, HeadersToRelay, SourceClient, TargetClient,
30};
31use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig};
32use relay_substrate_client::{
33	transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain,
34	ChainWithTransactions, Client, HashOf, HeaderOf, SyncHeader,
35};
36use relay_utils::{metrics::MetricsParams, TrackedTransactionStatus, TransactionTracker};
37use sp_core::Pair;
38use std::{fmt::Debug, marker::PhantomData};
39
40pub mod initialize;
41pub mod source;
42pub mod target;
43
44/// Default limit of recent finality proofs.
45///
46/// Finality delay of 4096 blocks is unlikely to happen in practice in
47/// Substrate+GRANDPA based chains (good to know).
48pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
49
50/// Convenience trait that adds bounds to `SubstrateFinalitySyncPipeline`.
51pub trait BaseSubstrateFinalitySyncPipeline:
52	SubstrateFinalityPipeline<TargetChain = Self::BoundedTargetChain>
53{
54	/// Bounded `SubstrateFinalityPipeline::TargetChain`.
55	type BoundedTargetChain: ChainWithTransactions<AccountId = Self::BoundedTargetChainAccountId>;
56
57	/// Bounded `AccountIdOf<SubstrateFinalityPipeline::TargetChain>`.
58	type BoundedTargetChainAccountId: From<<AccountKeyPairOf<Self::BoundedTargetChain> as Pair>::Public>
59		+ Send;
60}
61
62impl<T> BaseSubstrateFinalitySyncPipeline for T
63where
64	T: SubstrateFinalityPipeline,
65	T::TargetChain: ChainWithTransactions,
66	AccountIdOf<T::TargetChain>: From<<AccountKeyPairOf<Self::TargetChain> as Pair>::Public>,
67{
68	type BoundedTargetChain = T::TargetChain;
69	type BoundedTargetChainAccountId = AccountIdOf<T::TargetChain>;
70}
71
72/// Substrate -> Substrate finality proofs synchronization pipeline.
73#[async_trait]
74pub trait SubstrateFinalitySyncPipeline: BaseSubstrateFinalitySyncPipeline {
75	/// How submit finality proof call is built?
76	type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder<Self>;
77
78	/// Add relay guards if required.
79	async fn start_relay_guards(
80		target_client: &impl Client<Self::TargetChain>,
81		enable_version_guard: bool,
82	) -> relay_substrate_client::Result<()> {
83		if enable_version_guard {
84			relay_substrate_client::guard::abort_on_spec_version_change(
85				target_client.clone(),
86				target_client.simple_runtime_version().await?.spec_version,
87			);
88		}
89		Ok(())
90	}
91}
92
93/// Adapter that allows all `SubstrateFinalitySyncPipeline` to act as `FinalitySyncPipeline`.
94#[derive(Clone, Debug)]
95pub struct FinalitySyncPipelineAdapter<P: SubstrateFinalitySyncPipeline> {
96	_phantom: PhantomData<P>,
97}
98
99impl<P: SubstrateFinalitySyncPipeline> FinalityPipeline for FinalitySyncPipelineAdapter<P> {
100	const SOURCE_NAME: &'static str = P::SourceChain::NAME;
101	const TARGET_NAME: &'static str = P::TargetChain::NAME;
102
103	type Hash = HashOf<P::SourceChain>;
104	type Number = BlockNumberOf<P::SourceChain>;
105	type FinalityProof = SubstrateFinalityProof<P>;
106}
107
108impl<P: SubstrateFinalitySyncPipeline> FinalitySyncPipeline for FinalitySyncPipelineAdapter<P> {
109	type ConsensusLogReader = <P::FinalityEngine as Engine<P::SourceChain>>::ConsensusLogReader;
110	type Header = SyncHeader<HeaderOf<P::SourceChain>>;
111}
112
113/// Different ways of building `submit_finality_proof` calls.
114pub trait SubmitFinalityProofCallBuilder<P: SubstrateFinalitySyncPipeline> {
115	/// Given source chain header, its finality proof and the current authority set id, build call
116	/// of `submit_finality_proof` function of bridge GRANDPA module at the target chain.
117	fn build_submit_finality_proof_call(
118		header: SyncHeader<HeaderOf<P::SourceChain>>,
119		proof: SubstrateFinalityProof<P>,
120		is_free_execution_expected: bool,
121		context: <<P as SubstrateFinalityPipeline>::FinalityEngine as Engine<P::SourceChain>>::FinalityVerificationContext,
122	) -> CallOf<P::TargetChain>;
123}
124
125/// Building `submit_finality_proof` call when you have direct access to the target
126/// chain runtime.
127pub struct DirectSubmitGrandpaFinalityProofCallBuilder<P, R, I> {
128	_phantom: PhantomData<(P, R, I)>,
129}
130
131impl<P, R, I> SubmitFinalityProofCallBuilder<P>
132	for DirectSubmitGrandpaFinalityProofCallBuilder<P, R, I>
133where
134	P: SubstrateFinalitySyncPipeline,
135	R: BridgeGrandpaConfig<I>,
136	I: 'static,
137	R::BridgedChain: bp_runtime::Chain<Header = HeaderOf<P::SourceChain>>,
138	CallOf<P::TargetChain>: From<BridgeGrandpaCall<R, I>>,
139	P::FinalityEngine: Engine<
140		P::SourceChain,
141		FinalityProof = GrandpaJustification<HeaderOf<P::SourceChain>>,
142		FinalityVerificationContext = JustificationVerificationContext,
143	>,
144{
145	fn build_submit_finality_proof_call(
146		header: SyncHeader<HeaderOf<P::SourceChain>>,
147		proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
148		_is_free_execution_expected: bool,
149		_context: JustificationVerificationContext,
150	) -> CallOf<P::TargetChain> {
151		BridgeGrandpaCall::<R, I>::submit_finality_proof {
152			finality_target: Box::new(header.into_inner()),
153			justification: proof,
154		}
155		.into()
156	}
157}
158
159/// Macro that generates `SubmitFinalityProofCallBuilder` implementation for the case when
160/// you only have an access to the mocked version of target chain runtime. In this case you
161/// should provide "name" of the call variant for the bridge GRANDPA calls and the "name" of
162/// the variant for the `submit_finality_proof` call within that first option.
163#[rustfmt::skip]
164#[macro_export]
165macro_rules! generate_submit_finality_proof_call_builder {
166	($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => {
167		pub struct $mocked_builder;
168
169		impl $crate::finality::SubmitFinalityProofCallBuilder<$pipeline>
170			for $mocked_builder
171		{
172			fn build_submit_finality_proof_call(
173				header: relay_substrate_client::SyncHeader<
174					relay_substrate_client::HeaderOf<
175						<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain
176					>
177				>,
178				proof: bp_header_chain::justification::GrandpaJustification<
179					relay_substrate_client::HeaderOf<
180						<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain
181					>
182				>,
183				_is_free_execution_expected: bool,
184				_context: bp_header_chain::justification::JustificationVerificationContext,
185			) -> relay_substrate_client::CallOf<
186				<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain
187			> {
188				bp_runtime::paste::item! {
189					$bridge_grandpa($submit_finality_proof {
190						finality_target: Box::new(header.into_inner()),
191						justification: proof
192					})
193				}
194			}
195		}
196	};
197}
198
199/// Macro that generates `SubmitFinalityProofCallBuilder` implementation for the case when
200/// you only have an access to the mocked version of target chain runtime. In this case you
201/// should provide "name" of the call variant for the bridge GRANDPA calls and the "name" of
202/// the variant for the `submit_finality_proof_ex` call within that first option.
203#[rustfmt::skip]
204#[macro_export]
205macro_rules! generate_submit_finality_proof_ex_call_builder {
206	($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => {
207		pub struct $mocked_builder;
208
209		impl $crate::finality::SubmitFinalityProofCallBuilder<$pipeline>
210			for $mocked_builder
211		{
212			fn build_submit_finality_proof_call(
213				header: relay_substrate_client::SyncHeader<
214					relay_substrate_client::HeaderOf<
215						<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain
216					>
217				>,
218				proof: bp_header_chain::justification::GrandpaJustification<
219					relay_substrate_client::HeaderOf<
220						<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain
221					>
222				>,
223				is_free_execution_expected: bool,
224				context: bp_header_chain::justification::JustificationVerificationContext,
225			) -> relay_substrate_client::CallOf<
226				<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain
227			> {
228				bp_runtime::paste::item! {
229					$bridge_grandpa($submit_finality_proof {
230						finality_target: Box::new(header.into_inner()),
231						justification: proof,
232						current_set_id: context.authority_set_id,
233						is_free_execution_expected,
234					})
235				}
236			}
237		}
238	};
239}
240
241/// Run Substrate-to-Substrate finality sync loop.
242pub async fn run<P: SubstrateFinalitySyncPipeline>(
243	source_client: impl Client<P::SourceChain>,
244	target_client: impl Client<P::TargetChain>,
245	headers_to_relay: HeadersToRelay,
246	transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
247	metrics_params: MetricsParams,
248) -> anyhow::Result<()> {
249	log::info!(
250		target: "bridge",
251		"Starting {} -> {} finality proof relay: relaying {:?} headers",
252		P::SourceChain::NAME,
253		P::TargetChain::NAME,
254		headers_to_relay,
255	);
256
257	finality_relay::run(
258		SubstrateFinalitySource::<P, _>::new(source_client, None),
259		SubstrateFinalityTarget::<P, _>::new(target_client, transaction_params.clone()),
260		finality_relay::FinalitySyncParams {
261			tick: std::cmp::max(
262				P::SourceChain::AVERAGE_BLOCK_INTERVAL,
263				P::TargetChain::AVERAGE_BLOCK_INTERVAL,
264			),
265			recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
266			stall_timeout: transaction_stall_timeout(
267				transaction_params.mortality,
268				P::TargetChain::AVERAGE_BLOCK_INTERVAL,
269				relay_utils::STALL_TIMEOUT,
270			),
271			headers_to_relay,
272		},
273		metrics_params,
274		futures::future::pending(),
275	)
276	.await
277	.map_err(|e| anyhow::format_err!("{}", e))
278}
279
280/// Relay single header. No checks are made to ensure that transaction will succeed.
281pub async fn relay_single_header<P: SubstrateFinalitySyncPipeline>(
282	source_client: impl Client<P::SourceChain>,
283	target_client: impl Client<P::TargetChain>,
284	transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
285	header_number: BlockNumberOf<P::SourceChain>,
286) -> anyhow::Result<()> {
287	let finality_source = SubstrateFinalitySource::<P, _>::new(source_client, None);
288	let (header, proof) = finality_source.header_and_finality_proof(header_number).await?;
289	let Some(proof) = proof else {
290		return Err(anyhow::format_err!(
291			"Unable to submit {} header #{} to {}: no finality proof",
292			P::SourceChain::NAME,
293			header_number,
294			P::TargetChain::NAME,
295		));
296	};
297
298	let finality_target = SubstrateFinalityTarget::<P, _>::new(target_client, transaction_params);
299	let tx_tracker = finality_target.submit_finality_proof(header, proof, false).await?;
300	match tx_tracker.wait().await {
301		TrackedTransactionStatus::Finalized(_) => Ok(()),
302		TrackedTransactionStatus::Lost => Err(anyhow::format_err!(
303			"Transaction with {} header #{} is considered lost at {}",
304			P::SourceChain::NAME,
305			header_number,
306			P::TargetChain::NAME,
307		)),
308	}
309}