From d80171ec229d903ca13673f2bf0d759b7bbb830f Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Wed, 4 Oct 2023 01:14:01 +0300
Subject: [PATCH] Update bridges subtree (#1740)

Co-authored-by: Branislav Kontur <bkontur@gmail.com>
---
 Cargo.lock                                    |  17 +
 Cargo.toml                                    |   1 +
 .../runtime-common/src/priority_calculator.rs |   2 +-
 .../src/refund_relayer_extension.rs           | 928 +++++++++++++-----
 .../chain-bridge-hub-kusama/src/lib.rs        |   1 -
 .../chain-bridge-hub-polkadot/src/lib.rs      |   1 -
 .../chain-bridge-hub-rococo/src/lib.rs        |   2 +-
 .../chain-bridge-hub-wococo/src/lib.rs        |   1 -
 bridges/primitives/chain-kusama/src/lib.rs    |   1 -
 .../chain-polkadot-bulletin/Cargo.toml        |  41 +
 .../chain-polkadot-bulletin/src/lib.rs        | 215 ++++
 bridges/primitives/chain-polkadot/src/lib.rs  |   1 -
 bridges/primitives/chain-rococo/src/lib.rs    |   1 -
 bridges/primitives/chain-wococo/src/lib.rs    |   1 -
 .../src/justification/verification/mod.rs     |   1 +
 bridges/primitives/runtime/src/chain.rs       |  16 +-
 bridges/primitives/runtime/src/lib.rs         |   3 +
 .../src/bridge_hub_rococo_config.rs           |  20 +-
 .../src/bridge_hub_wococo_config.rs           |  20 +-
 19 files changed, 1017 insertions(+), 256 deletions(-)
 create mode 100644 bridges/primitives/chain-polkadot-bulletin/Cargo.toml
 create mode 100644 bridges/primitives/chain-polkadot-bulletin/src/lib.rs

diff --git a/Cargo.lock b/Cargo.lock
index 6d079c53b0f..b3ffd4b9fb6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1654,6 +1654,23 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "bp-polkadot-bulletin"
+version = "0.1.0"
+dependencies = [
+ "bp-header-chain",
+ "bp-messages",
+ "bp-polkadot-core",
+ "bp-runtime",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-api",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "bp-polkadot-core"
 version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 7edc28daf76..9c936024e5f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,6 +23,7 @@ members = [
 	"bridges/primitives/chain-bridge-hub-wococo",
 	"bridges/primitives/chain-kusama",
 	"bridges/primitives/chain-polkadot",
+	"bridges/primitives/chain-polkadot-bulletin",
 	"bridges/primitives/chain-rococo",
 	"bridges/primitives/chain-wococo",
 	"bridges/primitives/header-chain",
diff --git a/bridges/bin/runtime-common/src/priority_calculator.rs b/bridges/bin/runtime-common/src/priority_calculator.rs
index 3d53f9da8c2..fd103448125 100644
--- a/bridges/bin/runtime-common/src/priority_calculator.rs
+++ b/bridges/bin/runtime-common/src/priority_calculator.rs
@@ -38,7 +38,7 @@ where
 	PriorityBoostPerMessage: Get<TransactionPriority>,
 {
 	// we don't want any boost for transaction with single message => minus one
-	PriorityBoostPerMessage::get().saturating_mul(messages - 1)
+	PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1))
 }
 
 #[cfg(not(feature = "integrity-test"))]
diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/refund_relayer_extension.rs
index f0c2cbf4450..876c069dc0f 100644
--- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs
+++ b/bridges/bin/runtime-common/src/refund_relayer_extension.rs
@@ -24,8 +24,8 @@ use crate::messages_call_ext::{
 };
 use bp_messages::{LaneId, MessageNonce};
 use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
-use bp_runtime::{Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider};
-use codec::{Decode, Encode};
+use bp_runtime::{Chain, Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider};
+use codec::{Codec, Decode, Encode};
 use frame_support::{
 	dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo},
 	traits::IsSubType,
@@ -33,7 +33,8 @@ use frame_support::{
 	CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
 };
 use pallet_bridge_grandpa::{
-	CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo,
+	CallSubType as GrandpaCallSubType, Config as GrandpaConfig, SubmitFinalityProofHelper,
+	SubmitFinalityProofInfo,
 };
 use pallet_bridge_messages::Config as MessagesConfig;
 use pallet_bridge_parachains::{
@@ -96,7 +97,7 @@ where
 /// coming from this lane.
 pub trait RefundableMessagesLaneId {
 	/// The instance of the bridge messages pallet.
-	type Instance;
+	type Instance: 'static;
 	/// The messages lane id.
 	type Id: Get<LaneId>;
 }
@@ -106,6 +107,7 @@ pub struct RefundableMessagesLane<Instance, Id>(PhantomData<(Instance, Id)>);
 
 impl<Instance, Id> RefundableMessagesLaneId for RefundableMessagesLane<Instance, Id>
 where
+	Instance: 'static,
 	Id: Get<LaneId>,
 {
 	type Instance = Instance;
@@ -165,7 +167,11 @@ pub enum CallInfo {
 		SubmitParachainHeadsInfo,
 		MessagesCallInfo,
 	),
+	/// Relay chain finality + message delivery/confirmation calls.
+	RelayFinalityAndMsgs(SubmitFinalityProofInfo<RelayBlockNumber>, MessagesCallInfo),
 	/// Parachain finality + message delivery/confirmation calls.
+	///
+	/// This variant is used only when bridging with parachain.
 	ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo),
 	/// Standalone message delivery/confirmation call.
 	Msgs(MessagesCallInfo),
@@ -184,6 +190,7 @@ impl CallInfo {
 	fn submit_finality_proof_info(&self) -> Option<SubmitFinalityProofInfo<RelayBlockNumber>> {
 		match *self {
 			Self::AllFinalityAndMsgs(info, _, _) => Some(info),
+			Self::RelayFinalityAndMsgs(info, _) => Some(info),
 			_ => None,
 		}
 	}
@@ -201,6 +208,7 @@ impl CallInfo {
 	fn messages_call_info(&self) -> &MessagesCallInfo {
 		match self {
 			Self::AllFinalityAndMsgs(_, _, info) => info,
+			Self::RelayFinalityAndMsgs(_, info) => info,
 			Self::ParachainFinalityAndMsgs(_, info) => info,
 			Self::Msgs(info) => info,
 		}
@@ -209,7 +217,7 @@ impl CallInfo {
 
 /// The actions on relayer account that need to be performed because of his actions.
 #[derive(RuntimeDebug, PartialEq)]
-enum RelayerAccountAction<AccountId, Reward> {
+pub enum RelayerAccountAction<AccountId, Reward> {
 	/// Do nothing with relayer account.
 	None,
 	/// Reward the relayer.
@@ -218,121 +226,60 @@ enum RelayerAccountAction<AccountId, Reward> {
 	Slash(AccountId, RewardsAccountParams),
 }
 
-/// Signed extension that refunds a relayer for new messages coming from a parachain.
-///
-/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
-/// with message delivery transaction. Batch may deliver either both relay chain header and
-/// parachain head, or just parachain head. Corresponding headers must be used in messages
-/// proof verification.
-///
-/// Extension does not refund transaction tip due to security reasons.
-#[derive(
-	DefaultNoBound,
-	CloneNoBound,
-	Decode,
-	Encode,
-	EqNoBound,
-	PartialEqNoBound,
-	RuntimeDebugNoBound,
-	TypeInfo,
-)]
-#[scale_info(skip_type_params(Runtime, Para, Msgs, Refund, Priority, Id))]
-pub struct RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>(
-	PhantomData<(
-		// runtime with `frame-utility`, `pallet-bridge-grandpa`, `pallet-bridge-parachains`,
-		// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
-		Runtime,
-		// implementation of `RefundableParachainId` trait, which specifies the instance of
-		// the used `pallet-bridge-parachains` pallet and the bridged parachain id
-		Para,
-		// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
-		// the used `pallet-bridge-messages` pallet and the lane within this pallet
-		Msgs,
-		// implementation of the `RefundCalculator` trait, that is used to compute refund that
-		// we give to relayer for his transaction
-		Refund,
-		// getter for per-message `TransactionPriority` boost that we give to message
-		// delivery transactions
-		Priority,
-		// the runtime-unique identifier of this signed extension
-		Id,
-	)>,
-);
-
-impl<Runtime, Para, Msgs, Refund, Priority, Id>
-	RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
+/// Everything common among our refund signed extensions.
+pub trait RefundSignedExtension:
+	'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo
 where
-	Self: 'static + Send + Sync,
-	Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
-		+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
-		+ ParachainsConfig<Para::Instance>
-		+ MessagesConfig<Msgs::Instance>
-		+ RelayersConfig,
-	Para: RefundableParachainId,
-	Msgs: RefundableMessagesLaneId,
-	Refund: RefundCalculator<Balance = Runtime::Reward>,
-	Priority: Get<TransactionPriority>,
-	Id: StaticStrProvider,
-	CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
-		+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
-		+ GrandpaCallSubType<Runtime, Runtime::BridgesGrandpaPalletInstance>
-		+ ParachainsCallSubType<Runtime, Para::Instance>
-		+ MessagesCallSubType<Runtime, Msgs::Instance>,
+	<Self::Runtime as GrandpaConfig<Self::GrandpaInstance>>::BridgedChain:
+		Chain<BlockNumber = RelayBlockNumber>,
 {
-	fn expand_call<'a>(&self, call: &'a CallOf<Runtime>) -> Vec<&'a CallOf<Runtime>> {
-		match call.is_sub_type() {
-			Some(UtilityCall::<Runtime>::batch_all { ref calls }) if calls.len() <= 3 =>
-				calls.iter().collect(),
-			Some(_) => vec![],
-			None => vec![call],
-		}
-	}
-
+	/// This chain runtime.
+	type Runtime: UtilityConfig<RuntimeCall = CallOf<Self::Runtime>>
+		+ GrandpaConfig<Self::GrandpaInstance>
+		+ MessagesConfig<<Self::Msgs as RefundableMessagesLaneId>::Instance>
+		+ RelayersConfig;
+	/// Grandpa pallet reference.
+	type GrandpaInstance: 'static;
+	/// Messages pallet and lane reference.
+	type Msgs: RefundableMessagesLaneId;
+	/// Refund amount calculator.
+	type Refund: RefundCalculator<Balance = <Self::Runtime as RelayersConfig>::Reward>;
+	/// Priority boost calculator.
+	type Priority: Get<TransactionPriority>;
+	/// Signed extension unique identifier.
+	type Id: StaticStrProvider;
+
+	/// Unpack batch runtime call.
+	fn expand_call(call: &CallOf<Self::Runtime>) -> Vec<&CallOf<Self::Runtime>>;
+
+	/// Given runtime call, check if it has supported format. Additionally, check if any of
+	/// (optionally batched) calls are obsolete and we shall reject the transaction.
 	fn parse_and_check_for_obsolete_call(
-		&self,
-		call: &CallOf<Runtime>,
-	) -> Result<Option<CallInfo>, TransactionValidityError> {
-		let calls = self.expand_call(call);
-		let total_calls = calls.len();
-		let mut calls = calls.into_iter().map(Self::check_obsolete_call).rev();
+		call: &CallOf<Self::Runtime>,
+	) -> Result<Option<CallInfo>, TransactionValidityError>;
 
-		let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
-		let para_finality_call = calls
-			.next()
-			.transpose()?
-			.and_then(|c| c.submit_parachain_heads_info_for(Para::Id::get()));
-		let relay_finality_call =
-			calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
+	/// Check if parsed call is already obsolete.
+	fn check_obsolete_parsed_call(
+		call: &CallOf<Self::Runtime>,
+	) -> Result<&CallOf<Self::Runtime>, TransactionValidityError>;
 
-		Ok(match (total_calls, relay_finality_call, para_finality_call, msgs_call) {
-			(3, Some(relay_finality_call), Some(para_finality_call), Some(msgs_call)) => Some(
-				CallInfo::AllFinalityAndMsgs(relay_finality_call, para_finality_call, msgs_call),
-			),
-			(2, None, Some(para_finality_call), Some(msgs_call)) =>
-				Some(CallInfo::ParachainFinalityAndMsgs(para_finality_call, msgs_call)),
-			(1, None, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
-			_ => None,
-		})
-	}
-
-	fn check_obsolete_call(
-		call: &CallOf<Runtime>,
-	) -> Result<&CallOf<Runtime>, TransactionValidityError> {
-		call.check_obsolete_submit_finality_proof()?;
-		call.check_obsolete_submit_parachain_heads()?;
-		call.check_obsolete_call()?;
-		Ok(call)
-	}
+	/// Called from post-dispatch and shall perform additional checks (apart from relay
+	/// chain finality and messages transaction finality) of given call result.
+	fn additional_call_result_check(
+		relayer: &AccountIdOf<Self::Runtime>,
+		call_info: &CallInfo,
+	) -> bool;
 
 	/// Given post-dispatch information, analyze the outcome of relayer call and return
 	/// actions that need to be performed on relayer account.
 	fn analyze_call_result(
-		pre: Option<Option<PreDispatchData<Runtime::AccountId>>>,
+		pre: Option<Option<PreDispatchData<AccountIdOf<Self::Runtime>>>>,
 		info: &DispatchInfo,
 		post_info: &PostDispatchInfo,
 		len: usize,
 		result: &DispatchResult,
-	) -> RelayerAccountAction<AccountIdOf<Runtime>, Runtime::Reward> {
+	) -> RelayerAccountAction<AccountIdOf<Self::Runtime>, <Self::Runtime as RelayersConfig>::Reward>
+	{
 		let mut extra_weight = Weight::zero();
 		let mut extra_size = 0;
 
@@ -344,15 +291,18 @@ where
 
 		// now we know that the relayer either needs to be rewarded, or slashed
 		// => let's prepare the correspondent account that pays reward/receives slashed amount
-		let reward_account_params = RewardsAccountParams::new(
-			Msgs::Id::get(),
-			Runtime::BridgedChainId::get(),
-			if call_info.is_receive_messages_proof_call() {
-				RewardsAccountOwner::ThisChain
-			} else {
-				RewardsAccountOwner::BridgedChain
-			},
-		);
+		let reward_account_params =
+			RewardsAccountParams::new(
+				<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
+				<Self::Runtime as MessagesConfig<
+					<Self::Msgs as RefundableMessagesLaneId>::Instance,
+				>>::BridgedChainId::get(),
+				if call_info.is_receive_messages_proof_call() {
+					RewardsAccountOwner::ThisChain
+				} else {
+					RewardsAccountOwner::BridgedChain
+				},
+			);
 
 		// prepare return value for the case if the call has failed or it has not caused
 		// expected side effects (e.g. not all messages have been accepted)
@@ -376,10 +326,9 @@ where
 		if let Err(e) = result {
 			log::trace!(
 				target: "runtime::bridge",
-				"{} from parachain {} via {:?}: relayer {:?} has submitted invalid messages transaction: {:?}",
-				Self::IDENTIFIER,
-				Para::Id::get(),
-				Msgs::Id::get(),
+				"{} via {:?}: relayer {:?} has submitted invalid messages transaction: {:?}",
+				Self::Id::STR,
+				<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
 				relayer,
 				e,
 			);
@@ -388,19 +337,18 @@ where
 
 		// check if relay chain state has been updated
 		if let Some(finality_proof_info) = call_info.submit_finality_proof_info() {
-			if !SubmitFinalityProofHelper::<Runtime, Runtime::BridgesGrandpaPalletInstance>::was_successful(
+			if !SubmitFinalityProofHelper::<Self::Runtime, Self::GrandpaInstance>::was_successful(
 				finality_proof_info.block_number,
 			) {
 				// we only refund relayer if all calls have updated chain state
 				log::trace!(
 					target: "runtime::bridge",
-					"{} from parachain {} via {:?}: relayer {:?} has submitted invalid relay chain finality proof",
-					Self::IDENTIFIER,
-					Para::Id::get(),
-					Msgs::Id::get(),
+					"{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof",
+					Self::Id::STR,
+					<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
 					relayer,
 				);
-				return slash_relayer_if_delivery_result;
+				return slash_relayer_if_delivery_result
 			}
 
 			// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
@@ -416,39 +364,25 @@ where
 			extra_size = finality_proof_info.extra_size;
 		}
 
-		// check if parachain state has been updated
-		if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
-			if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful(
-				para_proof_info,
-			) {
-				// we only refund relayer if all calls have updated chain state
-				log::trace!(
-					target: "runtime::bridge",
-					"{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof",
-					Self::IDENTIFIER,
-					Para::Id::get(),
-					Msgs::Id::get(),
-					relayer,
-				);
-				return slash_relayer_if_delivery_result
-			}
-		}
-
 		// Check if the `ReceiveMessagesProof` call delivered at least some of the messages that
 		// it contained. If this happens, we consider the transaction "helpful" and refund it.
 		let msgs_call_info = call_info.messages_call_info();
-		if !MessagesCallHelper::<Runtime, Msgs::Instance>::was_successful(msgs_call_info) {
+		if !MessagesCallHelper::<Self::Runtime, <Self::Msgs as RefundableMessagesLaneId>::Instance>::was_successful(msgs_call_info) {
 			log::trace!(
 				target: "runtime::bridge",
-				"{} from parachain {} via {:?}: relayer {:?} has submitted invalid messages call",
-				Self::IDENTIFIER,
-				Para::Id::get(),
-				Msgs::Id::get(),
+				"{} via {:?}: relayer {:?} has submitted invalid messages call",
+				Self::Id::STR,
+				<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
 				relayer,
 			);
 			return slash_relayer_if_delivery_result
 		}
 
+		// do additional check
+		if !Self::additional_call_result_check(&relayer, &call_info) {
+			return slash_relayer_if_delivery_result
+		}
+
 		// regarding the tip - refund that happens here (at this side of the bridge) isn't the whole
 		// relayer compensation. He'll receive some amount at the other side of the bridge. It shall
 		// (in theory) cover the tip there. Otherwise, if we'll be compensating tip here, some
@@ -464,14 +398,14 @@ where
 		// let's also replace the weight of slashing relayer with the weight of rewarding relayer
 		if call_info.is_receive_messages_proof_call() {
 			post_info_weight = post_info_weight.saturating_sub(
-				<Runtime as RelayersConfig>::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(),
+				<Self::Runtime as RelayersConfig>::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(),
 			);
 		}
 
 		// compute the relayer refund
 		let mut post_info = *post_info;
 		post_info.actual_weight = Some(post_info_weight);
-		let refund = Refund::compute_refund(info, &post_info, post_info_len, tip);
+		let refund = Self::Refund::compute_refund(info, &post_info, post_info_len, tip);
 
 		// we can finally reward relayer
 		RelayerAccountAction::Reward(relayer, reward_account_params, refund)
@@ -497,7 +431,11 @@ where
 		let bundled_messages = parsed_call.messages_call_info().bundled_messages().saturating_len();
 
 		// a quick check to avoid invalid high-priority transactions
-		if bundled_messages > Runtime::MaxUnconfirmedMessagesAtInboundLane::get() {
+		let max_unconfirmed_messages_in_confirmation_tx = <Self::Runtime as MessagesConfig<
+			<Self::Msgs as RefundableMessagesLaneId>::Instance,
+		>>::MaxUnconfirmedMessagesAtInboundLane::get(
+		);
+		if bundled_messages > max_unconfirmed_messages_in_confirmation_tx {
 			return None
 		}
 
@@ -505,31 +443,37 @@ where
 	}
 }
 
-impl<Runtime, Para, Msgs, Refund, Priority, Id> SignedExtension
-	for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
+/// Adapter that allow implementing `sp_runtime::traits::SignedExtension` for any
+/// `RefundSignedExtension`.
+#[derive(
+	DefaultNoBound,
+	CloneNoBound,
+	Decode,
+	Encode,
+	EqNoBound,
+	PartialEqNoBound,
+	RuntimeDebugNoBound,
+	TypeInfo,
+)]
+pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T)
 where
-	Self: 'static + Send + Sync,
-	Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
-		+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
-		+ ParachainsConfig<Para::Instance>
-		+ MessagesConfig<Msgs::Instance>
-		+ RelayersConfig,
-	Para: RefundableParachainId,
-	Msgs: RefundableMessagesLaneId,
-	Refund: RefundCalculator<Balance = Runtime::Reward>,
-	Priority: Get<TransactionPriority>,
-	Id: StaticStrProvider,
-	CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
-		+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
-		+ GrandpaCallSubType<Runtime, Runtime::BridgesGrandpaPalletInstance>
-		+ ParachainsCallSubType<Runtime, Para::Instance>
-		+ MessagesCallSubType<Runtime, Msgs::Instance>,
+	<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
+		Chain<BlockNumber = RelayBlockNumber>;
+
+impl<T: RefundSignedExtension> SignedExtension for RefundSignedExtensionAdapter<T>
+where
+	<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
+		Chain<BlockNumber = RelayBlockNumber>,
+	CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+		+ IsSubType<CallableCallFor<UtilityPallet<T::Runtime>, T::Runtime>>
+		+ GrandpaCallSubType<T::Runtime, T::GrandpaInstance>
+		+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
 {
-	const IDENTIFIER: &'static str = Id::STR;
-	type AccountId = Runtime::AccountId;
-	type Call = CallOf<Runtime>;
+	const IDENTIFIER: &'static str = T::Id::STR;
+	type AccountId = AccountIdOf<T::Runtime>;
+	type Call = CallOf<T::Runtime>;
 	type AdditionalSigned = ();
-	type Pre = Option<PreDispatchData<Runtime::AccountId>>;
+	type Pre = Option<PreDispatchData<AccountIdOf<T::Runtime>>>;
 
 	fn additional_signed(&self) -> Result<(), TransactionValidityError> {
 		Ok(())
@@ -547,34 +491,32 @@ where
 		// we're not calling `validate` from `pre_dispatch` directly because of performance
 		// reasons, so if you're adding some code that may fail here, please check if it needs
 		// to be added to the `pre_dispatch` as well
-		let parsed_call = self.parse_and_check_for_obsolete_call(call)?;
+		let parsed_call = T::parse_and_check_for_obsolete_call(call)?;
 
 		// the following code just plays with transaction priority and never returns an error
 
 		// we only boost priority of presumably correct message delivery transactions
-		let bundled_messages = match Self::bundled_messages_for_priority_boost(parsed_call.as_ref())
-		{
+		let bundled_messages = match T::bundled_messages_for_priority_boost(parsed_call.as_ref()) {
 			Some(bundled_messages) => bundled_messages,
 			None => return Ok(Default::default()),
 		};
 
 		// we only boost priority if relayer has staked required balance
-		if !RelayersPallet::<Runtime>::is_registration_active(who) {
+		if !RelayersPallet::<T::Runtime>::is_registration_active(who) {
 			return Ok(Default::default())
 		}
 
 		// compute priority boost
 		let priority_boost =
-			crate::priority_calculator::compute_priority_boost::<Priority>(bundled_messages);
+			crate::priority_calculator::compute_priority_boost::<T::Priority>(bundled_messages);
 		let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost);
 
 		log::trace!(
 			target: "runtime::bridge",
-			"{} from parachain {} via {:?} has boosted priority of message delivery transaction \
+			"{} via {:?} has boosted priority of message delivery transaction \
 			of relayer {:?}: {} messages -> {} priority",
 			Self::IDENTIFIER,
-			Para::Id::get(),
-			Msgs::Id::get(),
+			<T::Msgs as RefundableMessagesLaneId>::Id::get(),
 			who,
 			bundled_messages,
 			priority_boost,
@@ -591,54 +533,294 @@ where
 		_len: usize,
 	) -> Result<Self::Pre, TransactionValidityError> {
 		// this is a relevant piece of `validate` that we need here (in `pre_dispatch`)
-		let parsed_call = self.parse_and_check_for_obsolete_call(call)?;
+		let parsed_call = T::parse_and_check_for_obsolete_call(call)?;
 
 		Ok(parsed_call.map(|call_info| {
 			log::trace!(
 				target: "runtime::bridge",
-				"{} from parachain {} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
+				"{} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
 				Self::IDENTIFIER,
-				Para::Id::get(),
-				Msgs::Id::get(),
+				<T::Msgs as RefundableMessagesLaneId>::Id::get(),
 				call_info,
 			);
 			PreDispatchData { relayer: who.clone(), call_info }
 		}))
 	}
 
-	fn post_dispatch(
-		pre: Option<Self::Pre>,
-		info: &DispatchInfoOf<Self::Call>,
-		post_info: &PostDispatchInfoOf<Self::Call>,
-		len: usize,
-		result: &DispatchResult,
-	) -> Result<(), TransactionValidityError> {
-		let call_result = Self::analyze_call_result(pre, info, post_info, len, result);
+	fn post_dispatch(
+		pre: Option<Self::Pre>,
+		info: &DispatchInfoOf<Self::Call>,
+		post_info: &PostDispatchInfoOf<Self::Call>,
+		len: usize,
+		result: &DispatchResult,
+	) -> Result<(), TransactionValidityError> {
+		let call_result = T::analyze_call_result(pre, info, post_info, len, result);
+
+		match call_result {
+			RelayerAccountAction::None => (),
+			RelayerAccountAction::Reward(relayer, reward_account, reward) => {
+				RelayersPallet::<T::Runtime>::register_relayer_reward(
+					reward_account,
+					&relayer,
+					reward,
+				);
+
+				log::trace!(
+					target: "runtime::bridge",
+					"{} via {:?} has registered reward: {:?} for {:?}",
+					Self::IDENTIFIER,
+					<T::Msgs as RefundableMessagesLaneId>::Id::get(),
+					reward,
+					relayer,
+				);
+			},
+			RelayerAccountAction::Slash(relayer, slash_account) =>
+				RelayersPallet::<T::Runtime>::slash_and_deregister(&relayer, slash_account),
+		}
+
+		Ok(())
+	}
+}
+
+/// Signed extension that refunds a relayer for new messages coming from a parachain.
+///
+/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
+/// with message delivery transaction. Batch may deliver either both relay chain header and
+/// parachain head, or just parachain head. Corresponding headers must be used in messages
+/// proof verification.
+///
+/// Extension does not refund transaction tip due to security reasons.
+#[derive(
+	DefaultNoBound,
+	CloneNoBound,
+	Decode,
+	Encode,
+	EqNoBound,
+	PartialEqNoBound,
+	RuntimeDebugNoBound,
+	TypeInfo,
+)]
+#[scale_info(skip_type_params(Runtime, Para, Msgs, Refund, Priority, Id))]
+pub struct RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>(
+	PhantomData<(
+		// runtime with `frame-utility`, `pallet-bridge-grandpa`, `pallet-bridge-parachains`,
+		// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
+		Runtime,
+		// implementation of `RefundableParachainId` trait, which specifies the instance of
+		// the used `pallet-bridge-parachains` pallet and the bridged parachain id
+		Para,
+		// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
+		// the used `pallet-bridge-messages` pallet and the lane within this pallet
+		Msgs,
+		// implementation of the `RefundCalculator` trait, that is used to compute refund that
+		// we give to relayer for his transaction
+		Refund,
+		// getter for per-message `TransactionPriority` boost that we give to message
+		// delivery transactions
+		Priority,
+		// the runtime-unique identifier of this signed extension
+		Id,
+	)>,
+);
+
+impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundSignedExtension
+	for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
+where
+	Self: 'static + Send + Sync,
+	Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
+		+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
+		+ ParachainsConfig<Para::Instance>
+		+ MessagesConfig<Msgs::Instance>
+		+ RelayersConfig,
+	Para: RefundableParachainId,
+	Msgs: RefundableMessagesLaneId,
+	Refund: RefundCalculator<Balance = Runtime::Reward>,
+	Priority: Get<TransactionPriority>,
+	Id: StaticStrProvider,
+	CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+		+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
+		+ GrandpaCallSubType<Runtime, Runtime::BridgesGrandpaPalletInstance>
+		+ ParachainsCallSubType<Runtime, Para::Instance>
+		+ MessagesCallSubType<Runtime, Msgs::Instance>,
+{
+	type Runtime = Runtime;
+	type GrandpaInstance = Runtime::BridgesGrandpaPalletInstance;
+	type Msgs = Msgs;
+	type Refund = Refund;
+	type Priority = Priority;
+	type Id = Id;
+
+	fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
+		match call.is_sub_type() {
+			Some(UtilityCall::<Runtime>::batch_all { ref calls }) if calls.len() <= 3 =>
+				calls.iter().collect(),
+			Some(_) => vec![],
+			None => vec![call],
+		}
+	}
+
+	fn parse_and_check_for_obsolete_call(
+		call: &CallOf<Runtime>,
+	) -> Result<Option<CallInfo>, TransactionValidityError> {
+		let calls = Self::expand_call(call);
+		let total_calls = calls.len();
+		let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
+
+		let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
+		let para_finality_call = calls
+			.next()
+			.transpose()?
+			.and_then(|c| c.submit_parachain_heads_info_for(Para::Id::get()));
+		let relay_finality_call =
+			calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
+
+		Ok(match (total_calls, relay_finality_call, para_finality_call, msgs_call) {
+			(3, Some(relay_finality_call), Some(para_finality_call), Some(msgs_call)) => Some(
+				CallInfo::AllFinalityAndMsgs(relay_finality_call, para_finality_call, msgs_call),
+			),
+			(2, None, Some(para_finality_call), Some(msgs_call)) =>
+				Some(CallInfo::ParachainFinalityAndMsgs(para_finality_call, msgs_call)),
+			(1, None, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
+			_ => None,
+		})
+	}
+
+	fn check_obsolete_parsed_call(
+		call: &CallOf<Runtime>,
+	) -> Result<&CallOf<Runtime>, TransactionValidityError> {
+		call.check_obsolete_submit_finality_proof()?;
+		call.check_obsolete_submit_parachain_heads()?;
+		call.check_obsolete_call()?;
+		Ok(call)
+	}
+
+	fn additional_call_result_check(relayer: &Runtime::AccountId, call_info: &CallInfo) -> bool {
+		// check if parachain state has been updated
+		if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
+			if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful(
+				para_proof_info,
+			) {
+				// we only refund relayer if all calls have updated chain state
+				log::trace!(
+					target: "runtime::bridge",
+					"{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof",
+					Id::STR,
+					Para::Id::get(),
+					Msgs::Id::get(),
+					relayer,
+				);
+				return false
+			}
+		}
+
+		true
+	}
+}
+
+/// Signed extension that refunds a relayer for new messages coming from a standalone (GRANDPA)
+/// chain.
+///
+/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
+/// with message delivery transaction. Batch may deliver either both relay chain header and
+/// parachain head, or just parachain head. Corresponding headers must be used in messages
+/// proof verification.
+///
+/// Extension does not refund transaction tip due to security reasons.
+#[derive(
+	DefaultNoBound,
+	CloneNoBound,
+	Decode,
+	Encode,
+	EqNoBound,
+	PartialEqNoBound,
+	RuntimeDebugNoBound,
+	TypeInfo,
+)]
+#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))]
+pub struct RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id>(
+	PhantomData<(
+		// runtime with `frame-utility`, `pallet-bridge-grandpa`,
+		// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
+		Runtime,
+		// bridge GRANDPA pallet instance, used to track bridged chain state
+		GrandpaInstance,
+		// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
+		// the used `pallet-bridge-messages` pallet and the lane within this pallet
+		Msgs,
+		// implementation of the `RefundCalculator` trait, that is used to compute refund that
+		// we give to relayer for his transaction
+		Refund,
+		// getter for per-message `TransactionPriority` boost that we give to message
+		// delivery transactions
+		Priority,
+		// the runtime-unique identifier of this signed extension
+		Id,
+	)>,
+);
+
+impl<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id> RefundSignedExtension
+	for RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id>
+where
+	Self: 'static + Send + Sync,
+	Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
+		+ BoundedBridgeGrandpaConfig<GrandpaInstance>
+		+ MessagesConfig<Msgs::Instance>
+		+ RelayersConfig,
+	GrandpaInstance: 'static,
+	Msgs: RefundableMessagesLaneId,
+	Refund: RefundCalculator<Balance = Runtime::Reward>,
+	Priority: Get<TransactionPriority>,
+	Id: StaticStrProvider,
+	CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+		+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
+		+ GrandpaCallSubType<Runtime, GrandpaInstance>
+		+ MessagesCallSubType<Runtime, Msgs::Instance>,
+{
+	type Runtime = Runtime;
+	type GrandpaInstance = GrandpaInstance;
+	type Msgs = Msgs;
+	type Refund = Refund;
+	type Priority = Priority;
+	type Id = Id;
+
+	fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
+		match call.is_sub_type() {
+			Some(UtilityCall::<Runtime>::batch_all { ref calls }) if calls.len() <= 2 =>
+				calls.iter().collect(),
+			Some(_) => vec![],
+			None => vec![call],
+		}
+	}
+
+	fn parse_and_check_for_obsolete_call(
+		call: &CallOf<Runtime>,
+	) -> Result<Option<CallInfo>, TransactionValidityError> {
+		let calls = Self::expand_call(call);
+		let total_calls = calls.len();
+		let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
 
-		match call_result {
-			RelayerAccountAction::None => (),
-			RelayerAccountAction::Reward(relayer, reward_account, reward) => {
-				RelayersPallet::<Runtime>::register_relayer_reward(
-					reward_account,
-					&relayer,
-					reward,
-				);
+		let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
+		let relay_finality_call =
+			calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
 
-				log::trace!(
-					target: "runtime::bridge",
-					"{} from parachain {} via {:?} has registered reward: {:?} for {:?}",
-					Self::IDENTIFIER,
-					Para::Id::get(),
-					Msgs::Id::get(),
-					reward,
-					relayer,
-				);
-			},
-			RelayerAccountAction::Slash(relayer, slash_account) =>
-				RelayersPallet::<Runtime>::slash_and_deregister(&relayer, slash_account),
-		}
+		Ok(match (total_calls, relay_finality_call, msgs_call) {
+			(2, Some(relay_finality_call), Some(msgs_call)) =>
+				Some(CallInfo::RelayFinalityAndMsgs(relay_finality_call, msgs_call)),
+			(1, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
+			_ => None,
+		})
+	}
 
-		Ok(())
+	fn check_obsolete_parsed_call(
+		call: &CallOf<Runtime>,
+	) -> Result<&CallOf<Runtime>, TransactionValidityError> {
+		call.check_obsolete_submit_finality_proof()?;
+		call.check_obsolete_call()?;
+		Ok(call)
+	}
+
+	fn additional_call_result_check(_relayer: &Runtime::AccountId, _call_info: &CallInfo) -> bool {
+		true
 	}
 }
 
@@ -655,7 +837,10 @@ mod tests {
 		},
 		mock::*,
 	};
-	use bp_messages::{InboundLaneData, MessageNonce, OutboundLaneData, UnrewardedRelayersState};
+	use bp_messages::{
+		DeliveredMessages, InboundLaneData, MessageNonce, OutboundLaneData, UnrewardedRelayer,
+		UnrewardedRelayersState,
+	};
 	use bp_parachains::{BestParaHeadHash, ParaInfo};
 	use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
 	use bp_runtime::HeaderId;
@@ -690,7 +875,17 @@ mod tests {
 	}
 
 	bp_runtime::generate_static_str_provider!(TestExtension);
-	type TestExtension = RefundBridgedParachainMessages<
+
+	type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages<
+		TestRuntime,
+		(),
+		RefundableMessagesLane<(), TestLaneId>,
+		ActualFeeRefund<TestRuntime>,
+		ConstU64<1>,
+		StrTestExtension,
+	>;
+	type TestGrandpaExtension = RefundSignedExtensionAdapter<TestGrandpaExtensionProvider>;
+	type TestExtensionProvider = RefundBridgedParachainMessages<
 		TestRuntime,
 		DefaultRefundableParachainId<(), TestParachain>,
 		RefundableMessagesLane<(), TestLaneId>,
@@ -698,6 +893,7 @@ mod tests {
 		ConstU64<1>,
 		StrTestExtension,
 	>;
+	type TestExtension = RefundSignedExtensionAdapter<TestExtensionProvider>;
 
 	fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
 		let test_stake: ThisChainBalance = TestStake::get();
@@ -849,6 +1045,30 @@ mod tests {
 		})
 	}
 
