Skip to content

Commit

Permalink
Report congestion and logical bridges/channel with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bkontur committed Nov 5, 2024
1 parent d821c08 commit c78e707
Show file tree
Hide file tree
Showing 16 changed files with 551 additions and 91 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bridges/modules/xcm-bridge-hub-router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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",
Expand Down
41 changes: 40 additions & 1 deletion bridges/modules/xcm-bridge-hub-router/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,53 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.

//! 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<T: Config<I>, I: 'static> bp_xcm_bridge_hub::LocalXcmChannelManager<BridgeIdOf<T, I>> for Pallet<T, I> {
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<T, I>) -> 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<T, I>) -> 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<T, I, E, BNF, BHLF>(PhantomData<(T, I, E, BNF, BHLF)>);

impl<T: Config<I>, I: 'static, E, BridgedNetworkIdFilter, BridgeHubLocationFilter> ExporterFor for ViaRemoteBridgeHubExporter<T, I, E, BridgedNetworkIdFilter, BridgeHubLocationFilter>
Expand Down
93 changes: 87 additions & 6 deletions bridges/modules/xcm-bridge-hub-router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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<Self::RuntimeOrigin, BridgeIdOf<Self, I>>;

/// Additional fee that is paid for every byte of the outbound message.
/// See `calculate_message_size_fees` for more details.
type ByteFee: Get<u128>;
Expand Down Expand Up @@ -204,9 +208,35 @@ pub mod pallet {
}
}

#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Notification about congested bridge queue.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::report_bridge_status())]
pub fn report_bridge_status(
origin: OriginFor<T>,
bridge_id: BridgeIdOf<T, I>,
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<T: Config<I>, I: 'static = ()> = StorageMap<_, Identity, BridgeIdOf<T, I>, BridgeState, OptionQuery>;
pub type Bridges<T: Config<I>, I: 'static = ()> = StorageMap<_, Blake2_128Concat, BridgeIdOf<T, I>, BridgeState, OptionQuery>;

impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Called when new message is sent to the `dest` (queued to local outbound XCM queue).
Expand Down Expand Up @@ -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<T, I>, is_congested: bool) {
Bridges::<T, I>::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]
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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::<TestRuntime, ()>(&dest) {
old_delivery_fee_factor = bridge_state.delivery_fee_factor;
last_delivery_fee_factor = bridge_state.delivery_fee_factor;
XcmBridgeHubRouter::on_initialize(One::one());
}

Expand All @@ -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,
}),
Expand Down Expand Up @@ -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::<TestRuntime, ()>(&dest).is_none());
report_bridge_status(bridge_id, false);
assert!(get_bridge_state_for::<TestRuntime, ()>(&dest).is_none());

// make congested
report_bridge_status(bridge_id, true);
assert_eq!(get_bridge_state_for::<TestRuntime, ()>(&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::<TestRuntime, ()>(&dest), Some(BridgeState {
delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR,
is_congested: false,
}));
});
}
}
6 changes: 4 additions & 2 deletions bridges/modules/xcm-bridge-hub-router/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -43,8 +44,8 @@ pub const BYTE_FEE: u128 = 1_000;
construct_runtime! {
pub enum TestRuntime
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
XcmBridgeHubRouter: pallet_xcm_bridge_hub_router::{Pallet, Storage, Event<T>},
System: frame_system,
XcmBridgeHubRouter: pallet_xcm_bridge_hub_router,
}
}

Expand Down Expand Up @@ -104,6 +105,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
>;

type BridgeIdResolver = EveryDestinationToSameBridgeIdResolver;
type BridgeHubOrigin = EnsureRoot<u64>;

type ByteFee = ConstU128<BYTE_FEE>;
type FeeAsset = BridgeFeeAsset;
Expand Down
27 changes: 27 additions & 0 deletions bridges/modules/xcm-bridge-hub-router/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -94,6 +95,19 @@ impl<T: frame_system::Config> WeightInfo for BridgeWeight<T> {
// 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
Expand Down Expand Up @@ -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))
}
}
Loading

0 comments on commit c78e707

Please sign in to comment.