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