+	fn relay_finality_and_delivery_batch_call(
+		relay_header_number: RelayBlockNumber,
+		best_message: MessageNonce,
+	) -> RuntimeCall {
+		RuntimeCall::Utility(UtilityCall::batch_all {
+			calls: vec![
+				submit_relay_header_call(relay_header_number),
+				message_delivery_call(best_message),
+			],
+		})
+	}
+
+	fn relay_finality_and_confirmation_batch_call(
+		relay_header_number: RelayBlockNumber,
+		best_message: MessageNonce,
+	) -> RuntimeCall {
+		RuntimeCall::Utility(UtilityCall::batch_all {
+			calls: vec![
+				submit_relay_header_call(relay_header_number),
+				message_confirmation_call(best_message),
+			],
+		})
+	}
+
 	fn all_finality_and_delivery_batch_call(
 		relay_header_number: RelayBlockNumber,
 		parachain_head_at_relay_header_number: RelayBlockNumber,
@@ -931,6 +1151,50 @@ mod tests {
 		}
 	}
 
+	fn relay_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+		PreDispatchData {
+			relayer: relayer_account_at_this_chain(),
+			call_info: CallInfo::RelayFinalityAndMsgs(
+				SubmitFinalityProofInfo {
+					block_number: 200,
+					extra_weight: Weight::zero(),
+					extra_size: 0,
+				},
+				MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
+					base: BaseMessagesProofInfo {
+						lane_id: TEST_LANE_ID,
+						bundled_range: 101..=200,
+						best_stored_nonce: 100,
+					},
+					unrewarded_relayers: UnrewardedRelayerOccupation {
+						free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
+						free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
+					},
+				}),
+			),
+		}
+	}
+
+	fn relay_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+		PreDispatchData {
+			relayer: relayer_account_at_this_chain(),
+			call_info: CallInfo::RelayFinalityAndMsgs(
+				SubmitFinalityProofInfo {
+					block_number: 200,
+					extra_weight: Weight::zero(),
+					extra_size: 0,
+				},
+				MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
+					BaseMessagesProofInfo {
+						lane_id: TEST_LANE_ID,
+						bundled_range: 101..=200,
+						best_stored_nonce: 100,
+					},
+				)),
+			),
+		}
+	}
+
 	fn parachain_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
