referrerpolicy=no-referrer-when-downgrade

bridge_runtime_common/
extensions.rs

1// Copyright (C) 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//! Transaction extension that rejects bridge-related transactions, that include
18//! obsolete (duplicated) data or do not pass some additional pallet-specific
19//! checks.
20
21use bp_parachains::SubmitParachainHeadsInfo;
22use bp_relayers::ExplicitOrAccountParams;
23use bp_runtime::Parachain;
24use pallet_bridge_grandpa::{
25	BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper,
26};
27use pallet_bridge_messages::CallSubType as MessagesCallSubType;
28use pallet_bridge_parachains::{CallSubType as ParachainsCallSubtype, SubmitParachainHeadsHelper};
29use pallet_bridge_relayers::Pallet as RelayersPallet;
30use sp_runtime::{
31	traits::{Get, UniqueSaturatedInto},
32	transaction_validity::{TransactionPriority, TransactionValidity, ValidTransactionBuilder},
33};
34use sp_std::marker::PhantomData;
35
36// Re-export to avoid include tuplex dependency everywhere.
37#[doc(hidden)]
38pub mod __private {
39	pub use tuplex;
40}
41
42/// A duplication of the `FilterCall` trait.
43///
44/// We need this trait in order to be able to implement it for the messages pallet,
45/// since the implementation is done outside of the pallet crate.
46pub trait BridgeRuntimeFilterCall<AccountId, Call> {
47	/// Data that may be passed from the validate to `post_dispatch`.
48	type ToPostDispatch;
49	/// Called during validation. Needs to checks whether a runtime call, submitted
50	/// by the `who` is valid. Transactions not signed are not validated.
51	fn validate(who: &AccountId, call: &Call) -> (Self::ToPostDispatch, TransactionValidity);
52	/// Called after transaction is dispatched.
53	fn post_dispatch(_who: &AccountId, _has_failed: bool, _to_post_dispatch: Self::ToPostDispatch) {
54	}
55}
56
57/// Wrapper for the bridge GRANDPA pallet that checks calls for obsolete submissions
58/// and also boosts transaction priority if it has submitted by registered relayer.
59/// The boost is computed as
60/// `(BundledHeaderNumber - 1 - BestFinalizedHeaderNumber) * Priority::get()`.
61/// The boost is only applied if submitter has active registration in the relayers
62/// pallet.
63pub struct CheckAndBoostBridgeGrandpaTransactions<T, I, Priority, SlashAccount>(
64	PhantomData<(T, I, Priority, SlashAccount)>,
65);
66
67impl<T, I: 'static, Priority: Get<TransactionPriority>, SlashAccount: Get<T::AccountId>>
68	BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
69	for CheckAndBoostBridgeGrandpaTransactions<T, I, Priority, SlashAccount>
70where
71	T: pallet_bridge_relayers::Config + pallet_bridge_grandpa::Config<I>,
72	T::RuntimeCall: GrandpaCallSubType<T, I>,
73{
74	// bridged header number, bundled in transaction
75	type ToPostDispatch = Option<BridgedBlockNumber<T, I>>;
76
77	fn validate(
78		who: &T::AccountId,
79		call: &T::RuntimeCall,
80	) -> (Self::ToPostDispatch, TransactionValidity) {
81		match GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call) {
82			Ok(Some(our_tx)) => {
83				let to_post_dispatch = Some(our_tx.base.block_number);
84				let total_priority_boost =
85					compute_priority_boost::<T, _, Priority>(who, our_tx.improved_by);
86				(
87					to_post_dispatch,
88					ValidTransactionBuilder::default().priority(total_priority_boost).build(),
89				)
90			},
91			Ok(None) => (None, ValidTransactionBuilder::default().build()),
92			Err(e) => (None, Err(e)),
93		}
94	}
95
96	fn post_dispatch(
97		relayer: &T::AccountId,
98		has_failed: bool,
99		bundled_block_number: Self::ToPostDispatch,
100	) {
101		// we are only interested in associated pallet submissions
102		let Some(bundled_block_number) = bundled_block_number else { return };
103		// we are only interested in failed or unneeded transactions
104		let has_failed =
105			has_failed || !SubmitFinalityProofHelper::<T, I>::was_successful(bundled_block_number);
106
107		if !has_failed {
108			return
109		}
110
111		// let's slash registered relayer
112		RelayersPallet::<T>::slash_and_deregister(
113			relayer,
114			ExplicitOrAccountParams::Explicit::<_, ()>(SlashAccount::get()),
115		);
116	}
117}
118
119/// Wrapper for the bridge parachains pallet that checks calls for obsolete submissions
120/// and also boosts transaction priority if it has submitted by registered relayer.
121/// The boost is computed as
122/// `(BundledHeaderNumber - 1 - BestKnownHeaderNumber) * Priority::get()`.
123/// The boost is only applied if submitter has active registration in the relayers
124/// pallet.
125pub struct CheckAndBoostBridgeParachainsTransactions<
126	T,
127	ParachainsInstance,
128	Para,
129	Priority,
130	SlashAccount,
131>(PhantomData<(T, ParachainsInstance, Para, Priority, SlashAccount)>);
132
133impl<
134		T,
135		ParachainsInstance,
136		Para,
137		Priority: Get<TransactionPriority>,
138		SlashAccount: Get<T::AccountId>,
139	> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
140	for CheckAndBoostBridgeParachainsTransactions<T, ParachainsInstance, Para, Priority, SlashAccount>
141where
142	T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config<ParachainsInstance>,
143	ParachainsInstance: 'static,
144	Para: Parachain,
145	T::RuntimeCall: ParachainsCallSubtype<T, ParachainsInstance>,
146{
147	// bridged header number, bundled in transaction
148	type ToPostDispatch = Option<SubmitParachainHeadsInfo>;
149
150	fn validate(
151		who: &T::AccountId,
152		call: &T::RuntimeCall,
153	) -> (Self::ToPostDispatch, TransactionValidity) {
154		match ParachainsCallSubtype::<T, ParachainsInstance>::check_obsolete_submit_parachain_heads(
155			call,
156		) {
157			Ok(Some(our_tx)) if our_tx.base.para_id.0 == Para::PARACHAIN_ID => {
158				let to_post_dispatch = Some(our_tx.base);
159				let total_priority_boost =
160					compute_priority_boost::<T, _, Priority>(&who, our_tx.improved_by);
161				(
162					to_post_dispatch,
163					ValidTransactionBuilder::default().priority(total_priority_boost).build(),
164				)
165			},
166			Ok(_) => (None, ValidTransactionBuilder::default().build()),
167			Err(e) => (None, Err(e)),
168		}
169	}
170
171	fn post_dispatch(relayer: &T::AccountId, has_failed: bool, maybe_update: Self::ToPostDispatch) {
172		// we are only interested in associated pallet submissions
173		let Some(update) = maybe_update else { return };
174		// we are only interested in failed or unneeded transactions
175		let has_failed = has_failed ||
176			!SubmitParachainHeadsHelper::<T, ParachainsInstance>::was_successful(&update);
177
178		if !has_failed {
179			return
180		}
181
182		// let's slash registered relayer
183		RelayersPallet::<T>::slash_and_deregister(
184			relayer,
185			ExplicitOrAccountParams::Explicit::<_, ()>(SlashAccount::get()),
186		);
187	}
188}
189
190impl<T, I: 'static> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
191	for pallet_bridge_grandpa::Pallet<T, I>
192where
193	T: pallet_bridge_grandpa::Config<I>,
194	T::RuntimeCall: GrandpaCallSubType<T, I>,
195{
196	type ToPostDispatch = ();
197	fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
198		(
199			(),
200			GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
201				.and_then(|_| ValidTransactionBuilder::default().build()),
202		)
203	}
204}
205
206impl<T, I: 'static> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
207	for pallet_bridge_parachains::Pallet<T, I>
208where
209	T: pallet_bridge_parachains::Config<I>,
210	T::RuntimeCall: ParachainsCallSubtype<T, I>,
211{
212	type ToPostDispatch = ();
213	fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
214		(
215			(),
216			ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
217				.and_then(|_| ValidTransactionBuilder::default().build()),
218		)
219	}
220}
221
222impl<T: pallet_bridge_messages::Config<I>, I: 'static>
223	BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall> for pallet_bridge_messages::Pallet<T, I>
224where
225	T::RuntimeCall: MessagesCallSubType<T, I>,
226{
227	type ToPostDispatch = ();
228	/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
229	/// transactions, that are delivering outdated messages/confirmations. Without this validation,
230	/// even honest relayers may lose their funds if there are multiple relays running and
231	/// submitting the same messages/confirmations.
232	fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
233		((), call.check_obsolete_call())
234	}
235}
236
237/// Computes priority boost that improved known header by `improved_by`
238fn compute_priority_boost<T, N, Priority>(
239	relayer: &T::AccountId,
240	improved_by: N,
241) -> TransactionPriority
242where
243	T: pallet_bridge_relayers::Config,
244	N: UniqueSaturatedInto<TransactionPriority>,
245	Priority: Get<TransactionPriority>,
246{
247	// we only boost priority if relayer has staked required balance
248	let is_relayer_registration_active = RelayersPallet::<T>::is_registration_active(relayer);
249	// if tx improves by just one, there's no need to bump its priority
250	let improved_by: TransactionPriority = improved_by.unique_saturated_into().saturating_sub(1);
251	// if relayer is registered, for every skipped header we improve by `Priority`
252	let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 };
253	improved_by.saturating_mul(boost_per_header)
254}
255
256/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
257///
258/// ## Example
259///
260/// ```nocompile
261/// generate_bridge_reject_obsolete_headers_and_messages!{
262///     Call, AccountId
263///     BridgeRococoGrandpa, BridgeRococoMessages,
264///     BridgeRococoParachains
265/// }
266/// ```
267///
268/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged
269/// headers and messages. Without that extension, even honest relayers may lose their funds if
270/// there are multiple relays running and submitting the same information.
271#[macro_export]
272macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
273	($call:ty, $account_id:ty, $($filter_call:ty),*) => {
274		#[derive(Clone, codec::Decode, codec::DecodeWithMemTracking, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)]
275		pub struct BridgeRejectObsoleteHeadersAndMessages;
276		impl sp_runtime::traits::TransactionExtension<$call> for BridgeRejectObsoleteHeadersAndMessages {
277			const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
278			type Implicit = ();
279			type Val = Option<(
280				$account_id,
281				( $(
282					<$filter_call as $crate::extensions::BridgeRuntimeFilterCall<
283						$account_id,
284						$call,
285					>>::ToPostDispatch,
286				)* ),
287			)>;
288			type Pre = Self::Val;
289
290			fn weight(&self, _: &$call) -> frame_support::pallet_prelude::Weight {
291				frame_support::pallet_prelude::Weight::zero()
292			}
293
294			fn validate(
295				&self,
296				origin: <$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
297				call: &$call,
298				_info: &sp_runtime::traits::DispatchInfoOf<$call>,
299				_len: usize,
300				_self_implicit: Self::Implicit,
301				_inherited_implication: &impl codec::Encode,
302				_source: sp_runtime::transaction_validity::TransactionSource,
303			) -> Result<
304				(
305					sp_runtime::transaction_validity::ValidTransaction,
306					Self::Val,
307					<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
308				), sp_runtime::transaction_validity::TransactionValidityError
309			> {
310				use $crate::extensions::__private::tuplex::PushBack;
311				use sp_runtime::traits::AsSystemOriginSigner;
312
313				let Some(who) = origin.as_system_origin_signer() else {
314					return Ok((Default::default(), None, origin));
315				};
316
317				let to_post_dispatch = ();
318				let tx_validity = sp_runtime::transaction_validity::ValidTransaction::default();
319				$(
320					let (from_validate, call_filter_validity) = <
321						$filter_call as
322						$crate::extensions::BridgeRuntimeFilterCall<
323							$account_id,
324							$call,
325						>>::validate(who, call);
326					let to_post_dispatch = to_post_dispatch.push_back(from_validate);
327					let tx_validity = tx_validity.combine_with(call_filter_validity?);
328				)*
329				Ok((tx_validity, Some((who.clone(), to_post_dispatch)), origin))
330			}
331
332			fn prepare(
333				self,
334				val: Self::Val,
335				_origin: &<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
336				_call: &$call,
337				_info: &sp_runtime::traits::DispatchInfoOf<$call>,
338				_len: usize,
339			) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
340				Ok(val)
341			}
342
343			#[allow(unused_variables)]
344			fn post_dispatch_details(
345				to_post_dispatch: Self::Pre,
346				info: &sp_runtime::traits::DispatchInfoOf<$call>,
347				post_info: &sp_runtime::traits::PostDispatchInfoOf<$call>,
348				len: usize,
349				result: &sp_runtime::DispatchResult,
350			) -> Result<frame_support::pallet_prelude::Weight, sp_runtime::transaction_validity::TransactionValidityError> {
351				use $crate::extensions::__private::tuplex::PopFront;
352
353				let Some((relayer, to_post_dispatch)) = to_post_dispatch else {
354					return Ok(frame_support::pallet_prelude::Weight::zero())
355				};
356
357				let has_failed = result.is_err();
358				$(
359					let (item, to_post_dispatch) = to_post_dispatch.pop_front();
360					<
361						$filter_call as
362						$crate::extensions::BridgeRuntimeFilterCall<
363							$account_id,
364							$call,
365						>>::post_dispatch(&relayer, has_failed, item);
366				)*
367				Ok(frame_support::pallet_prelude::Weight::zero())
368			}
369		}
370	};
371}
372
373#[cfg(test)]
374mod tests {
375	use super::*;
376	use crate::mock::*;
377	use bp_header_chain::StoredHeaderDataBuilder;
378	use bp_messages::{InboundLaneData, MessageNonce, OutboundLaneData};
379	use bp_parachains::{BestParaHeadHash, ParaInfo};
380	use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
381	use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
382	use bp_runtime::HeaderId;
383	use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID};
384	use codec::{Decode, Encode, MaxEncodedLen};
385	use frame_support::{assert_err, assert_ok, traits::fungible::Mutate};
386	use pallet_bridge_grandpa::{Call as GrandpaCall, StoredAuthoritySet};
387	use pallet_bridge_parachains::Call as ParachainsCall;
388	use scale_info::TypeInfo;
389	use sp_runtime::{
390		traits::{
391			parameter_types, AsSystemOriginSigner, AsTransactionAuthorizedOrigin, ConstU64,
392			DispatchTransaction, Header as _, TransactionExtension,
393		},
394		transaction_validity::{
395			InvalidTransaction, TransactionSource::External, TransactionValidity, ValidTransaction,
396		},
397		DispatchError,
398	};
399
400	parameter_types! {
401		pub MsgProofsRewardsAccount: RewardsAccountParams<TestLaneIdType> = RewardsAccountParams::new(
402			test_lane_id(),
403			TEST_BRIDGED_CHAIN_ID,
404			RewardsAccountOwner::ThisChain,
405		);
406		pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams<TestLaneIdType> = RewardsAccountParams::new(
407			test_lane_id(),
408			TEST_BRIDGED_CHAIN_ID,
409			RewardsAccountOwner::BridgedChain,
410		);
411	}
412
413	#[derive(Debug, Clone, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
414	pub struct MockCall {
415		data: u32,
416	}
417
418	#[derive(Debug, Clone, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
419	pub struct MockOrigin(pub u64);
420
421	impl AsSystemOriginSigner<u64> for MockOrigin {
422		fn as_system_origin_signer(&self) -> Option<&u64> {
423			Some(&self.0)
424		}
425	}
426
427	impl AsTransactionAuthorizedOrigin for MockOrigin {
428		fn is_transaction_authorized(&self) -> bool {
429			true
430		}
431	}
432
433	impl From<u64> for MockOrigin {
434		fn from(o: u64) -> Self {
435			Self(o)
436		}
437	}
438
439	impl sp_runtime::traits::Dispatchable for MockCall {
440		type RuntimeOrigin = MockOrigin;
441		type Config = ();
442		type Info = ();
443		type PostInfo = ();
444
445		fn dispatch(
446			self,
447			_origin: Self::RuntimeOrigin,
448		) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
449			unimplemented!()
450		}
451	}
452
453	pub struct FirstFilterCall;
454	impl FirstFilterCall {
455		fn post_dispatch_called_with(success: bool) {
456			frame_support::storage::unhashed::put(&[1], &success);
457		}
458
459		fn verify_post_dispatch_called_with(success: bool) {
460			assert_eq!(frame_support::storage::unhashed::get::<bool>(&[1]), Some(success));
461		}
462	}
463
464	impl BridgeRuntimeFilterCall<u64, MockCall> for FirstFilterCall {
465		type ToPostDispatch = u64;
466		fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
467			if call.data <= 1 {
468				return (1, InvalidTransaction::Custom(1).into())
469			}
470
471			(1, Ok(ValidTransaction { priority: 1, ..Default::default() }))
472		}
473
474		fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) {
475			Self::post_dispatch_called_with(!has_failed);
476			assert_eq!(to_post_dispatch, 1);
477		}
478	}
479
480	pub struct SecondFilterCall;
481
482	impl SecondFilterCall {
483		fn post_dispatch_called_with(success: bool) {
484			frame_support::storage::unhashed::put(&[2], &success);
485		}
486
487		fn verify_post_dispatch_called_with(success: bool) {
488			assert_eq!(frame_support::storage::unhashed::get::<bool>(&[2]), Some(success));
489		}
490	}
491
492	impl BridgeRuntimeFilterCall<u64, MockCall> for SecondFilterCall {
493		type ToPostDispatch = u64;
494		fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
495			if call.data <= 2 {
496				return (2, InvalidTransaction::Custom(2).into())
497			}
498
499			(2, Ok(ValidTransaction { priority: 2, ..Default::default() }))
500		}
501
502		fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) {
503			Self::post_dispatch_called_with(!has_failed);
504			assert_eq!(to_post_dispatch, 2);
505		}
506	}
507
508	fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
509		let test_stake: ThisChainBalance = TestStake::get();
510		ExistentialDeposit::get().saturating_add(test_stake * 100)
511	}
512
513	// in tests, the following accounts are equal (because of how `into_sub_account_truncating`
514	// works)
515
516	fn delivery_rewards_account() -> ThisChainAccountId {
517		TestPaymentProcedure::rewards_account(MsgProofsRewardsAccount::get())
518	}
519
520	fn confirmation_rewards_account() -> ThisChainAccountId {
521		TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
522	}
523
524	fn relayer_account_at_this_chain() -> ThisChainAccountId {
525		0
526	}
527
528	fn initialize_environment(
529		best_relay_header_number: BridgedChainBlockNumber,
530		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
531		best_message: MessageNonce,
532	) {
533		let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
534		let best_relay_header = HeaderId(best_relay_header_number, BridgedChainHash::default());
535		pallet_bridge_grandpa::CurrentAuthoritySet::<TestRuntime>::put(
536			StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
537		);
538		pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(best_relay_header);
539		pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
540			best_relay_header.hash(),
541			bp_test_utils::test_header::<BridgedChainHeader>(0).build(),
542		);
543
544		let para_id = ParaId(BridgedUnderlyingParachain::PARACHAIN_ID);
545		let para_info = ParaInfo {
546			best_head_hash: BestParaHeadHash {
547				at_relay_block_number: parachain_head_at_relay_header_number,
548				head_hash: [parachain_head_at_relay_header_number as u8; 32].into(),
549			},
550			next_imported_hash_position: 0,
551		};
552		pallet_bridge_parachains::ParasInfo::<TestRuntime>::insert(para_id, para_info);
553
554		let lane_id = test_lane_id();
555		let in_lane_data =
556			InboundLaneData { last_confirmed_nonce: best_message, ..Default::default() };
557		pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
558
559		let out_lane_data =
560			OutboundLaneData { latest_received_nonce: best_message, ..Default::default() };
561		pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(lane_id, out_lane_data);
562
563		Balances::mint_into(&delivery_rewards_account(), ExistentialDeposit::get()).unwrap();
564		Balances::mint_into(&confirmation_rewards_account(), ExistentialDeposit::get()).unwrap();
565		Balances::mint_into(
566			&relayer_account_at_this_chain(),
567			initial_balance_of_relayer_account_at_this_chain(),
568		)
569		.unwrap();
570	}
571
572	fn submit_relay_header_call(relay_header_number: BridgedChainBlockNumber) -> RuntimeCall {
573		let relay_header = BridgedChainHeader::new(
574			relay_header_number,
575			Default::default(),
576			Default::default(),
577			Default::default(),
578			Default::default(),
579		);
580		let relay_justification = make_default_justification(&relay_header);
581
582		RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof {
583			finality_target: Box::new(relay_header),
584			justification: relay_justification,
585		})
586	}
587
588	fn submit_parachain_head_call(
589		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
590	) -> RuntimeCall {
591		RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
592			at_relay_block: (parachain_head_at_relay_header_number, BridgedChainHash::default()),
593			parachains: vec![(
594				ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
595				[parachain_head_at_relay_header_number as u8; 32].into(),
596			)],
597			parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
598		})
599	}
600
601	#[test]
602	fn test_generated_obsolete_extension() {
603		generate_bridge_reject_obsolete_headers_and_messages!(
604			MockCall,
605			u64,
606			FirstFilterCall,
607			SecondFilterCall
608		);
609
610		run_test(|| {
611			assert_err!(
612				BridgeRejectObsoleteHeadersAndMessages.validate_only(
613					42u64.into(),
614					&MockCall { data: 1 },
615					&(),
616					0,
617					External,
618					0,
619				),
620				InvalidTransaction::Custom(1)
621			);
622			assert_err!(
623				BridgeRejectObsoleteHeadersAndMessages.validate_and_prepare(
624					42u64.into(),
625					&MockCall { data: 1 },
626					&(),
627					0,
628					0,
629				),
630				InvalidTransaction::Custom(1)
631			);
632
633			assert_err!(
634				BridgeRejectObsoleteHeadersAndMessages.validate_only(
635					42u64.into(),
636					&MockCall { data: 2 },
637					&(),
638					0,
639					External,
640					0,
641				),
642				InvalidTransaction::Custom(2)
643			);
644			assert_err!(
645				BridgeRejectObsoleteHeadersAndMessages.validate_and_prepare(
646					42u64.into(),
647					&MockCall { data: 2 },
648					&(),
649					0,
650					0,
651				),
652				InvalidTransaction::Custom(2)
653			);
654
655			assert_eq!(
656				BridgeRejectObsoleteHeadersAndMessages
657					.validate_only(42u64.into(), &MockCall { data: 3 }, &(), 0, External, 0)
658					.unwrap()
659					.0,
660				ValidTransaction { priority: 3, ..Default::default() },
661			);
662			assert_eq!(
663				BridgeRejectObsoleteHeadersAndMessages
664					.validate_and_prepare(42u64.into(), &MockCall { data: 3 }, &(), 0, 0)
665					.unwrap()
666					.0
667					.unwrap(),
668				(42, (1, 2)),
669			);
670
671			// when post_dispatch is called with `Ok(())`, it is propagated to all "nested"
672			// extensions
673			assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch_details(
674				Some((0, (1, 2))),
675				&(),
676				&(),
677				0,
678				&Ok(()),
679			));
680			FirstFilterCall::verify_post_dispatch_called_with(true);
681			SecondFilterCall::verify_post_dispatch_called_with(true);
682
683			// when post_dispatch is called with `Err(())`, it is propagated to all "nested"
684			// extensions
685			assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch_details(
686				Some((0, (1, 2))),
687				&(),
688				&(),
689				0,
690				&Err(DispatchError::BadOrigin),
691			));
692			FirstFilterCall::verify_post_dispatch_called_with(false);
693			SecondFilterCall::verify_post_dispatch_called_with(false);
694		});
695	}
696
697	frame_support::parameter_types! {
698		pub SlashDestination: ThisChainAccountId = 42;
699	}
700
701	type BridgeGrandpaWrapper =
702		CheckAndBoostBridgeGrandpaTransactions<TestRuntime, (), ConstU64<1_000>, SlashDestination>;
703
704	#[test]
705	fn grandpa_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
706		run_test(|| {
707			initialize_environment(100, 100, 100);
708
709			let priority_boost = BridgeGrandpaWrapper::validate(
710				&relayer_account_at_this_chain(),
711				&submit_relay_header_call(200),
712			)
713			.1
714			.unwrap()
715			.priority;
716			assert_eq!(priority_boost, 0);
717		})
718	}
719
720	#[test]
721	fn grandpa_wrapper_boosts_extensions_for_registered_relayer() {
722		run_test(|| {
723			initialize_environment(100, 100, 100);
724			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
725				.unwrap();
726
727			let priority_boost = BridgeGrandpaWrapper::validate(
728				&relayer_account_at_this_chain(),
729				&submit_relay_header_call(200),
730			)
731			.1
732			.unwrap()
733			.priority;
734			assert_eq!(priority_boost, 99_000);
735		})
736	}
737
738	#[test]
739	fn grandpa_wrapper_slashes_registered_relayer_if_transaction_fails() {
740		run_test(|| {
741			initialize_environment(100, 100, 100);
742			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
743				.unwrap();
744
745			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
746			BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), true, Some(150));
747			assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
748		})
749	}
750
751	#[test]
752	fn grandpa_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
753		run_test(|| {
754			initialize_environment(100, 100, 100);
755			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
756				.unwrap();
757
758			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
759			BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), false, Some(100));
760			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
761		})
762	}
763
764	type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions<
765		TestRuntime,
766		(),
767		BridgedUnderlyingParachain,
768		ConstU64<1_000>,
769		SlashDestination,
770	>;
771
772	#[test]
773	fn parachains_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
774		run_test(|| {
775			initialize_environment(100, 100, 100);
776
777			let priority_boost = BridgeParachainsWrapper::validate(
778				&relayer_account_at_this_chain(),
779				&submit_parachain_head_call(200),
780			)
781			.1
782			.unwrap()
783			.priority;
784			assert_eq!(priority_boost, 0);
785		})
786	}
787
788	#[test]
789	fn parachains_wrapper_boosts_extensions_for_registered_relayer() {
790		run_test(|| {
791			initialize_environment(100, 100, 100);
792			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
793				.unwrap();
794
795			let priority_boost = BridgeParachainsWrapper::validate(
796				&relayer_account_at_this_chain(),
797				&submit_parachain_head_call(200),
798			)
799			.1
800			.unwrap()
801			.priority;
802			assert_eq!(priority_boost, 99_000);
803		})
804	}
805
806	#[test]
807	fn parachains_wrapper_slashes_registered_relayer_if_transaction_fails() {
808		run_test(|| {
809			initialize_environment(100, 100, 100);
810			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
811				.unwrap();
812
813			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
814			BridgeParachainsWrapper::post_dispatch(
815				&relayer_account_at_this_chain(),
816				true,
817				Some(SubmitParachainHeadsInfo {
818					at_relay_block: HeaderId(150, Default::default()),
819					para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
820					para_head_hash: [150u8; 32].into(),
821					is_free_execution_expected: false,
822				}),
823			);
824			assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
825		})
826	}
827
828	#[test]
829	fn parachains_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
830		run_test(|| {
831			initialize_environment(100, 100, 100);
832			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
833				.unwrap();
834
835			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
836			BridgeParachainsWrapper::post_dispatch(
837				&relayer_account_at_this_chain(),
838				false,
839				Some(SubmitParachainHeadsInfo {
840					at_relay_block: HeaderId(100, Default::default()),
841					para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
842					para_head_hash: [100u8; 32].into(),
843					is_free_execution_expected: false,
844				}),
845			);
846			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
847		})
848	}
849}