substrate_relay_helper/finality/
mod.rs1use 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
44pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
49
50pub trait BaseSubstrateFinalitySyncPipeline:
52 SubstrateFinalityPipeline<TargetChain = Self::BoundedTargetChain>
53{
54 type BoundedTargetChain: ChainWithTransactions<AccountId = Self::BoundedTargetChainAccountId>;
56
57 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#[async_trait]
74pub trait SubstrateFinalitySyncPipeline: BaseSubstrateFinalitySyncPipeline {
75 type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder<Self>;
77
78 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#[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
113pub trait SubmitFinalityProofCallBuilder<P: SubstrateFinalitySyncPipeline> {
115 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
125pub 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#[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#[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
241pub 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
280pub 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}