@@ -1013,6 +1277,7 @@ mod tests {
 	) -> PreDispatchData<ThisChainAccountId> {
 		let msg_info = match pre_dispatch_data.call_info {
 			CallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info,
+			CallInfo::RelayFinalityAndMsgs(_, ref mut info) => info,
 			CallInfo::ParachainFinalityAndMsgs(_, ref mut info) => info,
 			CallInfo::Msgs(ref mut info) => info,
 		};
@@ -1025,7 +1290,14 @@ mod tests {
 	}
 
 	fn run_validate(call: RuntimeCall) -> TransactionValidity {
-		let extension: TestExtension = RefundBridgedParachainMessages(PhantomData);
+		let extension: TestExtension =
+			RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
+		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
+	}
+
+	fn run_grandpa_validate(call: RuntimeCall) -> TransactionValidity {
+		let extension: TestGrandpaExtension =
+			RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
 		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
@@ -1039,7 +1311,16 @@ mod tests {
 	fn run_pre_dispatch(
 		call: RuntimeCall,
 	) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
-		let extension: TestExtension = RefundBridgedParachainMessages(PhantomData);
+		let extension: TestExtension =
+			RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
+		extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
+	}
+
+	fn run_grandpa_pre_dispatch(
+		call: RuntimeCall,
+	) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
+		let extension: TestGrandpaExtension =
+			RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
 		extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
@@ -1674,7 +1955,7 @@ mod tests {
 		pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
 		dispatch_result: DispatchResult,
 	) -> RelayerAccountAction<ThisChainAccountId, ThisChainBalance> {
-		TestExtension::analyze_call_result(
+		TestExtensionProvider::analyze_call_result(
 			Some(Some(pre_dispatch_data)),
 			&dispatch_info(),
 			&post_dispatch_info(),
@@ -1737,4 +2018,209 @@ mod tests {
 			);
 		});
 	}
+
+	#[test]
+	fn grandpa_ext_only_parses_valid_batches() {
+		run_test(|| {
+			initialize_environment(100, 100, 100);
+
+			// relay + parachain + message delivery calls batch is ignored
+			assert_eq!(
+				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+					&all_finality_and_delivery_batch_call(200, 200, 200)
+				),
+				Ok(None),
+			);
+
+			// relay + parachain + message confirmation calls batch is ignored
+			assert_eq!(
+				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+					&all_finality_and_confirmation_batch_call(200, 200, 200)
+				),
+				Ok(None),
+			);
+
+			// parachain + message delivery call batch is ignored
+			assert_eq!(
+				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+					&parachain_finality_and_delivery_batch_call(200, 200)
+				),
+				Ok(None),
+			);
+
+			// parachain + message confirmation call batch is ignored
+			assert_eq!(
+				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+					&parachain_finality_and_confirmation_batch_call(200, 200)
+				),
+				Ok(None),
+			);
+
+			// relay + message delivery call batch is accepted
+			assert_eq!(
+				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+					&relay_finality_and_delivery_batch_call(200, 200)
+				),
+				Ok(Some(relay_finality_pre_dispatch_data().call_info)),
+			);
+
+			// relay + message confirmation call batch is accepted
+			assert_eq!(
+				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+					&relay_finality_and_confirmation_batch_call(200, 200)
+				),
+				Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)),
+			);
+
+			// message delivery call batch is accepted
+			assert_eq!(
+				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+					&message_delivery_call(200)
+				),
+				Ok(Some(delivery_pre_dispatch_data().call_info)),
+			);
+
+			// message confirmation call batch is accepted
+			assert_eq!(
+				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+					&message_confirmation_call(200)
+				),
+				Ok(Some(confirmation_pre_dispatch_data().call_info)),
+			);
+		});
+	}
+
+	#[test]
+	fn grandpa_ext_rejects_batch_with_obsolete_relay_chain_header() {
+		run_test(|| {
+			initialize_environment(100, 100, 100);
+
+			assert_eq!(
+				run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(100, 200)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+
+			assert_eq!(
+				run_grandpa_validate(relay_finality_and_delivery_batch_call(100, 200)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+		});
+	}
+
+	#[test]
+	fn grandpa_ext_rejects_calls_with_obsolete_messages() {
+		run_test(|| {
+			initialize_environment(100, 100, 100);
+
+			assert_eq!(
+				run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 100)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+			assert_eq!(
+				run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 100)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+
+			assert_eq!(
+				run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 100)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+			assert_eq!(
+				run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 100)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+
+			assert_eq!(
+				run_grandpa_pre_dispatch(message_delivery_call(100)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+			assert_eq!(
+				run_grandpa_pre_dispatch(message_confirmation_call(100)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+
+			assert_eq!(
+				run_grandpa_validate(message_delivery_call(100)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+			assert_eq!(
+				run_grandpa_validate(message_confirmation_call(100)),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+			);
+		});
+	}
+
+	#[test]
+	fn grandpa_ext_accepts_calls_with_new_messages() {
+		run_test(|| {
+			initialize_environment(100, 100, 100);
+
+			assert_eq!(
+				run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 200)),
+				Ok(Some(relay_finality_pre_dispatch_data()),)
+			);
+			assert_eq!(
+				run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 200)),
+				Ok(Some(relay_finality_confirmation_pre_dispatch_data())),
+			);
+
+			assert_eq!(
+				run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 200)),
+				Ok(Default::default()),
+			);
+			assert_eq!(
+				run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 200)),
+				Ok(Default::default()),
+			);
+
+			assert_eq!(
+				run_grandpa_pre_dispatch(message_delivery_call(200)),
+				Ok(Some(delivery_pre_dispatch_data())),
+			);
+			assert_eq!(
+				run_grandpa_pre_dispatch(message_confirmation_call(200)),
+				Ok(Some(confirmation_pre_dispatch_data())),
+			);
+
+			assert_eq!(run_grandpa_validate(message_delivery_call(200)), Ok(Default::default()),);
+			assert_eq!(
+				run_grandpa_validate(message_confirmation_call(200)),
+				Ok(Default::default()),
+			);
+		});
+	}
+
+	#[test]
+	fn does_not_panic_on_boosting_priority_of_empty_message_delivery_transaction() {
+		run_test(|| {
+			let best_delivered_message = MaxUnconfirmedMessagesAtInboundLane::get();
+			initialize_environment(100, 100, best_delivered_message);
+
+			// register relayer so it gets priority boost
+			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
+				.unwrap();
+
+			// allow empty message delivery transactions
+			let lane_id = TestLaneId::get();
+			let in_lane_data = InboundLaneData {
+				last_confirmed_nonce: 0,
+				relayers: vec![UnrewardedRelayer {
+					relayer: relayer_account_at_bridged_chain(),
+					messages: DeliveredMessages { begin: 1, end: best_delivered_message },
+				}]
+				.into(),
+			};
+			pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
+
+			// now check that the priority of empty tx is the same as priority of 1-message tx
+			let priority_of_zero_messages_delivery =
+				run_validate(message_delivery_call(best_delivered_message)).unwrap().priority;
+			let priority_of_one_messages_delivery =
+				run_validate(message_delivery_call(best_delivered_message + 1))
+					.unwrap()
+					.priority;
+
+			assert_eq!(priority_of_zero_messages_delivery, priority_of_one_messages_delivery);
+		});
+	}
 }
