From b58f0aef2d85b1a70097de4f6530c382f7c24099 Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Fri, 15 Dec 2023 10:01:49 +0300
Subject: [PATCH] Governance can halt and resume Rococo <> Wococo bridge
 pallets over XCM (#2712)

This PR adds possibility for relay chain governance to halt and resume
bridge pallets using XCM calls. Following calls are enabled over XCM for
the `root` origin: `pallet_bridge_grandpa::set_operating_mode`,
`pallet_bridge_parachains::set_operating_mode` and
`pallet_bridge_messages::set_operating_mode`.
---
 .../bridge-hub-rococo/src/xcm_config.rs       |  32 ++-
 .../bridge-hub-rococo/tests/tests.rs          |  57 ++++-
 .../bridge-hub-westend/src/xcm_config.rs      |  14 +-
 .../bridge-hub-westend/tests/tests.rs         |  30 ++-
 .../test-utils/src/test_cases/mod.rs          | 210 +++++++++++++++++-
 5 files changed, 312 insertions(+), 31 deletions(-)

diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs
index 19fb4fdea0f..f89e7b39db8 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs
@@ -19,9 +19,13 @@ use super::{
 	ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
 	TransactionByteFee, WeightToFee, XcmpQueue,
 };
-use crate::bridge_common_config::{
-	BridgeGrandpaRococoBulletinInstance, BridgeGrandpaWestendInstance, DeliveryRewardInBalance,
-	RequiredStakeForStakeAndSlash,
+use crate::{
+	bridge_common_config::{
+		BridgeGrandpaRococoBulletinInstance, BridgeGrandpaWestendInstance,
+		BridgeParachainWestendInstance, DeliveryRewardInBalance, RequiredStakeForStakeAndSlash,
+	},
+	bridge_to_bulletin_config::WithRococoBulletinMessagesInstance,
+	bridge_to_westend_config::WithBridgeHubWestendMessagesInstance,
 };
 use bp_messages::LaneId;
 use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams};
@@ -190,10 +194,30 @@ impl Contains<RuntimeCall> for SafeCallFilter {
 					Runtime,
 					BridgeGrandpaWestendInstance,
 				>::initialize { .. }) |
+				RuntimeCall::BridgeWestendGrandpa(pallet_bridge_grandpa::Call::<
+					Runtime,
+					BridgeGrandpaWestendInstance,
+				>::set_operating_mode { .. }) |
+				RuntimeCall::BridgeWestendParachains(pallet_bridge_parachains::Call::<
+					Runtime,
+					BridgeParachainWestendInstance,
+				>::set_operating_mode { .. }) |
+				RuntimeCall::BridgeWestendMessages(pallet_bridge_messages::Call::<
+					Runtime,
+					WithBridgeHubWestendMessagesInstance,
+				>::set_operating_mode { .. }) |
 				RuntimeCall::BridgePolkadotBulletinGrandpa(pallet_bridge_grandpa::Call::<
 					Runtime,
 					BridgeGrandpaRococoBulletinInstance,
-				>::initialize { .. })
+				>::initialize { .. }) |
+				RuntimeCall::BridgePolkadotBulletinGrandpa(pallet_bridge_grandpa::Call::<
+					Runtime,
+					BridgeGrandpaRococoBulletinInstance,
+				>::set_operating_mode { .. }) |
+				RuntimeCall::BridgePolkadotBulletinMessages(pallet_bridge_messages::Call::<
+					Runtime,
+					WithRococoBulletinMessagesInstance,
+				>::set_operating_mode { .. })
 		)
 	}
 }
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
index b5e4552c452..0ba5fb37aef 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
@@ -152,11 +152,34 @@ mod bridge_hub_westend_tests {
 		bridge_hub_test_utils::test_cases::initialize_bridge_by_governance_works::<
 			Runtime,
 			BridgeGrandpaWestendInstance,
-		>(
-			collator_session_keys(),
-			bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID,
-			Box::new(|call| RuntimeCall::BridgeWestendGrandpa(call).encode()),
-		)
+		>(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID)
+	}
+
+	#[test]
+	fn change_bridge_grandpa_pallet_mode_by_governance_works() {
+		// for Westend finality
+		bridge_hub_test_utils::test_cases::change_bridge_grandpa_pallet_mode_by_governance_works::<
+			Runtime,
+			BridgeGrandpaWestendInstance,
+		>(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID)
+	}
+
+	#[test]
+	fn change_bridge_parachains_pallet_mode_by_governance_works() {
+		// for Westend finality
+		bridge_hub_test_utils::test_cases::change_bridge_parachains_pallet_mode_by_governance_works::<
+			Runtime,
+			BridgeParachainWestendInstance,
+		>(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID)
+	}
+
+	#[test]
+	fn change_bridge_messages_pallet_mode_by_governance_works() {
+		// for Westend finality
+		bridge_hub_test_utils::test_cases::change_bridge_messages_pallet_mode_by_governance_works::<
+			Runtime,
+			WithBridgeHubWestendMessagesInstance,
+		>(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID)
 	}
 
 	#[test]
