diff --git a/Cargo.lock b/Cargo.lock
index f74917b60650c..6bf1e8b576d09 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4491,7 +4491,7 @@ name = "cumulus-pallet-xcmp-queue"
version = "0.7.0"
dependencies = [
"bounded-collections",
- "bp-xcm-bridge-hub-router",
+ "bp-xcm-bridge-hub",
"cumulus-pallet-parachain-system",
"cumulus-primitives-core",
"frame-benchmarking",
@@ -13276,6 +13276,7 @@ dependencies = [
name = "pallet-xcm-bridge-hub-router"
version = "0.5.0"
dependencies = [
+ "bp-xcm-bridge-hub",
"bp-xcm-bridge-hub-router",
"frame-benchmarking",
"frame-support",
diff --git a/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/bridges/modules/xcm-bridge-hub-router/Cargo.toml
index 55824f6a7fe7b..1cda8893967e2 100644
--- a/bridges/modules/xcm-bridge-hub-router/Cargo.toml
+++ b/bridges/modules/xcm-bridge-hub-router/Cargo.toml
@@ -17,6 +17,7 @@ scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true }
# Bridge dependencies
bp-xcm-bridge-hub-router = { workspace = true }
+bp-xcm-bridge-hub = { workspace = true }
# Substrate Dependencies
frame-benchmarking = { optional = true, workspace = true }
@@ -38,6 +39,7 @@ sp-std = { workspace = true, default-features = true }
default = ["std"]
std = [
"bp-xcm-bridge-hub-router/std",
+ "bp-xcm-bridge-hub/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
diff --git a/bridges/modules/xcm-bridge-hub-router/src/impls.rs b/bridges/modules/xcm-bridge-hub-router/src/impls.rs
index 99dac41f93c37..6114a0b75ca6b 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/impls.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/impls.rs
@@ -15,14 +15,53 @@
// along with Parity Bridges Common. If not, see .
//! Various implementations supporting easier configuration of the pallet.
-use crate::{Config, Pallet, Bridges, LOG_TARGET};
+use crate::{Config, Pallet, Bridges, BridgeIdOf, LOG_TARGET};
use xcm_builder::ExporterFor;
use bp_xcm_bridge_hub_router::ResolveBridgeId;
use codec::Encode;
use frame_support::pallet_prelude::PhantomData;
use frame_support::traits::Get;
+use frame_support::ensure;
use xcm::prelude::*;
+/// Implementation of `LocalXcmChannelManager` which tracks and updates `is_congested` for a given `BridgeId`.
+/// This implementation is useful for managing congestion and dynamic fees with the local `ExportXcm` implementation.
+impl, I: 'static> bp_xcm_bridge_hub::LocalXcmChannelManager> for Pallet {
+ type Error = ();
+
+ /// Suspends the given bridge.
+ ///
+ /// This function ensures that the `local_origin` matches the expected `Location::here()`. If the check passes, it updates the bridge status to congested.
+ fn suspend_bridge(local_origin: &Location, bridge: BridgeIdOf) -> Result<(), Self::Error> {
+ log::trace!(
+ target: LOG_TARGET,
+ "LocalXcmChannelManager::suspend_bridge(local_origin: {local_origin:?}, bridge: {bridge:?})",
+ );
+ ensure!(local_origin.eq(&Location::here()), ());
+
+ // update status
+ Self::update_bridge_status(bridge, true);
+
+ Ok(())
+ }
+
+ /// Resumes the given bridge.
+ ///
+ /// This function ensures that the `local_origin` matches the expected `Location::here()`. If the check passes, it updates the bridge status to not congested.
+ fn resume_bridge(local_origin: &Location, bridge: BridgeIdOf) -> Result<(), Self::Error> {
+ log::trace!(
+ target: LOG_TARGET,
+ "LocalXcmChannelManager::resume_bridge(local_origin: {local_origin:?}, bridge: {bridge:?})",
+ );
+ ensure!(local_origin.eq(&Location::here()), ());
+
+ // update status
+ Self::update_bridge_status(bridge, false);
+
+ Ok(())
+ }
+}
+
pub struct ViaRemoteBridgeHubExporter(PhantomData<(T, I, E, BNF, BHLF)>);
impl, I: 'static, E, BridgedNetworkIdFilter, BridgeHubLocationFilter> ExporterFor for ViaRemoteBridgeHubExporter
diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs
index 1d4a4c4fdcdbe..a6dfe20266f9b 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs
@@ -31,7 +31,7 @@
use bp_xcm_bridge_hub_router::{BridgeState, ResolveBridgeId};
use codec::Encode;
-use frame_support::traits::Get;
+use frame_support::traits::{EnsureOriginWithArg, Get};
use sp_runtime::{FixedPointNumber, FixedU128, Saturating};
use sp_std::vec::Vec;
use xcm::prelude::*;
@@ -127,6 +127,10 @@ pub mod pallet {
#[pallet::no_default]
type BridgeIdResolver: ResolveBridgeId;
+ /// Origin of the sibling bridge hub that is allowed to report bridge status.
+ #[pallet::no_default]
+ type BridgeHubOrigin: EnsureOriginWithArg>;
+
/// Additional fee that is paid for every byte of the outbound message.
/// See `calculate_message_size_fees` for more details.
type ByteFee: Get;
@@ -204,9 +208,35 @@ pub mod pallet {
}
}
+ #[pallet::call]
+ impl, I: 'static> Pallet {
+ /// Notification about congested bridge queue.
+ #[pallet::call_index(0)]
+ #[pallet::weight(T::WeightInfo::report_bridge_status())]
+ pub fn report_bridge_status(
+ origin: OriginFor,
+ bridge_id: BridgeIdOf,
+ is_congested: bool,
+ ) -> DispatchResult {
+ let _ = T::BridgeHubOrigin::ensure_origin(origin, &bridge_id)?;
+
+ log::info!(
+ target: LOG_TARGET,
+ "Received bridge status from {:?}: congested = {}",
+ bridge_id,
+ is_congested,
+ );
+
+ // update status
+ Self::update_bridge_status(bridge_id, is_congested);
+
+ Ok(())
+ }
+ }
+
/// Stores `BridgeState` for congestion control and dynamic fees for each resolved bridge ID associated with a destination.
#[pallet::storage]
- pub type Bridges, I: 'static = ()> = StorageMap<_, Identity, BridgeIdOf, BridgeState, OptionQuery>;
+ pub type Bridges, I: 'static = ()> = StorageMap<_, Blake2_128Concat, BridgeIdOf, BridgeState, OptionQuery>;
impl, I: 'static> Pallet {
/// Called when new message is sent to the `dest` (queued to local outbound XCM queue).
@@ -276,6 +306,24 @@ pub mod pallet {
}
None
}
+ /// Updates the congestion status of a bridge for a given `bridge_id`.
+ ///
+ /// If the bridge does not exist and:
+ /// - `is_congested` is true, a new `BridgeState` is created with a default `delivery_fee_factor`.
+ /// - `is_congested` is false, does nothing and no `BridgeState` is created.
+ pub(crate) fn update_bridge_status(bridge_id: BridgeIdOf, is_congested: bool) {
+ Bridges::::mutate(bridge_id, |bridge| match bridge {
+ Some(bridge) => bridge.is_congested = is_congested,
+ None => {
+ if is_congested {
+ *bridge = Some(BridgeState {
+ delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR,
+ is_congested,
+ })
+ }
+ }
+ });
+ }
}
#[pallet::event]
@@ -410,7 +458,7 @@ mod tests {
use frame_support::traits::Hooks;
use frame_system::{EventRecord, Phase};
- use sp_runtime::traits::One;
+ use sp_runtime::traits::{Dispatchable, One};
#[test]
fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_is_congested() {
@@ -445,9 +493,9 @@ mod tests {
}));
// it should eventually decrease and remove
- let mut old_delivery_fee_factor = initial_fee_factor;
+ let mut last_delivery_fee_factor = initial_fee_factor;
while let Some(bridge_state) = get_bridge_state_for::(&dest) {
- old_delivery_fee_factor = bridge_state.delivery_fee_factor;
+ last_delivery_fee_factor = bridge_state.delivery_fee_factor;
XcmBridgeHubRouter::on_initialize(One::one());
}
@@ -473,7 +521,7 @@ mod tests {
Some(EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::XcmBridgeHubRouter(Event::DeliveryFeeFactorDecreased {
- previous_value: old_delivery_fee_factor,
+ previous_value: last_delivery_fee_factor,
new_value: 0.into(),
bridge_id,
}),
@@ -679,4 +727,37 @@ mod tests {
assert_eq!(XcmBridgeHubRouter::get_messages(), vec![]);
});
}
+
+ #[test]
+ fn report_bridge_status_works() {
+ run_test(|| {
+ let dest = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]);
+ let bridge_id = ();
+ let report_bridge_status = |bridge_id, is_congested| {
+ let call = RuntimeCall::XcmBridgeHubRouter(Call::report_bridge_status {
+ bridge_id,
+ is_congested,
+ });
+ assert_ok!(call.dispatch(RuntimeOrigin::root()));
+ };
+
+ assert!(get_bridge_state_for::(&dest).is_none());
+ report_bridge_status(bridge_id, false);
+ assert!(get_bridge_state_for::(&dest).is_none());
+
+ // make congested
+ report_bridge_status(bridge_id, true);
+ assert_eq!(get_bridge_state_for::(&dest), Some(BridgeState {
+ delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR,
+ is_congested: true,
+ }));
+
+ // make uncongested
+ report_bridge_status(bridge_id, false);
+ assert_eq!(get_bridge_state_for::(&dest), Some(BridgeState {
+ delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR,
+ is_congested: false,
+ }));
+ });
+ }
}
diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs
index a3b078c2c471f..9d9aabc16540c 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs
@@ -24,6 +24,7 @@ use frame_support::{
construct_runtime, derive_impl, parameter_types,
traits::{Contains, Equals},
};
+use frame_system::EnsureRoot;
use sp_runtime::{traits::ConstU128, BuildStorage};
use sp_std::cell::RefCell;
use xcm::prelude::*;
@@ -43,8 +44,8 @@ pub const BYTE_FEE: u128 = 1_000;
construct_runtime! {
pub enum TestRuntime
{
- System: frame_system::{Pallet, Call, Config, Storage, Event},
- XcmBridgeHubRouter: pallet_xcm_bridge_hub_router::{Pallet, Storage, Event},
+ System: frame_system,
+ XcmBridgeHubRouter: pallet_xcm_bridge_hub_router,
}
}
@@ -104,6 +105,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
>;
type BridgeIdResolver = EveryDestinationToSameBridgeIdResolver;
+ type BridgeHubOrigin = EnsureRoot;
type ByteFee = ConstU128;
type FeeAsset = BridgeFeeAsset;
diff --git a/bridges/modules/xcm-bridge-hub-router/src/weights.rs b/bridges/modules/xcm-bridge-hub-router/src/weights.rs
index 2685d676d3850..dc5f1ea1a99ad 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/weights.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/weights.rs
@@ -54,6 +54,7 @@ pub trait WeightInfo {
fn on_initialize_when_bridge_state_updated() -> Weight;
fn on_initialize_when_non_congested() -> Weight;
fn on_initialize_when_congested() -> Weight;
+ fn report_bridge_status() -> Weight;
}
/// Weights for `pallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets.
@@ -94,6 +95,19 @@ impl WeightInfo for BridgeWeight {
// Minimum execution time: 4_239 nanoseconds.
Weight::from_parts(4_383_000, 3547).saturating_add(T::DbWeight::get().reads(1_u64))
}
+ /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
+ ///
+ /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
+ /// 512, mode: `MaxEncodedLen`)
+ fn report_bridge_status() -> Weight {
+ // Proof Size summary in bytes:
+ // Measured: `53`
+ // Estimated: `1502`
+ // Minimum execution time: 10_427 nanoseconds.
+ Weight::from_parts(10_682_000, 1502)
+ .saturating_add(T::DbWeight::get().reads(1_u64))
+ .saturating_add(T::DbWeight::get().writes(1_u64))
+ }
}
// For backwards compatibility and tests
@@ -137,4 +151,17 @@ impl WeightInfo for () {
// Minimum execution time: 4_239 nanoseconds.
Weight::from_parts(4_383_000, 3547).saturating_add(RocksDbWeight::get().reads(1_u64))
}
+ /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
+ ///
+ /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
+ /// 512, mode: `MaxEncodedLen`)
+ fn report_bridge_status() -> Weight {
+ // Proof Size summary in bytes:
+ // Measured: `53`
+ // Estimated: `1502`
+ // Minimum execution time: 10_427 nanoseconds.
+ Weight::from_parts(10_682_000, 1502)
+ .saturating_add(RocksDbWeight::get().reads(1_u64))
+ .saturating_add(RocksDbWeight::get().writes(1_u64))
+ }
}
diff --git a/bridges/modules/xcm-bridge-hub/src/congestion.rs b/bridges/modules/xcm-bridge-hub/src/congestion.rs
new file mode 100644
index 0000000000000..2307f692a0af2
--- /dev/null
+++ b/bridges/modules/xcm-bridge-hub/src/congestion.rs
@@ -0,0 +1,151 @@
+// Copyright 2019-2021 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 .
+
+//! The module contains utilities for handling congestion between the bridge hub and routers.
+
+use sp_std::vec::Vec;
+use sp_std::marker::PhantomData;
+use codec::Encode;
+use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager, Receiver};
+use sp_runtime::traits::Convert;
+use xcm::latest::{send_xcm, Location, SendXcm, Xcm};
+use crate::{Config, Bridges, LOG_TARGET};
+
+/// Switches the implementation of `LocalXcmChannelManager` based on the `local_origin`.
+///
+/// - `HereXcmChannelManager` is applied when the origin is `Here`.
+/// - Otherwise, `LocalConsensusXcmChannelManager` is used.
+///
+/// This is useful when the `pallet-xcm-bridge-hub` needs to support both:
+/// - A local router deployed on the same chain as the `pallet-xcm-bridge-hub`.
+/// - A remote router deployed on a different chain than the `pallet-xcm-bridge-hub`.
+pub struct HereOrLocalConsensusXcmChannelManager(PhantomData<(Bridge, HereXcmChannelManager, LocalConsensusXcmChannelManager)>);
+impl, LocalConsensusXcmChannelManager: LocalXcmChannelManager> LocalXcmChannelManager
+for HereOrLocalConsensusXcmChannelManager {
+ type Error = ();
+
+ fn suspend_bridge(local_origin: &Location, bridge: Bridge) -> Result<(), Self::Error> {
+ if local_origin.eq(&Location::here()) {
+ HereXcmChannelManager::suspend_bridge(local_origin, bridge).map_err(|e| {
+ log::error!(
+ target: LOG_TARGET,
+ "HereXcmChannelManager::suspend_bridge error: {e:?} for local_origin: {:?} and bridge: {:?}",
+ local_origin,
+ bridge,
+ );
+ ()
+ })
+ } else {
+ LocalConsensusXcmChannelManager::suspend_bridge(local_origin, bridge).map_err(|e| {
+ log::error!(
+ target: LOG_TARGET,
+ "LocalConsensusXcmChannelManager::suspend_bridge error: {e:?} for local_origin: {:?} and bridge: {:?}",
+ local_origin,
+ bridge,
+ );
+ ()
+ })
+ }
+ }
+
+ fn resume_bridge(local_origin: &Location, bridge: Bridge) -> Result<(), Self::Error> {
+ if local_origin.eq(&Location::here()) {
+ HereXcmChannelManager::resume_bridge(local_origin, bridge).map_err(|e| {
+ log::error!(
+ target: LOG_TARGET,
+ "HereXcmChannelManager::resume_bridge error: {e:?} for local_origin: {:?} and bridge: {:?}",
+ local_origin,
+ bridge,
+ );
+ ()
+ })
+ } else {
+ LocalConsensusXcmChannelManager::resume_bridge(local_origin, bridge).map_err(|e| {
+ log::error!(
+ target: LOG_TARGET,
+ "LocalConsensusXcmChannelManager::resume_bridge error: {e:?} for local_origin: {:?} and bridge: {:?}",
+ local_origin,
+ bridge,
+ );
+ ()
+ })
+ }
+ }
+}
+
+/// Manages the local XCM channels by sending XCM messages with the `report_bridge_status` extrinsic to the `local_origin`.
+/// The `XcmProvider` type converts the encoded call to `XCM`, which is then sent by `XcmSender` to the `local_origin`.
+/// This is useful, for example, when a router with `ExportMessage` is deployed on a different chain, and we want to control congestion by sending XCMs.
+pub struct ReportBridgeStatusXcmChannelManager(PhantomData<(T, I, XcmProvider, XcmSender)>);
+impl, I: 'static, XcmProvider: Convert, Xcm<()>>, XcmSender: SendXcm> ReportBridgeStatusXcmChannelManager {
+ fn report_bridge_status(local_origin: &Location, bridge_id: BridgeId, is_congested: bool) -> Result<(), ()> {
+ // check the bridge and get `maybe_notify` callback.
+ let bridge = Bridges::::get(&bridge_id).ok_or(())?;
+ let Some(Receiver { pallet_index, call_index }) = bridge.maybe_notify else {
+ // `local_origin` did not set `maybe_notify`, so nothing to notify, so it is ok.
+ return Ok(())
+ };
+
+ // constructing expected call
+ let remote_runtime_call = (pallet_index, call_index, bridge_id, is_congested);
+ // construct XCM
+ let xcm = XcmProvider::convert(remote_runtime_call.encode());
+ log::trace!(
+ target: LOG_TARGET,
+ "ReportBridgeStatusXcmChannelManager is going to send status with is_congested: {:?} to the local_origin: {:?} and bridge: {:?} as xcm: {:?}",
+ is_congested,
+ local_origin,
+ bridge,
+ xcm,
+ );
+
+ // send XCM
+ send_xcm::(local_origin.clone(), xcm)
+ .map(|result| {
+ log::warn!(
+ target: LOG_TARGET,
+ "ReportBridgeStatusXcmChannelManager successfully sent status with is_congested: {:?} to the local_origin: {:?} and bridge: {:?} with result: {:?}",
+ is_congested,
+ local_origin,
+ bridge,
+ result,
+ );
+ ()
+ })
+ .map_err(|e| {
+ log::error!(
+ target: LOG_TARGET,
+ "ReportBridgeStatusXcmChannelManager failed to send status with is_congested: {:?} to the local_origin: {:?} and bridge: {:?} with error: {:?}",
+ is_congested,
+ local_origin,
+ bridge,
+ e,
+ );
+ ()
+ })
+ }
+}
+impl, I: 'static, XcmProvider: Convert, Xcm<()>>, XcmSender: SendXcm> LocalXcmChannelManager for ReportBridgeStatusXcmChannelManager {
+ type Error = ();
+
+ fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
+ Self::report_bridge_status(local_origin, bridge, true)
+ }
+
+ fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
+ Self::report_bridge_status(local_origin, bridge, false)
+ }
+}
diff --git a/bridges/modules/xcm-bridge-hub/src/dispatcher.rs b/bridges/modules/xcm-bridge-hub/src/dispatcher.rs
index daa49ed415d39..bbc9c8ae54d63 100644
--- a/bridges/modules/xcm-bridge-hub/src/dispatcher.rs
+++ b/bridges/modules/xcm-bridge-hub/src/dispatcher.rs
@@ -21,11 +21,11 @@
//!
//! This code is executed at the target bridge hub.
-use crate::{Config, Pallet, LOG_TARGET};
+use crate::{Config, Pallet, LOG_TARGET, DispatchChannelStatusProvider};
use bp_messages::target_chain::{DispatchMessage, MessageDispatch};
use bp_runtime::messages::MessageDispatchResult;
-use bp_xcm_bridge_hub::{LocalXcmChannelManager, XcmAsPlainPayload};
+use bp_xcm_bridge_hub::XcmAsPlainPayload;
use codec::{Decode, Encode};
use frame_support::{weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound};
use pallet_bridge_messages::{Config as BridgeMessagesConfig, WeightInfoExt};
@@ -60,7 +60,7 @@ where
fn is_active(lane: Self::LaneId) -> bool {
Pallet::::bridge_by_lane_id(&lane)
.and_then(|(_, bridge)| bridge.bridge_origin_relative_location.try_as().cloned().ok())
- .map(|recipient: Location| !T::LocalXcmChannelManager::is_congested(&recipient))
+ .map(|recipient: Location| !T::BlobDispatcher::is_congested(&recipient))
.unwrap_or(false)
}
@@ -158,6 +158,7 @@ mod tests {
state: BridgeState::Opened,
deposit: None,
lane_id,
+ maybe_notify: None,
},
);
LaneToBridge::::insert(lane_id, bridge.bridge_id());
@@ -206,8 +207,11 @@ mod tests {
#[test]
fn dispatcher_is_inactive_when_channel_with_target_chain_is_congested() {
run_test_with_opened_bridge(|| {
- TestLocalXcmChannelManager::make_congested();
- assert!(!XcmOverBridge::is_active(bridge().1));
+ let bridge = bridge();
+ let with = bridge.0.bridge_origin_relative_location();
+ let lane_id = bridge.1;
+ TestBlobDispatcher::make_congested(with);
+ assert!(!XcmOverBridge::is_active(lane_id));
});
}
diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs
index bbdca5dd6dd9e..392fe44906425 100644
--- a/bridges/modules/xcm-bridge-hub/src/exporter.rs
+++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs
@@ -233,7 +233,7 @@ impl, I: 'static> Pallet {
{
Ok(bridge_origin_relative_location) => bridge_origin_relative_location,
Err(_) => {
- log::debug!(
+ log::error!(
target: LOG_TARGET,
"Failed to convert the bridge {:?} origin location {:?}",
bridge_id,
@@ -255,7 +255,7 @@ impl, I: 'static> Pallet {
);
},
Err(e) => {
- log::debug!(
+ log::error!(
target: LOG_TARGET,
"Failed to suspended the bridge {:?}, originated by the {:?}: {:?}",
bridge_id,
@@ -298,7 +298,7 @@ impl, I: 'static> Pallet {
let bridge_origin_relative_location = match bridge_origin_relative_location {
Ok(bridge_origin_relative_location) => bridge_origin_relative_location,
Err(e) => {
- log::debug!(
+ log::error!(
target: LOG_TARGET,
"Failed to convert the bridge {:?} location for lane_id: {:?}, error {:?}",
bridge_id,
@@ -323,7 +323,7 @@ impl, I: 'static> Pallet {
);
},
Err(e) => {
- log::debug!(
+ log::error!(
target: LOG_TARGET,
"Failed to resume the bridge {:?} and lane_id: {:?}, originated by the {:?}: {:?}",
bridge_id,
@@ -364,7 +364,7 @@ mod tests {
use crate::{mock::*, Bridges, LanesManagerOf};
use bp_runtime::RangeInclusiveExt;
- use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
+ use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState, Receiver};
use bp_xcm_bridge_hub_router::ResolveBridgeId;
use frame_support::{
assert_ok,
@@ -451,7 +451,7 @@ mod tests {
run_test(|| {
let (bridge_id, _) =
open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin());
- assert!(!TestLocalXcmChannelManager::is_bridge_suspened());
+ assert!(!TestLocalXcmChannelManager::is_bridge_suspened(&bridge_id));
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
});
}
@@ -469,7 +469,7 @@ mod tests {
}
open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin());
- assert!(!TestLocalXcmChannelManager::is_bridge_suspened());
+ assert!(!TestLocalXcmChannelManager::is_bridge_suspened(&bridge_id));
});
}
@@ -482,11 +482,11 @@ mod tests {
open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin());
}
- assert!(!TestLocalXcmChannelManager::is_bridge_suspened());
+ assert!(!TestLocalXcmChannelManager::is_bridge_suspened(&bridge_id));
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin());
- assert!(TestLocalXcmChannelManager::is_bridge_suspened());
+ assert!(TestLocalXcmChannelManager::is_bridge_suspened(&bridge_id));
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended);
});
}
@@ -504,7 +504,7 @@ mod tests {
OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1,
);
- assert!(!TestLocalXcmChannelManager::is_bridge_resumed());
+ assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended);
});
}
@@ -519,7 +519,7 @@ mod tests {
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
);
- assert!(!TestLocalXcmChannelManager::is_bridge_resumed());
+ assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
});
}
@@ -537,7 +537,7 @@ mod tests {
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
);
- assert!(TestLocalXcmChannelManager::is_bridge_resumed());
+ assert!(TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
});
}
@@ -603,6 +603,7 @@ mod tests {
state: BridgeState::Opened,
deposit: None,
lane_id: expected_lane_id,
+ maybe_notify: None,
},
);
}
@@ -843,12 +844,22 @@ mod tests {
// we need to set `UniversalLocation` for `sibling_parachain_origin` for `XcmOverBridgeWrappedWithExportMessageRouterInstance`.
ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get()));
+ // we need to update `maybe_notify` for `bridge_1` with `pallet_index` of `XcmOverBridgeWrappedWithExportMessageRouter`,
+ Bridges::::mutate_extant(bridge_1.bridge_id(), |bridge| {
+ bridge.maybe_notify = Some(Receiver::new(57, 0));
+ });
+
// check before
+ // bridges are opened
assert_eq!(XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, BridgeState::Opened);
assert_eq!(XcmOverBridge::bridge(bridge_2.bridge_id()).unwrap().state, BridgeState::Opened);
// both routers are uncongested
assert!(!router_bridge_state::(&dest).map(|bs| bs.is_congested).unwrap_or(false));
assert!(!router_bridge_state::(&dest).map(|bs| bs.is_congested).unwrap_or(false));
+ assert!(!TestLocalXcmChannelManager::is_bridge_suspened(bridge_1.bridge_id()));
+ assert!(!TestLocalXcmChannelManager::is_bridge_suspened(bridge_2.bridge_id()));
+ assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
+ assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_2.bridge_id()));
// make bridges congested with sending too much messages
for _ in 1..(OUTBOUND_LANE_CONGESTED_THRESHOLD + 2) {
@@ -861,11 +872,35 @@ mod tests {
}
// checks after
+ // bridges are suspended
assert_eq!(XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, BridgeState::Suspended);
assert_eq!(XcmOverBridge::bridge(bridge_2.bridge_id()).unwrap().state, BridgeState::Suspended);
// both routers are congested
assert!(router_bridge_state::(&dest).unwrap().is_congested);
assert!(router_bridge_state::(&dest).unwrap().is_congested);
+ assert!(TestLocalXcmChannelManager::is_bridge_suspened(bridge_1.bridge_id()));
+ assert!(TestLocalXcmChannelManager::is_bridge_suspened(bridge_2.bridge_id()));
+ assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
+ assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_2.bridge_id()));
+
+ // make bridges uncongested to trigger resume signal
+ XcmOverBridge::on_bridge_messages_delivered(
+ expected_lane_id_1,
+ OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
+ );
+ XcmOverBridge::on_bridge_messages_delivered(
+ expected_lane_id_2,
+ OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
+ );
+
+ // bridges are again opened
+ assert_eq!(XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, BridgeState::Opened);
+ assert_eq!(XcmOverBridge::bridge(bridge_2.bridge_id()).unwrap().state, BridgeState::Opened);
+ // both routers are uncongested
+ assert!(!router_bridge_state::(&dest).unwrap().is_congested);
+ assert!(!router_bridge_state::(&dest).unwrap().is_congested);
+ assert!(TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
+ assert!(TestLocalXcmChannelManager::is_bridge_resumed(bridge_2.bridge_id()));
})
}
}
diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs
index 658ad279aa7a4..75e76bd11b2bd 100644
--- a/bridges/modules/xcm-bridge-hub/src/lib.rs
+++ b/bridges/modules/xcm-bridge-hub/src/lib.rs
@@ -148,6 +148,7 @@ use bp_runtime::{AccountIdOf, BalanceOf, RangeInclusiveExt};
pub use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState};
use bp_xcm_bridge_hub::{
BridgeLocations, BridgeLocationsError, Deposit, DepositOf, LocalXcmChannelManager,
+ ChannelStatusProvider as DispatchChannelStatusProvider,
};
use frame_support::{traits::fungible::MutateHold, DefaultNoBound};
use frame_system::Config as SystemConfig;
@@ -164,6 +165,7 @@ pub use pallet::*;
mod dispatcher;
mod exporter;
+pub mod congestion;
pub mod migration;
mod mock;
@@ -241,10 +243,10 @@ pub mod pallet {
/// For example, it is possible to make an exception for a system parachain or relay.
type AllowWithoutBridgeDeposit: Contains;
- /// Local XCM channel manager.
- type LocalXcmChannelManager: LocalXcmChannelManager;
+ /// Local XCM channel manager. Dedicated to exporting capabilities when handling congestion with the sending side.
+ type LocalXcmChannelManager: LocalXcmChannelManager;
/// XCM-level dispatcher for inbound bridge messages.
- type BlobDispatcher: DispatchBlob;
+ type BlobDispatcher: DispatchBlob + DispatchChannelStatusProvider;
}
/// An alias for the bridge metadata.
@@ -510,6 +512,7 @@ pub mod pallet {
state: BridgeState::Opened,
deposit: deposit.clone(),
lane_id,
+ maybe_notify: None,
});
Ok(())
},
@@ -879,6 +882,7 @@ mod tests {
state: BridgeState::Opened,
deposit,
lane_id,
+ maybe_notify: None,
};
Bridges::::insert(locations.bridge_id(), bridge.clone());
LaneToBridge::::insert(bridge.lane_id, locations.bridge_id());
@@ -1034,6 +1038,7 @@ mod tests {
state: BridgeState::Opened,
deposit: None,
lane_id,
+ maybe_notify: None,
},
);
@@ -1166,7 +1171,8 @@ mod tests {
),
state: BridgeState::Opened,
deposit: expected_deposit.clone(),
- lane_id
+ lane_id,
+ maybe_notify: None,
}),
);
assert_eq!(
@@ -1521,6 +1527,7 @@ mod tests {
state: BridgeState::Opened,
deposit: Some(Deposit::new(bridge_owner_account.clone(), Zero::zero())),
lane_id,
+ maybe_notify: None,
},
(lane_id, bridge_id),
(lane_id, lane_id),
@@ -1546,6 +1553,7 @@ mod tests {
state: BridgeState::Opened,
deposit: Some(Deposit::new(bridge_owner_account.clone(), Zero::zero())),
lane_id,
+ maybe_notify: None,
},
(lane_id, bridge_id_mismatch),
(lane_id, lane_id),
@@ -1571,6 +1579,7 @@ mod tests {
state: BridgeState::Opened,
deposit: Some(Deposit::new(bridge_owner_account_mismatch.clone(), Zero::zero())),
lane_id,
+ maybe_notify: None,
},
(lane_id, bridge_id),
(lane_id, lane_id),
@@ -1595,6 +1604,7 @@ mod tests {
state: BridgeState::Opened,
deposit: Some(Deposit::new(bridge_owner_account_mismatch.clone(), Zero::zero())),
lane_id,
+ maybe_notify: None,
},
(lane_id, bridge_id_mismatch),
(lane_id, lane_id),
@@ -1620,6 +1630,7 @@ mod tests {
state: BridgeState::Opened,
deposit: Some(Deposit::new(bridge_owner_account.clone(), Zero::zero())),
lane_id,
+ maybe_notify: None,
},
(lane_id, bridge_id),
(lane_id_mismatch, lane_id),
@@ -1645,6 +1656,7 @@ mod tests {
state: BridgeState::Opened,
deposit: Some(Deposit::new(bridge_owner_account, Zero::zero())),
lane_id,
+ maybe_notify: None,
},
(lane_id, bridge_id),
(lane_id, lane_id_mismatch),
diff --git a/bridges/modules/xcm-bridge-hub/src/migration.rs b/bridges/modules/xcm-bridge-hub/src/migration.rs
index dc1a17d130727..c9474e6283007 100644
--- a/bridges/modules/xcm-bridge-hub/src/migration.rs
+++ b/bridges/modules/xcm-bridge-hub/src/migration.rs
@@ -222,6 +222,7 @@ pub mod v1 {
state,
deposit,
lane_id,
+ maybe_notify: None,
})
};
Bridges::::translate_values(translate);
diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs
index 63f767045cf54..1e4575c543114 100644
--- a/bridges/modules/xcm-bridge-hub/src/mock.rs
+++ b/bridges/modules/xcm-bridge-hub/src/mock.rs
@@ -18,7 +18,7 @@
use sp_std::marker::PhantomData;
use crate as pallet_xcm_bridge_hub;
-
+use pallet_xcm_bridge_hub::{congestion::{HereOrLocalConsensusXcmChannelManager, ReportBridgeStatusXcmChannelManager}};
use bp_messages::{
target_chain::{DispatchMessage, MessageDispatch},
ChainWithMessages, HashedLaneId, MessageNonce,
@@ -33,7 +33,7 @@ use frame_support::{
weights::RuntimeDbWeight,
};
use frame_support::traits::Get;
-use frame_system::EnsureRootWithSuccess;
+use frame_system::{EnsureNever, EnsureRoot, EnsureRootWithSuccess};
use polkadot_parachain_primitives::primitives::Sibling;
use sp_core::H256;
use sp_runtime::{
@@ -41,6 +41,7 @@ use sp_runtime::{
traits::{BlakeTwo256, ConstU32, IdentityLookup},
AccountId32, BuildStorage, StateVersion,
};
+use sp_runtime::traits::Convert;
use sp_std::cell::RefCell;
use xcm::prelude::*;
use xcm_builder::{
@@ -48,7 +49,7 @@ use xcm_builder::{
InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset,
SiblingParachainConvertsVia, SovereignPaidRemoteExporter, UnpaidLocalExporter, ensure_is_remote,
};
-use xcm_executor::{traits::ConvertLocation, XcmExecutor};
+use xcm_executor::{traits::{ConvertLocation, ConvertOrigin}, XcmExecutor};
pub type AccountId = AccountId32;
pub type Balance = u64;
@@ -189,6 +190,22 @@ pub fn bridged_asset_hub_universal_location() -> InteriorLocation {
BridgedUniversalDestination::get()
}
+pub(crate) type TestLocalXcmChannelManager = TestingLocalXcmChannelManager<
+ BridgeId,
+ HereOrLocalConsensusXcmChannelManager<
+ BridgeId,
+ // handles congestion for `XcmOverBridgeByExportXcmRouter`
+ XcmOverBridgeByExportXcmRouter,
+ // handles congestion for `XcmOverBridgeWrappedWithExportMessageRouter`
+ ReportBridgeStatusXcmChannelManager<
+ TestRuntime,
+ (),
+ ReportBridgeStatusXcmProvider,
+ FromBridgeHubLocationXcmSender
+ >,
+ >
+>;
+
impl pallet_xcm_bridge_hub::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
@@ -199,7 +216,7 @@ impl pallet_xcm_bridge_hub::Config for TestRuntime {
type MessageExportPrice = ();
type DestinationVersion = AlwaysLatest;
- type ForceOrigin = frame_system::EnsureNever<()>;
+ type ForceOrigin = EnsureNever<()>;
type OpenBridgeOrigin = EitherOf<
// We want to translate `RuntimeOrigin::root()` to the `Location::here()`
EnsureRootWithSuccess,
@@ -241,6 +258,8 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
>;
type BridgeIdResolver = EnsureIsRemoteBridgeIdResolver;
+ // We convert to root here `BridgeHubLocationXcmOriginAsRoot`
+ type BridgeHubOrigin = EnsureRoot;
}
/// A router instance simulates a scenario where the router is deployed on the same chain as the
@@ -253,6 +272,8 @@ impl pallet_xcm_bridge_hub_router::Config;
type BridgeIdResolver = EnsureIsRemoteBridgeIdResolver;
+ // We don't need to support here `report_bridge_status`.
+ type BridgeHubOrigin = EnsureNever<()>;
}
/// Implementation of `ResolveBridgeId` returning `BridgeId` based on the configured `UniversalLocation`.
@@ -287,12 +308,29 @@ thread_local! {
pub static EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION: RefCell