diff --git a/bridges/primitives/chain-bridge-hub-kusama/src/lib.rs b/bridges/primitives/chain-bridge-hub-kusama/src/lib.rs
index 3a919648df4..66e0dad0589 100644
--- a/bridges/primitives/chain-bridge-hub-kusama/src/lib.rs
+++ b/bridges/primitives/chain-bridge-hub-kusama/src/lib.rs
@@ -29,7 +29,6 @@ use frame_support::{
 	sp_runtime::{MultiAddress, MultiSigner},
 };
 use sp_runtime::RuntimeDebug;
-use sp_std::prelude::Vec;
 
 /// BridgeHubKusama parachain.
 #[derive(RuntimeDebug)]
diff --git a/bridges/primitives/chain-bridge-hub-polkadot/src/lib.rs b/bridges/primitives/chain-bridge-hub-polkadot/src/lib.rs
index bf8d8e07c3a..c3661c1adca 100644
--- a/bridges/primitives/chain-bridge-hub-polkadot/src/lib.rs
+++ b/bridges/primitives/chain-bridge-hub-polkadot/src/lib.rs
@@ -26,7 +26,6 @@ use bp_runtime::{
 };
 use frame_support::dispatch::DispatchClass;
 use sp_runtime::RuntimeDebug;
-use sp_std::prelude::Vec;
 
 /// BridgeHubPolkadot parachain.
 #[derive(RuntimeDebug)]