@@ -365,11 +388,25 @@ mod bridge_hub_bulletin_tests {
 		bridge_hub_test_utils::test_cases::initialize_bridge_by_governance_works::<
 			Runtime,
 			BridgeGrandpaRococoBulletinInstance,
-		>(
-			collator_session_keys(),
-			bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID,
-			Box::new(|call| RuntimeCall::BridgePolkadotBulletinGrandpa(call).encode()),
-		)
+		>(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID)
+	}
+
+	#[test]
+	fn change_bridge_grandpa_pallet_mode_by_governance_works() {
+		// for Bulletin finality
+		bridge_hub_test_utils::test_cases::change_bridge_grandpa_pallet_mode_by_governance_works::<
+			Runtime,
+			BridgeGrandpaRococoBulletinInstance,
+		>(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID)
+	}
+
+	#[test]
+	fn change_bridge_messages_pallet_mode_by_governance_works() {
+		// for Bulletin finality
+		bridge_hub_test_utils::test_cases::change_bridge_messages_pallet_mode_by_governance_works::<
+			Runtime,
+			WithRococoBulletinMessagesInstance,
+		>(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID)
 	}
 
 	#[test]
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs
index 26dc1f62950..d112328723d 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs
@@ -176,7 +176,19 @@ impl Contains<RuntimeCall> for SafeCallFilter {
 				RuntimeCall::BridgeRococoGrandpa(pallet_bridge_grandpa::Call::<
 					Runtime,
 					crate::bridge_to_rococo_config::BridgeGrandpaRococoInstance,
-				>::initialize { .. })
+				>::initialize { .. }) |
+				RuntimeCall::BridgeRococoGrandpa(pallet_bridge_grandpa::Call::<
+					Runtime,
+					crate::bridge_to_rococo_config::BridgeGrandpaRococoInstance,
+				>::set_operating_mode { .. }) |
+				RuntimeCall::BridgeRococoParachains(pallet_bridge_parachains::Call::<
+					Runtime,
+					crate::bridge_to_rococo_config::BridgeParachainRococoInstance,
+				>::set_operating_mode { .. }) |
+				RuntimeCall::BridgeRococoMessages(pallet_bridge_messages::Call::<
+					Runtime,
+					crate::bridge_to_rococo_config::WithBridgeHubRococoMessagesInstance,
+				>::set_operating_mode { .. })
 		)
 	}
 }
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
index ffa2fb1cfdc..9a7f13f14c3 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
@@ -123,11 +123,31 @@ fn initialize_bridge_by_governance_works() {
 	bridge_hub_test_utils::test_cases::initialize_bridge_by_governance_works::<
 		Runtime,
 		BridgeGrandpaRococoInstance,
-	>(
-		collator_session_keys(),
-		bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
-		Box::new(|call| RuntimeCall::BridgeRococoGrandpa(call).encode()),
-	)
+	>(collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID)
+}
+
+#[test]
+fn change_bridge_grandpa_pallet_mode_by_governance_works() {
+	bridge_hub_test_utils::test_cases::change_bridge_grandpa_pallet_mode_by_governance_works::<
+		Runtime,
+		BridgeGrandpaRococoInstance,
+	>(collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID)
+}
+
+#[test]
+fn change_bridge_parachains_pallet_mode_by_governance_works() {
+	bridge_hub_test_utils::test_cases::change_bridge_parachains_pallet_mode_by_governance_works::<
+		Runtime,
+		BridgeParachainRococoInstance,
+	>(collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID)
+}
+
+#[test]
+fn change_bridge_messages_pallet_mode_by_governance_works() {
+	bridge_hub_test_utils::test_cases::change_bridge_messages_pallet_mode_by_governance_works::<
+		Runtime,
+		WithBridgeHubRococoMessagesInstance,
+	>(collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID)
 }
 
 #[test]
diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs
index cc347ecf30d..c12a12548c7 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs
@@ -29,8 +29,9 @@ use crate::test_data;
 use asset_test_utils::BasicParachainRuntime;
 use bp_messages::{
 	target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
-	LaneId, MessageKey, OutboundLaneData,
+	LaneId, MessageKey, MessagesOperatingMode, OutboundLaneData,
 };
+use bp_runtime::BasicOperatingMode;
 use bridge_runtime_common::messages_xcm_extension::{
 	XcmAsPlainPayload, XcmBlobMessageDispatchResult,
 };
@@ -88,13 +89,12 @@ where
 pub fn initialize_bridge_by_governance_works<Runtime, GrandpaPalletInstance>(
 	collator_session_key: CollatorSessionKeys<Runtime>,
 	runtime_para_id: u32,
-	runtime_call_encode: Box<
-		dyn Fn(pallet_bridge_grandpa::Call<Runtime, GrandpaPalletInstance>) -> Vec<u8>,
-	>,
 ) where
 	Runtime: BasicParachainRuntime + pallet_bridge_grandpa::Config<GrandpaPalletInstance>,
 	GrandpaPalletInstance: 'static,
 	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
+	<Runtime as frame_system::Config>::RuntimeCall:
+		From<pallet_bridge_grandpa::Call<Runtime, GrandpaPalletInstance>>,
 {
 	run_test::<Runtime, _>(collator_session_key, runtime_para_id, vec![], || {
 		// check mode before
@@ -104,12 +104,14 @@ pub fn initialize_bridge_by_governance_works<Runtime, GrandpaPalletInstance>(
 		);
 
 		// encode `initialize` call
-		let initialize_call = runtime_call_encode(pallet_bridge_grandpa::Call::<
-			Runtime,
-			GrandpaPalletInstance,
-		>::initialize {
-			init_data: test_data::initialization_data::<Runtime, GrandpaPalletInstance>(12345),
-		});
+		let initialize_call =
+			<Runtime as frame_system::Config>::RuntimeCall::from(pallet_bridge_grandpa::Call::<
+				Runtime,
+				GrandpaPalletInstance,
+			>::initialize {
+				init_data: test_data::initialization_data::<Runtime, GrandpaPalletInstance>(12345),
+			})
+			.encode();
 
 		// overestimate - check weight for `pallet_bridge_grandpa::Pallet::initialize()` call
 		let require_weight_at_most =
@@ -125,11 +127,197 @@ pub fn initialize_bridge_by_governance_works<Runtime, GrandpaPalletInstance>(
 		// check mode after
 		assert_eq!(
 			pallet_bridge_grandpa::PalletOperatingMode::<Runtime, GrandpaPalletInstance>::try_get(),
-			Ok(bp_runtime::BasicOperatingMode::Normal)
+			Ok(BasicOperatingMode::Normal)
 		);
 	})
 }
 
+/// Test-case makes sure that `Runtime` can change bridge GRANDPA pallet operating mode via
+/// governance-like call.
+pub fn change_bridge_grandpa_pallet_mode_by_governance_works<Runtime, GrandpaPalletInstance>(
+	collator_session_key: CollatorSessionKeys<Runtime>,
+	runtime_para_id: u32,
+) where
+	Runtime: BasicParachainRuntime + pallet_bridge_grandpa::Config<GrandpaPalletInstance>,
+	GrandpaPalletInstance: 'static,
+	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
+	<Runtime as frame_system::Config>::RuntimeCall:
+		From<pallet_bridge_grandpa::Call<Runtime, GrandpaPalletInstance>>,
+{
+	run_test::<Runtime, _>(collator_session_key, runtime_para_id, vec![], || {
+		let dispatch_set_operating_mode_call = |old_mode, new_mode| {
+			// check old mode
+			assert_eq!(
+				pallet_bridge_grandpa::PalletOperatingMode::<Runtime, GrandpaPalletInstance>::get(),
+				old_mode,
+			);
+
+			// overestimate - check weight for `pallet_bridge_grandpa::Pallet::set_operating_mode()`
+			// call
+			let require_weight_at_most =
+				<Runtime as frame_system::Config>::DbWeight::get().reads_writes(7, 7);
+
+			// encode `set_operating_mode` call
+			let set_operating_mode_call = <Runtime as frame_system::Config>::RuntimeCall::from(
+				pallet_bridge_grandpa::Call::<Runtime, GrandpaPalletInstance>::set_operating_mode {
+					operating_mode: new_mode,
+				},
+			)
+			.encode();
+
+			// execute XCM with Transacts to `initialize bridge` as governance does
+			assert_ok!(RuntimeHelper::<Runtime>::execute_as_governance(
+				set_operating_mode_call,
+				require_weight_at_most
+			)
+			.ensure_complete());
+
+			// check mode after
+			assert_eq!(
+				pallet_bridge_grandpa::PalletOperatingMode::<Runtime, GrandpaPalletInstance>::try_get(),
+				Ok(new_mode)
+			);
+		};
+
+		// check mode before
+		assert_eq!(
+			pallet_bridge_grandpa::PalletOperatingMode::<Runtime, GrandpaPalletInstance>::try_get(),
+			Err(())
+		);
+
+		dispatch_set_operating_mode_call(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
+		dispatch_set_operating_mode_call(BasicOperatingMode::Halted, BasicOperatingMode::Normal);
+	});
+}
+
+/// Test-case makes sure that `Runtime` can change bridge parachains pallet operating mode via
+/// governance-like call.
+pub fn change_bridge_parachains_pallet_mode_by_governance_works<Runtime, ParachainsPalletInstance>(
+	collator_session_key: CollatorSessionKeys<Runtime>,
+	runtime_para_id: u32,
+) where
+	Runtime: BasicParachainRuntime + pallet_bridge_parachains::Config<ParachainsPalletInstance>,
+	ParachainsPalletInstance: 'static,
+	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
+	<Runtime as frame_system::Config>::RuntimeCall:
+		From<pallet_bridge_parachains::Call<Runtime, ParachainsPalletInstance>>,
+{
+	run_test::<Runtime, _>(collator_session_key, runtime_para_id, vec![], || {
+		let dispatch_set_operating_mode_call = |old_mode, new_mode| {
+			// check old mode
+			assert_eq!(
+				pallet_bridge_parachains::PalletOperatingMode::<Runtime, ParachainsPalletInstance>::get(),
+				old_mode,
+			);
+
+			// overestimate - check weight for
+			// `pallet_bridge_parachains::Pallet::set_operating_mode()` call
+			let require_weight_at_most =
+				<Runtime as frame_system::Config>::DbWeight::get().reads_writes(7, 7);
+
+			// encode `set_operating_mode` call
+			let set_operating_mode_call = <Runtime as frame_system::Config>::RuntimeCall::from(pallet_bridge_parachains::Call::<
+				Runtime,
+				ParachainsPalletInstance,
+			>::set_operating_mode {
+				operating_mode: new_mode,
+			}).encode();
+
+			// execute XCM with Transacts to `initialize bridge` as governance does
+			assert_ok!(RuntimeHelper::<Runtime>::execute_as_governance(
+				set_operating_mode_call,
+				require_weight_at_most
+			)
+			.ensure_complete());
+
+			// check mode after
+			assert_eq!(
+				pallet_bridge_parachains::PalletOperatingMode::<Runtime, ParachainsPalletInstance>::try_get(),
+				Ok(new_mode)
+			);
+		};
+
+		// check mode before
+		assert_eq!(
+			pallet_bridge_parachains::PalletOperatingMode::<Runtime, ParachainsPalletInstance>::try_get(),
+			Err(())
+		);
+
+		dispatch_set_operating_mode_call(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
+		dispatch_set_operating_mode_call(BasicOperatingMode::Halted, BasicOperatingMode::Normal);
+	});
+}
+
+/// Test-case makes sure that `Runtime` can change bridge messaging pallet operating mode via
+/// governance-like call.
+pub fn change_bridge_messages_pallet_mode_by_governance_works<Runtime, MessagesPalletInstance>(
+	collator_session_key: CollatorSessionKeys<Runtime>,
+	runtime_para_id: u32,
+) where
+	Runtime: BasicParachainRuntime + pallet_bridge_messages::Config<MessagesPalletInstance>,
+	MessagesPalletInstance: 'static,
+	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
+	<Runtime as frame_system::Config>::RuntimeCall:
+		From<pallet_bridge_messages::Call<Runtime, MessagesPalletInstance>>,
+{
+	run_test::<Runtime, _>(collator_session_key, runtime_para_id, vec![], || {
+		let dispatch_set_operating_mode_call = |old_mode, new_mode| {
+			// check old mode
+			assert_eq!(
+				pallet_bridge_messages::PalletOperatingMode::<Runtime, MessagesPalletInstance>::get(
+				),
+				old_mode,
+			);
+
+			// overestimate - check weight for
+			// `pallet_bridge_messages::Pallet::set_operating_mode()` call
+			let require_weight_at_most =
+				<Runtime as frame_system::Config>::DbWeight::get().reads_writes(7, 7);
+
+			// encode `set_operating_mode` call
+			let set_operating_mode_call = <Runtime as frame_system::Config>::RuntimeCall::from(pallet_bridge_messages::Call::<
+				Runtime,
+				MessagesPalletInstance,
+			>::set_operating_mode {
+				operating_mode: new_mode,
+			}).encode();
+
+			// execute XCM with Transacts to `initialize bridge` as governance does
+			assert_ok!(RuntimeHelper::<Runtime>::execute_as_governance(
+				set_operating_mode_call,
+				require_weight_at_most
+			)
+			.ensure_complete());
+
+			// check mode after
+			assert_eq!(
+				pallet_bridge_messages::PalletOperatingMode::<Runtime, MessagesPalletInstance>::try_get(),
+				Ok(new_mode)
+			);
+		};
+
+		// check mode before
+		assert_eq!(
+			pallet_bridge_messages::PalletOperatingMode::<Runtime, MessagesPalletInstance>::try_get(
+			),
+			Err(())
+		);
+
+		dispatch_set_operating_mode_call(
+			MessagesOperatingMode::Basic(BasicOperatingMode::Normal),
+			MessagesOperatingMode::RejectingOutboundMessages,
+		);
+		dispatch_set_operating_mode_call(
+			MessagesOperatingMode::RejectingOutboundMessages,
+			MessagesOperatingMode::Basic(BasicOperatingMode::Halted),
+		);
+		dispatch_set_operating_mode_call(
+			MessagesOperatingMode::Basic(BasicOperatingMode::Halted),
+			MessagesOperatingMode::Basic(BasicOperatingMode::Normal),
+		);
+	});
+}
+
 /// Test-case makes sure that `Runtime` can handle xcm `ExportMessage`:
 /// Checks if received XCM messages is correctly added to the message outbound queue for delivery.
 /// For SystemParachains we expect unpaid execution.
-- 
GitLab