Skip to main content

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		#[allow(dead_code)]
275		#[derive(Clone, codec::Decode, codec::DecodeWithMemTracking, Default, codec::Encode, Eq, PartialEq, Debug, scale_info::TypeInfo)]
276		pub struct BridgeRejectObsoleteHeadersAndMessages;
277		impl sp_runtime::traits::TransactionExtension<$call> for BridgeRejectObsoleteHeadersAndMessages {
278			const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
279			type Implicit = ();
280			type Val = Option<(
281				$account_id,
282				( $(
283					<$filter_call as $crate::extensions::BridgeRuntimeFilterCall<
284						$account_id,
285						$call,
286					>>::ToPostDispatch,
287				)* ),
288			)>;
289			type Pre = Self::Val;
290
291			fn weight(&self, _: &$call) -> frame_support::pallet_prelude::Weight {
292				frame_support::pallet_prelude::Weight::zero()
293			}
294
295			fn validate(
296				&self,
297				origin: <$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
298				call: &$call,
299				_info: &sp_runtime::traits::DispatchInfoOf<$call>,
300				_len: usize,
301				_self_implicit: Self::Implicit,
302				_inherited_implication: &impl codec::Encode,
303				_source: sp_runtime::transaction_validity::TransactionSource,
304			) -> Result<
305				(
306					sp_runtime::transaction_validity::ValidTransaction,
307					Self::Val,
308					<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
309				), sp_runtime::transaction_validity::TransactionValidityError
310			> {
311				use $crate::extensions::__private::tuplex::PushBack;
312				use sp_runtime::traits::AsSystemOriginSigner;
313
314				let Some(who) = origin.as_system_origin_signer() else {
315					return Ok((Default::default(), None, origin));
316				};
317
318				let to_post_dispatch = ();
319				let tx_validity = sp_runtime::transaction_validity::ValidTransaction::default();
320				$(
321					let (from_validate, call_filter_validity) = <
322						$filter_call as
323						$crate::extensions::BridgeRuntimeFilterCall<
324							$account_id,
325							$call,
326						>>::validate(who, call);
327					let to_post_dispatch = to_post_dispatch.push_back(from_validate);
328					let tx_validity = tx_validity.combine_with(call_filter_validity?);
329				)*
330				Ok((tx_validity, Some((who.clone(), to_post_dispatch)), origin))
331			}
332
333			fn prepare(
334				self,
335				val: Self::Val,
336				_origin: &<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
337				_call: &$call,
338				_info: &sp_runtime::traits::DispatchInfoOf<$call>,
339				_len: usize,
340			) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
341				Ok(val)
342			}
343
344			#[allow(unused_variables)]
345			fn post_dispatch_details(
346				to_post_dispatch: Self::Pre,
347				info: &sp_runtime::traits::DispatchInfoOf<$call>,
348				post_info: &sp_runtime::traits::PostDispatchInfoOf<$call>,
349				len: usize,
350				result: &sp_runtime::DispatchResult,
351			) -> Result<frame_support::pallet_prelude::Weight, sp_runtime::transaction_validity::TransactionValidityError> {
352				use $crate::extensions::__private::tuplex::PopFront;
353
354				let Some((relayer, to_post_dispatch)) = to_post_dispatch else {
355					return Ok(frame_support::pallet_prelude::Weight::zero())
356				};
357
358				let has_failed = result.is_err();
359				$(
360					let (item, to_post_dispatch) = to_post_dispatch.pop_front();
361					<
362						$filter_call as
363						$crate::extensions::BridgeRuntimeFilterCall<
364							$account_id,
365							$call,
366						>>::post_dispatch(&relayer, has_failed, item);
367				)*
368				Ok(frame_support::pallet_prelude::Weight::zero())
369			}
370		}
371	};
372}
373
374#[cfg(test)]
375mod tests {
376	use super::*;
377	use crate::mock::*;
378	use bp_header_chain::StoredHeaderDataBuilder;
379	use bp_messages::{InboundLaneData, MessageNonce, OutboundLaneData};
380	use bp_parachains::{BestParaHeadHash, ParaInfo};
381	use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
382	use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
383	use bp_runtime::HeaderId;
384	use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID};
385	use codec::{Decode, Encode, MaxEncodedLen};
386	use frame_support::{assert_err, assert_ok, traits::fungible::Mutate};
387	use pallet_bridge_grandpa::{Call as GrandpaCall, StoredAuthoritySet};
388	use pallet_bridge_parachains::Call as ParachainsCall;
389	use scale_info::TypeInfo;
390	use sp_runtime::{
391		traits::{
392			parameter_types, AsSystemOriginSigner, AsTransactionAuthorizedOrigin, ConstU64,
393			DispatchTransaction, Header as _, TransactionExtension,
394		},
395		transaction_validity::{
396			InvalidTransaction, TransactionSource::External, TransactionValidity, ValidTransaction,
397		},
398		DispatchError,
399	};
400
401	parameter_types! {
402		pub MsgProofsRewardsAccount: RewardsAccountParams<TestLaneIdType> = RewardsAccountParams::new(
403			test_lane_id(),
404			TEST_BRIDGED_CHAIN_ID,
405			RewardsAccountOwner::ThisChain,
406		);
407		pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams<TestLaneIdType> = RewardsAccountParams::new(
408			test_lane_id(),
409			TEST_BRIDGED_CHAIN_ID,
410			RewardsAccountOwner::BridgedChain,
411		);
412	}
413
414	#[derive(Debug, Clone, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
415	pub struct MockCall {
416		data: u32,
417	}
418
419	#[derive(Debug, Clone, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
420	pub struct MockOrigin(pub u64);
421
422	impl AsSystemOriginSigner<u64> for MockOrigin {
423		fn as_system_origin_signer(&self) -> Option<&u64> {
424			Some(&self.0)
425		}
426	}
427
428	impl AsTransactionAuthorizedOrigin for MockOrigin {
429		fn is_transaction_authorized(&self) -> bool {
430			true
431		}
432	}
433
434	impl From<u64> for MockOrigin {
435		fn from(o: u64) -> Self {
436			Self(o)
437		}
438	}
439
440	impl sp_runtime::traits::Dispatchable for MockCall {
441		type RuntimeOrigin = MockOrigin;
442		type Config = ();
443		type Info = ();
444		type PostInfo = ();
445
446		fn dispatch(
447			self,
448			_origin: Self::RuntimeOrigin,
449		) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
450			unimplemented!()
451		}
452	}
453
454	pub struct FirstFilterCall;
455	impl FirstFilterCall {
456		fn post_dispatch_called_with(success: bool) {
457			frame_support::storage::unhashed::put(&[1], &success);
458		}
459
460		fn verify_post_dispatch_called_with(success: bool) {
461			assert_eq!(frame_support::storage::unhashed::get::<bool>(&[1]), Some(success));
462		}
463	}
464
465	impl BridgeRuntimeFilterCall<u64, MockCall> for FirstFilterCall {
466		type ToPostDispatch = u64;
467		fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
468			if call.data <= 1 {
469				return (1, InvalidTransaction::Custom(1).into());
470			}
471
472			(1, Ok(ValidTransaction { priority: 1, ..Default::default() }))
473		}
474
475		fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) {
476			Self::post_dispatch_called_with(!has_failed);
477			assert_eq!(to_post_dispatch, 1);
478		}
479	}
480
481	pub struct SecondFilterCall;
482
483	impl SecondFilterCall {
484		fn post_dispatch_called_with(success: bool) {
485			frame_support::storage::unhashed::put(&[2], &success);
486		}
487
488		fn verify_post_dispatch_called_with(success: bool) {
489			assert_eq!(frame_support::storage::unhashed::get::<bool>(&[2]), Some(success));
490		}
491	}
492
493	impl BridgeRuntimeFilterCall<u64, MockCall> for SecondFilterCall {
494		type ToPostDispatch = u64;
495		fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
496			if call.data <= 2 {
497				return (2, InvalidTransaction::Custom(2).into());
498			}
499
500			(2, Ok(ValidTransaction { priority: 2, ..Default::default() }))
501		}
502
503		fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) {
504			Self::post_dispatch_called_with(!has_failed);
505			assert_eq!(to_post_dispatch, 2);
506		}
507	}
508
509	fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
510		let test_stake: ThisChainBalance = TestStake::get();
511		ExistentialDeposit::get().saturating_add(test_stake * 100)
512	}
513
514	// in tests, the following accounts are equal (because of how `into_sub_account_truncating`
515	// works)
516
517	fn delivery_rewards_account() -> ThisChainAccountId {
518		TestPaymentProcedure::rewards_account(MsgProofsRewardsAccount::get())
519	}
520
521	fn confirmation_rewards_account() -> ThisChainAccountId {
522		TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
523	}
524
525	fn relayer_account_at_this_chain() -> ThisChainAccountId {
526		0
527	}
528
529	fn initialize_environment(
530		best_relay_header_number: BridgedChainBlockNumber,
531		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
532		best_message: MessageNonce,
533	) {
534		let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
535		let best_relay_header = HeaderId(best_relay_header_number, BridgedChainHash::default());
536		pallet_bridge_grandpa::CurrentAuthoritySet::<TestRuntime>::put(
537			StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
538		);
539		pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(best_relay_header);
540		pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
541			best_relay_header.hash(),
542			bp_test_utils::test_header::<BridgedChainHeader>(0).build(),
543		);
544
545		let para_id = ParaId(BridgedUnderlyingParachain::PARACHAIN_ID);
546		let para_info = ParaInfo {
547			best_head_hash: BestParaHeadHash {
548				at_relay_block_number: parachain_head_at_relay_header_number,
549				head_hash: [parachain_head_at_relay_header_number as u8; 32].into(),
550			},
551			next_imported_hash_position: 0,
552		};
553		pallet_bridge_parachains::ParasInfo::<TestRuntime>::insert(para_id, para_info);
554
555		let lane_id = test_lane_id();
556		let in_lane_data =
557			InboundLaneData { last_confirmed_nonce: best_message, ..Default::default() };
558		pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
559
560		let out_lane_data =
561			OutboundLaneData { latest_received_nonce: best_message, ..Default::default() };
562		pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(lane_id, out_lane_data);
563
564		Balances::mint_into(&delivery_rewards_account(), ExistentialDeposit::get()).unwrap();
565		Balances::mint_into(&confirmation_rewards_account(), ExistentialDeposit::get()).unwrap();
566		Balances::mint_into(
567			&relayer_account_at_this_chain(),
568			initial_balance_of_relayer_account_at_this_chain(),
569		)
570		.unwrap();
571	}
572
573	fn submit_relay_header_call(relay_header_number: BridgedChainBlockNumber) -> RuntimeCall {
574		let relay_header = BridgedChainHeader::new(
575			relay_header_number,
576			Default::default(),
577			Default::default(),
578			Default::default(),
579			Default::default(),
580		);
581		let relay_justification = make_default_justification(&relay_header);
582
583		RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof {
584			finality_target: Box::new(relay_header),
585			justification: relay_justification,
586		})
587	}
588
589	fn submit_parachain_head_call(
590		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
591	) -> RuntimeCall {
592		RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
593			at_relay_block: (parachain_head_at_relay_header_number, BridgedChainHash::default()),
594			parachains: vec![(
595				ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
596				[parachain_head_at_relay_header_number as u8; 32].into(),
597			)],
598			parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
599		})
600	}
601
602	#[test]
603	fn test_generated_obsolete_extension() {
604		generate_bridge_reject_obsolete_headers_and_messages!(
605			MockCall,
606			u64,
607			FirstFilterCall,
608			SecondFilterCall
609		);
610
611		run_test(|| {
612			assert_err!(
613				BridgeRejectObsoleteHeadersAndMessages.validate_only(
614					42u64.into(),
615					&MockCall { data: 1 },
616					&(),
617					0,
618					External,
619					0,
620				),
621				InvalidTransaction::Custom(1)
622			);
623			assert_err!(
624				BridgeRejectObsoleteHeadersAndMessages.validate_and_prepare(
625					42u64.into(),
626					&MockCall { data: 1 },
627					&(),
628					0,
629					0,
630				),
631				InvalidTransaction::Custom(1)
632			);
633
634			assert_err!(
635				BridgeRejectObsoleteHeadersAndMessages.validate_only(
636					42u64.into(),
637					&MockCall { data: 2 },
638					&(),
639					0,
640					External,
641					0,
642				),
643				InvalidTransaction::Custom(2)
644			);
645			assert_err!(
646				BridgeRejectObsoleteHeadersAndMessages.validate_and_prepare(
647					42u64.into(),
648					&MockCall { data: 2 },
649					&(),
650					0,
651					0,
652				),
653				InvalidTransaction::Custom(2)
654			);
655
656			assert_eq!(
657				BridgeRejectObsoleteHeadersAndMessages
658					.validate_only(42u64.into(), &MockCall { data: 3 }, &(), 0, External, 0)
659					.unwrap()
660					.0,
661				ValidTransaction { priority: 3, ..Default::default() },
662			);
663			assert_eq!(
664				BridgeRejectObsoleteHeadersAndMessages
665					.validate_and_prepare(42u64.into(), &MockCall { data: 3 }, &(), 0, 0)
666					.unwrap()
667					.0
668					.unwrap(),
669				(42, (1, 2)),
670			);
671
672			// when post_dispatch is called with `Ok(())`, it is propagated to all "nested"
673			// extensions
674			assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch_details(
675				Some((0, (1, 2))),
676				&(),
677				&(),
678				0,
679				&Ok(()),
680			));
681			FirstFilterCall::verify_post_dispatch_called_with(true);
682			SecondFilterCall::verify_post_dispatch_called_with(true);
683
684			// when post_dispatch is called with `Err(())`, it is propagated to all "nested"
685			// extensions
686			assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch_details(
687				Some((0, (1, 2))),
688				&(),
689				&(),
690				0,
691				&Err(DispatchError::BadOrigin),
692			));
693			FirstFilterCall::verify_post_dispatch_called_with(false);
694			SecondFilterCall::verify_post_dispatch_called_with(false);
695		});
696	}
697
698	frame_support::parameter_types! {
699		pub SlashDestination: ThisChainAccountId = 42;
700	}
701
702	type BridgeGrandpaWrapper =
703		CheckAndBoostBridgeGrandpaTransactions<TestRuntime, (), ConstU64<1_000>, SlashDestination>;
704
705	#[test]
706	fn grandpa_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
707		run_test(|| {
708			initialize_environment(100, 100, 100);
709
710			let priority_boost = BridgeGrandpaWrapper::validate(
711				&relayer_account_at_this_chain(),
712				&submit_relay_header_call(200),
713			)
714			.1
715			.unwrap()
716			.priority;
717			assert_eq!(priority_boost, 0);
718		})
719	}
720
721	#[test]
722	fn grandpa_wrapper_boosts_extensions_for_registered_relayer() {
723		run_test(|| {
724			initialize_environment(100, 100, 100);
725			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
726				.unwrap();
727
728			let priority_boost = BridgeGrandpaWrapper::validate(
729				&relayer_account_at_this_chain(),
730				&submit_relay_header_call(200),
731			)
732			.1
733			.unwrap()
734			.priority;
735			assert_eq!(priority_boost, 99_000);
736		})
737	}
738
739	#[test]
740	fn grandpa_wrapper_slashes_registered_relayer_if_transaction_fails() {
741		run_test(|| {
742			initialize_environment(100, 100, 100);
743			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
744				.unwrap();
745
746			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
747			BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), true, Some(150));
748			assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
749		})
750	}
751
752	#[test]
753	fn grandpa_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
754		run_test(|| {
755			initialize_environment(100, 100, 100);
756			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
757				.unwrap();
758
759			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
760			BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), false, Some(100));
761			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
762		})
763	}
764
765	type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions<
766		TestRuntime,
767		(),
768		BridgedUnderlyingParachain,
769		ConstU64<1_000>,
770		SlashDestination,
771	>;
772
773	#[test]
774	fn parachains_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
775		run_test(|| {
776			initialize_environment(100, 100, 100);
777
778			let priority_boost = BridgeParachainsWrapper::validate(
779				&relayer_account_at_this_chain(),
780				&submit_parachain_head_call(200),
781			)
782			.1
783			.unwrap()
784			.priority;
785			assert_eq!(priority_boost, 0);
786		})
787	}
788
789	#[test]
790	fn parachains_wrapper_boosts_extensions_for_registered_relayer() {
791		run_test(|| {
792			initialize_environment(100, 100, 100);
793			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
794				.unwrap();
795
796			let priority_boost = BridgeParachainsWrapper::validate(
797				&relayer_account_at_this_chain(),
798				&submit_parachain_head_call(200),
799			)
800			.1
801			.unwrap()
802			.priority;
803			assert_eq!(priority_boost, 99_000);
804		})
805	}
806
807	#[test]
808	fn parachains_wrapper_slashes_registered_relayer_if_transaction_fails() {
809		run_test(|| {
810			initialize_environment(100, 100, 100);
811			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
812				.unwrap();
813
814			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
815			BridgeParachainsWrapper::post_dispatch(
816				&relayer_account_at_this_chain(),
817				true,
818				Some(SubmitParachainHeadsInfo {
819					at_relay_block: HeaderId(150, Default::default()),
820					para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
821					para_head_hash: [150u8; 32].into(),
822					is_free_execution_expected: false,
823				}),
824			);
825			assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
826		})
827	}
828
829	#[test]
830	fn parachains_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
831		run_test(|| {
832			initialize_environment(100, 100, 100);
833			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
834				.unwrap();
835
836			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
837			BridgeParachainsWrapper::post_dispatch(
838				&relayer_account_at_this_chain(),
839				false,
840				Some(SubmitParachainHeadsInfo {
841					at_relay_block: HeaderId(100, Default::default()),
842					para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
843					para_head_hash: [100u8; 32].into(),
844					is_free_execution_expected: false,
845				}),
846			);
847			assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
848		})
849	}
850}