diff --git a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
index b726c62ac42..a50bda23ac8 100644
--- a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
+++ b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
@@ -26,7 +26,7 @@ use bp_runtime::{
 };
 use frame_support::dispatch::DispatchClass;
 use sp_runtime::{MultiAddress, MultiSigner, RuntimeDebug};
-use sp_std::prelude::Vec;
+
 /// BridgeHubRococo parachain.
 #[derive(RuntimeDebug)]
 pub struct BridgeHubRococo;
diff --git a/bridges/primitives/chain-bridge-hub-wococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-wococo/src/lib.rs
index 5e4758645d9..ce4600f5ff3 100644
--- a/bridges/primitives/chain-bridge-hub-wococo/src/lib.rs
+++ b/bridges/primitives/chain-bridge-hub-wococo/src/lib.rs
@@ -26,7 +26,6 @@ use bp_runtime::{
 };
 use frame_support::dispatch::DispatchClass;
 use sp_runtime::RuntimeDebug;
-use sp_std::prelude::Vec;
 
 /// BridgeHubWococo parachain.
 #[derive(RuntimeDebug)]
diff --git a/bridges/primitives/chain-kusama/src/lib.rs b/bridges/primitives/chain-kusama/src/lib.rs
index 8c3fbd9c203..d5748aa132c 100644
--- a/bridges/primitives/chain-kusama/src/lib.rs
+++ b/bridges/primitives/chain-kusama/src/lib.rs
@@ -23,7 +23,6 @@ pub use bp_polkadot_core::*;
 use bp_header_chain::ChainWithGrandpa;
 use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
 use frame_support::weights::Weight;
-use sp_std::prelude::Vec;
 
 /// Kusama Chain
 pub struct Kusama;
diff --git a/bridges/primitives/chain-polkadot-bulletin/Cargo.toml b/bridges/primitives/chain-polkadot-bulletin/Cargo.toml
new file mode 100644
index 00000000000..4311aec4727
--- /dev/null
+++ b/bridges/primitives/chain-polkadot-bulletin/Cargo.toml
@@ -0,0 +1,41 @@
+[package]
+name = "bp-polkadot-bulletin"
+description = "Primitives of Polkadot Bulletin chain runtime."
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2021"
+license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] }
+scale-info = { version = "2.9.0", default-features = false, features = ["derive"] }
+
+# Bridge Dependencies
+
+bp-header-chain = { path = "../header-chain", default-features = false }
+bp-messages = { path = "../messages", default-features = false }
+bp-polkadot-core = { path = "../polkadot-core", default-features = false }
+bp-runtime = { path = "../runtime", default-features = false }
+
+# Substrate Based Dependencies
+
+frame-support = { path = "../../../substrate/frame/support", default-features = false }
+frame-system = { path = "../../../substrate/frame/system", default-features = false }
+sp-api = { path = "../../../substrate/primitives/api", default-features = false }
+sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
+sp-std = { path = "../../../substrate/primitives/std", default-features = false }
+
+[features]
+default = [ "std" ]
+std = [
+	"bp-header-chain/std",
+	"bp-messages/std",
+	"bp-polkadot-core/std",
+	"bp-runtime/std",
+	"codec/std",
+	"frame-support/std",
+	"frame-system/std",
+	"sp-api/std",
+	"sp-runtime/std",
+	"sp-std/std",
+]
diff --git a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs
new file mode 100644
index 00000000000..fcc6e90eb1b
--- /dev/null
+++ b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs
@@ -0,0 +1,215 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Polkadot Bulletin Chain primitives.
+
+#![warn(missing_docs)]
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use bp_header_chain::ChainWithGrandpa;
+use bp_messages::MessageNonce;
+use bp_runtime::{
+	decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis,
+	extensions::{
+		CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion,
+		CheckWeight, GenericSignedExtension, GenericSignedExtensionSchema,
+	},
+	Chain, TransactionEra,
+};
+use codec::{Decode, Encode};
+use frame_support::{
+	dispatch::DispatchClass,
+	parameter_types,
+	weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
+};
+use frame_system::limits;
+use scale_info::TypeInfo;
+use sp_runtime::{traits::DispatchInfoOf, transaction_validity::TransactionValidityError, Perbill};
+
+// This chain reuses most of Polkadot primitives.
+pub use bp_polkadot_core::{
+	AccountAddress, AccountId, Balance, Block, BlockNumber, Hash, Hasher, Header, Nonce, Signature,
+	SignedBlock, UncheckedExtrinsic, AVERAGE_HEADER_SIZE_IN_JUSTIFICATION,
+	EXTRA_STORAGE_PROOF_SIZE, MAX_HEADER_SIZE, REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY,
+};
+
+/// Maximal number of GRANDPA authorities at Polkadot Bulletin chain.
+pub const MAX_AUTHORITIES_COUNT: u32 = 100;
+
+/// Name of the With-Polkadot Bulletin chain GRANDPA pallet instance that is deployed at bridged
+/// chains.
+pub const WITH_POLKADOT_BULLETIN_GRANDPA_PALLET_NAME: &str = "BridgePolkadotBulletinGrandpa";
+/// Name of the With-Polkadot Bulletin chain messages pallet instance that is deployed at bridged
+/// chains.
+pub const WITH_POLKADOT_BULLETIN_MESSAGES_PALLET_NAME: &str = "BridgePolkadotBulletinMessages";
+
+// There are fewer system operations on this chain (e.g. staking, governance, etc.). Use a higher
+// percentage of the block for data storage.
+const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(90);
+
+// Re following constants - we are using the same values at Cumulus parachains. They are limited
+// by the maximal transaction weight/size. Since block limits at Bulletin Chain are larger than
+// at the Cumulus Bridgeg Hubs, we could reuse the same values.
+
+/// Maximal number of unrewarded relayer entries at inbound lane for Cumulus-based parachains.
+pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024;
+
+/// Maximal number of unconfirmed messages at inbound lane for Cumulus-based parachains.
+pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 4096;
+
+/// This signed extension is used to ensure that the chain transactions are signed by proper
+pub type ValidateSigned = GenericSignedExtensionSchema<(), ()>;
+
+/// Signed extension schema, used by Polkadot Bulletin.
+pub type SignedExtensionSchema = GenericSignedExtension<(
+	(
+		CheckNonZeroSender,
+		CheckSpecVersion,
+		CheckTxVersion,
+		CheckGenesis<Hash>,
+		CheckEra<Hash>,
+		CheckNonce<Nonce>,
+		CheckWeight,
+	),
+	ValidateSigned,
+)>;
+
+/// Signed extension, used by Polkadot Bulletin.
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
+pub struct SignedExtension(SignedExtensionSchema);
+
+impl sp_runtime::traits::SignedExtension for SignedExtension {
+	const IDENTIFIER: &'static str = "Not needed.";
+	type AccountId = ();
+	type Call = ();
+	type AdditionalSigned =
+		<SignedExtensionSchema as sp_runtime::traits::SignedExtension>::AdditionalSigned;
+	type Pre = ();
+
+	fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
+		self.0.additional_signed()
+	}
+
+	fn pre_dispatch(
+		self,
+		_who: &Self::AccountId,
+		_call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
+		_len: usize,
+	) -> Result<Self::Pre, TransactionValidityError> {
+		Ok(())
+	}
+}
+
+impl SignedExtension {
+	/// Create signed extension from its components.
+	pub fn from_params(
+		spec_version: u32,
+		transaction_version: u32,
+		era: TransactionEra<BlockNumber, Hash>,
+		genesis_hash: Hash,
+		nonce: Nonce,
+	) -> Self {
+		Self(GenericSignedExtension::new(
+			(
+				(
+					(),              // non-zero sender
+					(),              // spec version
+					(),              // tx version
+					(),              // genesis
+					era.frame_era(), // era
+					nonce.into(),    // nonce (compact encoding)
+					(),              // Check weight
+				),
+				(),
+			),
+			Some((
+				(
+					(),
+					spec_version,
+					transaction_version,
+					genesis_hash,
+					era.signed_payload(genesis_hash),
+					(),
+					(),
+				),
+				(),
+			)),
+		))
+	}
+
+	/// Return transaction nonce.
+	pub fn nonce(&self) -> Nonce {
+		let common_payload = self.0.payload.0;
+		common_payload.5 .0
+	}
+}
+
+parameter_types! {
+	/// We allow for 2 seconds of compute with a 6 second average block time.
+	pub BlockWeights: limits::BlockWeights = limits::BlockWeights::with_sensible_defaults(
+			Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX),
+			NORMAL_DISPATCH_RATIO,
+		);
+	// Note: Max transaction size is 8 MB. Set max block size to 10 MB to facilitate data storage.
+	// This is double the "normal" Relay Chain block length limit.
+	/// Maximal block length at Polkadot Bulletin chain.
+	pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio(
+		10 * 1024 * 1024,
+		NORMAL_DISPATCH_RATIO,
+	);
+}
+
+/// Polkadot Bulletin Chain declaration.
+pub struct PolkadotBulletin;
+
+impl Chain for PolkadotBulletin {
+	type BlockNumber = BlockNumber;
+	type Hash = Hash;
+	type Hasher = Hasher;
+	type Header = Header;
+
+	type AccountId = AccountId;
+	// The Bulletin Chain is a permissioned blockchain without any balances. Our `Chain` trait
+	// requires balance type, which is then used by various bridge infrastructure code. However
+	// this code is optional and we are not planning to use it in our bridge.
+	type Balance = Balance;
+	type Nonce = Nonce;
+	type Signature = Signature;
+
+	fn max_extrinsic_size() -> u32 {
+		*BlockLength::get().max.get(DispatchClass::Normal)
+	}
+
+	fn max_extrinsic_weight() -> Weight {
+		BlockWeights::get()
+			.get(DispatchClass::Normal)
+			.max_extrinsic
+			.unwrap_or(Weight::MAX)
+	}
+}
+
+impl ChainWithGrandpa for PolkadotBulletin {
+	const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_POLKADOT_BULLETIN_GRANDPA_PALLET_NAME;
+	const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT;
+	const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 =
+		REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY;
+	const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE;
+	const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION;
+}
+
+decl_bridge_finality_runtime_apis!(polkadot_bulletin, grandpa);
+decl_bridge_messages_runtime_apis!(polkadot_bulletin);
diff --git a/bridges/primitives/chain-polkadot/src/lib.rs b/bridges/primitives/chain-polkadot/src/lib.rs
index d1d6f745431..61c8ca927d8 100644
--- a/bridges/primitives/chain-polkadot/src/lib.rs
+++ b/bridges/primitives/chain-polkadot/src/lib.rs
@@ -23,7 +23,6 @@ pub use bp_polkadot_core::*;
 use bp_header_chain::ChainWithGrandpa;
 use bp_runtime::{decl_bridge_finality_runtime_apis, extensions::PrevalidateAttests, Chain};
 use frame_support::weights::Weight;
-use sp_std::prelude::Vec;
 
 /// Polkadot Chain
 pub struct Polkadot;
diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs
index 1589d14ea51..5436ad84646 100644
--- a/bridges/primitives/chain-rococo/src/lib.rs
+++ b/bridges/primitives/chain-rococo/src/lib.rs
@@ -23,7 +23,6 @@ pub use bp_polkadot_core::*;
 use bp_header_chain::ChainWithGrandpa;
 use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
 use frame_support::{parameter_types, weights::Weight};
-use sp_std::prelude::Vec;
 
 /// Rococo Chain
 pub struct Rococo;
diff --git a/bridges/primitives/chain-wococo/src/lib.rs b/bridges/primitives/chain-wococo/src/lib.rs
index 5b5bde82690..b1df65630be 100644
--- a/bridges/primitives/chain-wococo/src/lib.rs
+++ b/bridges/primitives/chain-wococo/src/lib.rs
@@ -26,7 +26,6 @@ pub use bp_rococo::{
 use bp_header_chain::ChainWithGrandpa;
 use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
 use frame_support::weights::Weight;
-use sp_std::prelude::Vec;
 
 /// Wococo Chain
 pub struct Wococo;
diff --git a/bridges/primitives/header-chain/src/justification/verification/mod.rs b/bridges/primitives/header-chain/src/justification/verification/mod.rs
index bb8aaadf327..a66fc1e0d91 100644
--- a/bridges/primitives/header-chain/src/justification/verification/mod.rs
+++ b/bridges/primitives/header-chain/src/justification/verification/mod.rs
@@ -143,6 +143,7 @@ pub enum PrecommitError {
 }
 
 /// The context needed for validating GRANDPA finality proofs.
+#[derive(RuntimeDebug)]
 pub struct JustificationVerificationContext {
 	/// The authority set used to verify the justification.
 	pub voter_set: VoterSet<AuthorityId>,
diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs
index 5caaebd42ba..e1809e14524 100644
--- a/bridges/primitives/runtime/src/chain.rs
+++ b/bridges/primitives/runtime/src/chain.rs
@@ -311,7 +311,7 @@ macro_rules! decl_bridge_finality_runtime_apis {
 						$(
 							/// Returns the justifications accepted in the current block.
 							fn [<synced_headers_ $consensus:lower _info>](
-							) -> Vec<$justification_type>;
+							) -> sp_std::vec::Vec<$justification_type>;
 						)?
 					}
 				}
@@ -360,10 +360,10 @@ macro_rules! decl_bridge_messages_runtime_apis {
 						/// If some (or all) messages are missing from the storage, they'll also will
 						/// be missing from the resulting vector. The vector is ordered by the nonce.
 						fn message_details(
-							lane: LaneId,
-							begin: MessageNonce,
-							end: MessageNonce,
-						) -> Vec<OutboundMessageDetails>;
+							lane: bp_messages::LaneId,
+							begin: bp_messages::MessageNonce,
+							end: bp_messages::MessageNonce,
+						) -> sp_std::vec::Vec<bp_messages::OutboundMessageDetails>;
 					}
 
 					/// Inbound message lane API for messages sent by this chain.
@@ -376,9 +376,9 @@ macro_rules! decl_bridge_messages_runtime_apis {
 					pub trait [<From $chain:camel InboundLaneApi>] {
 						/// Return details of given inbound messages.
 						fn message_details(
-							lane: LaneId,
-							messages: Vec<(MessagePayload, OutboundMessageDetails)>,
-						) -> Vec<InboundMessageDetails>;
+							lane: bp_messages::LaneId,
+							messages: sp_std::vec::Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>,
+						) -> sp_std::vec::Vec<bp_messages::InboundMessageDetails>;
 					}
 				}
 			}
diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs
index ece782e352b..7f4a1a030b1 100644
--- a/bridges/primitives/runtime/src/lib.rs
+++ b/bridges/primitives/runtime/src/lib.rs
@@ -74,6 +74,9 @@ pub const MILLAU_CHAIN_ID: ChainId = *b"mlau";
 /// Polkadot chain id.
 pub const POLKADOT_CHAIN_ID: ChainId = *b"pdot";
 
+/// Polkadot Bulletin chain id.
+pub const POLKADOT_BULLETIN_CHAIN_ID: ChainId = *b"pdbc";
+
 /// Kusama chain id.
 pub const KUSAMA_CHAIN_ID: ChainId = *b"ksma";
 
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs
index bc8f97ad97c..f59c9e238f5 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs
@@ -29,8 +29,8 @@ use bridge_runtime_common::{
 	},
 	messages_xcm_extension::{SenderAndLane, XcmBlobHauler, XcmBlobHaulerAdapter},
 	refund_relayer_extension::{
-		ActualFeeRefund, RefundBridgedParachainMessages, RefundableMessagesLane,
-		RefundableParachain,
+		ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter,
+		RefundableMessagesLane, RefundableParachain,
 	},
 };
 use frame_support::{parameter_types, traits::PalletInfoAccess};
@@ -136,13 +136,15 @@ impl ThisChainWithMessages for BridgeHubRococo {
 }
 
 /// Signed extension that refunds relayers that are delivering messages from the Wococo parachain.
-pub type BridgeRefundBridgeHubWococoMessages = RefundBridgedParachainMessages<
-	Runtime,
-	RefundableParachain<BridgeParachainWococoInstance, bp_bridge_hub_wococo::BridgeHubWococo>,
-	RefundableMessagesLane<WithBridgeHubWococoMessagesInstance, BridgeHubWococoMessagesLane>,
-	ActualFeeRefund<Runtime>,
-	PriorityBoostPerMessage,
-	StrBridgeRefundBridgeHubWococoMessages,
+pub type BridgeRefundBridgeHubWococoMessages = RefundSignedExtensionAdapter<
+	RefundBridgedParachainMessages<
+		Runtime,
+		RefundableParachain<BridgeParachainWococoInstance, bp_bridge_hub_wococo::BridgeHubWococo>,
+		RefundableMessagesLane<WithBridgeHubWococoMessagesInstance, BridgeHubWococoMessagesLane>,
+		ActualFeeRefund<Runtime>,
+		PriorityBoostPerMessage,
+		StrBridgeRefundBridgeHubWococoMessages,
+	>,
 >;
 bp_runtime::generate_static_str_provider!(BridgeRefundBridgeHubWococoMessages);
 
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs
index 5178b75c303..a0b16bace51 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs
@@ -29,8 +29,8 @@ use bridge_runtime_common::{
 	},
 	messages_xcm_extension::{SenderAndLane, XcmBlobHauler, XcmBlobHaulerAdapter},
 	refund_relayer_extension::{
-		ActualFeeRefund, RefundBridgedParachainMessages, RefundableMessagesLane,
-		RefundableParachain,
+		ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter,
+		RefundableMessagesLane, RefundableParachain,
 	},
 };
 use frame_support::{parameter_types, traits::PalletInfoAccess};
@@ -136,13 +136,15 @@ impl ThisChainWithMessages for BridgeHubWococo {
 }
 
 /// Signed extension that refunds relayers that are delivering messages from the Rococo parachain.
-pub type BridgeRefundBridgeHubRococoMessages = RefundBridgedParachainMessages<
-	Runtime,
-	RefundableParachain<BridgeParachainRococoInstance, bp_bridge_hub_rococo::BridgeHubRococo>,
-	RefundableMessagesLane<WithBridgeHubRococoMessagesInstance, BridgeHubRococoMessagesLane>,
-	ActualFeeRefund<Runtime>,
-	PriorityBoostPerMessage,
-	StrBridgeRefundBridgeHubRococoMessages,
+pub type BridgeRefundBridgeHubRococoMessages = RefundSignedExtensionAdapter<
+	RefundBridgedParachainMessages<
+		Runtime,
+		RefundableParachain<BridgeParachainRococoInstance, bp_bridge_hub_rococo::BridgeHubRococo>,
+		RefundableMessagesLane<WithBridgeHubRococoMessagesInstance, BridgeHubRococoMessagesLane>,
+		ActualFeeRefund<Runtime>,
+		PriorityBoostPerMessage,
+		StrBridgeRefundBridgeHubRococoMessages,
+	>,
 >;
 bp_runtime::generate_static_str_provider!(BridgeRefundBridgeHubRococoMessages);
 
-- 
GitLab