// 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 code that allows to use the pezpallet (`pezpallet-xcm-bridge-hub`) as XCM message //! exporter at the sending bridge hub. Internally, it just enqueues outbound blob //! in the messages pezpallet queue. //! //! This code is executed at the source bridge hub. use crate::{Config, Pezpallet, LOG_TARGET}; use crate::{BridgeOf, Bridges}; use bp_messages::{ source_chain::{MessagesBridge, OnMessagesDelivered}, MessageNonce, }; use bp_xcm_bridge_hub::{BridgeId, BridgeState, LocalXcmChannelManager, XcmAsPlainPayload}; use pezframe_support::{ensure, traits::Get}; use pezpallet_bridge_messages::{ Config as BridgeMessagesConfig, Error, Pezpallet as BridgeMessagesPallet, }; use xcm::prelude::*; use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter}; use xcm_executor::traits::ExportXcm; /// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we /// suspend a bridge. const OUTBOUND_LANE_CONGESTED_THRESHOLD: MessageNonce = 8_192; /// After we have suspended the bridge, we wait until number of messages in the outbound bridge /// queue drops to this count, before sending resuming the bridge. const OUTBOUND_LANE_UNCONGESTED_THRESHOLD: MessageNonce = 1_024; /// An easy way to access `HaulBlobExporter`. pub type PalletAsHaulBlobExporter = HaulBlobExporter< DummyHaulBlob, >::BridgedNetwork, >::DestinationVersion, >::MessageExportPrice, >; /// An easy way to access associated messages pezpallet. type MessagesPallet = BridgeMessagesPallet>::BridgeMessagesPalletInstance>; impl, I: 'static> ExportXcm for Pezpallet where T: BridgeMessagesConfig, { type Ticket = ( BridgeId, BridgeOf, as MessagesBridge>::SendMessageArgs, XcmHash, ); fn validate( network: NetworkId, channel: u32, universal_source: &mut Option, destination: &mut Option, message: &mut Option>, ) -> Result<(Self::Ticket, Assets), SendError> { tracing::trace!( target: LOG_TARGET, ?network, ?channel, ?universal_source, ?destination, "Validate for network" ); // `HaulBlobExporter` may consume the `universal_source` and `destination` arguments, so // let's save them before let bridge_origin_universal_location = universal_source.clone().ok_or(SendError::MissingArgument)?; // Note: watch out this is `ExportMessage::destination`, which is relative to the `network`, // which means it does not contain `GlobalConsensus`, We need to find `BridgeId` with // `Self::bridge_locations` which requires **universal** location for destination. let bridge_destination_universal_location = { let dest = destination.clone().ok_or(SendError::MissingArgument)?; match dest.global_consensus() { Ok(dest_network) => { tracing::trace!( target: LOG_TARGET, ?dest, ?dest_network, ?network, "Destination is already universal, checking if matches: {:?}", dest_network == network ); ensure!(dest_network == network, SendError::NotApplicable); // ok, `dest` looks like a universal location, so let's use it dest }, Err(_) => { // `dest` is not a universal location, so we need to prepend it with // `GlobalConsensus`. dest.pushed_front_with(GlobalConsensus(network)).map_err(|error_data| { tracing::error!( target: LOG_TARGET, error=?error_data, "Destination is not a universal and prepending failed!" ); SendError::NotApplicable })? }, } }; // prepare the origin relative location let bridge_origin_relative_location = bridge_origin_universal_location.relative_to(&T::UniversalLocation::get()); // then we are able to compute the `BridgeId` and find `LaneId` used to send messages let locations = Self::bridge_locations( bridge_origin_relative_location, bridge_destination_universal_location.into(), ) .map_err(|e| { tracing::error!( target: LOG_TARGET, error=?e, "Validate `bridge_locations` with error" ); SendError::NotApplicable })?; let bridge = Self::bridge(locations.bridge_id()).ok_or_else(|| { tracing::error!( target: LOG_TARGET, bridge_origin_relative_location=?locations.bridge_origin_relative_location(), bridge_destination_universal_location=?locations.bridge_destination_universal_location(), "No opened bridge for requested" ); SendError::NotApplicable })?; // check if we are able to route the message. We use existing `HaulBlobExporter` for that. // It will make all required changes and will encode message properly, so that the // `DispatchBlob` at the bridged bridge hub will be able to decode it let ((blob, id), price) = PalletAsHaulBlobExporter::::validate( network, channel, universal_source, destination, message, )?; let bridge_message = MessagesPallet::::validate_message(bridge.lane_id, &blob) .map_err(|e| { match e { Error::LanesManager(ref ei) => { tracing::error!(target: LOG_TARGET, error=?ei, "LanesManager") }, Error::MessageRejectedByPallet(ref ei) => { tracing::error!(target: LOG_TARGET, error=?ei, "MessageRejectedByPallet") }, Error::ReceptionConfirmation(ref ei) => { tracing::error!(target: LOG_TARGET, error=?ei, "ReceptionConfirmation") }, _ => (), }; tracing::error!( target: LOG_TARGET, error=?e, topic_id=?id, bridge_id=?locations, lane_id=?bridge.lane_id, "XCM message cannot be exported" ); SendError::Transport("BridgeValidateError") })?; Ok(((*locations.bridge_id(), bridge, bridge_message, id), price)) } fn deliver( (bridge_id, bridge, bridge_message, id): Self::Ticket, ) -> Result { let artifacts = MessagesPallet::::send_message(bridge_message); tracing::info!( target: LOG_TARGET, topic_id=?id, bridge_id=?bridge_id, lane_id=?bridge.lane_id, nonce=%artifacts.nonce, "XCM message has been enqueued" ); // maybe we need switch to congested state Self::on_bridge_message_enqueued(bridge_id, bridge, artifacts.enqueued_messages); Ok(id) } } impl, I: 'static> OnMessagesDelivered for Pezpallet { fn on_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) { Self::on_bridge_messages_delivered(lane_id, enqueued_messages); } } impl, I: 'static> Pezpallet { /// Called when new message is pushed onto outbound bridge queue. fn on_bridge_message_enqueued( bridge_id: BridgeId, bridge: BridgeOf, enqueued_messages: MessageNonce, ) { // if the bridge queue is not congested, we don't want to do anything let is_congested = enqueued_messages > OUTBOUND_LANE_CONGESTED_THRESHOLD; if !is_congested { return; } // TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/83 we either need fishermens // to watch this rule violation (suspended, but keep sending new messages), or we need a // hard limit for that like other XCM queues have // check if the lane is already suspended. If it is, do nothing. We still accept new // messages to the suspended bridge, hoping that it'll be actually resumed soon if bridge.state == BridgeState::Suspended { return; } // else - suspend the bridge let result_bridge_origin_relative_location = (*bridge.bridge_origin_relative_location).clone().try_into(); let bridge_origin_relative_location = match &result_bridge_origin_relative_location { Ok(bridge_origin_relative_location) => bridge_origin_relative_location, Err(_) => { tracing::debug!( target: LOG_TARGET, ?bridge_id, origin_location=?bridge.bridge_origin_relative_location, "Failed to convert" ); return; }, }; let suspend_result = T::LocalXcmChannelManager::suspend_bridge(bridge_origin_relative_location, bridge_id); match suspend_result { Ok(_) => { tracing::debug!( target: LOG_TARGET, ?bridge_id, originated_by=?bridge.bridge_origin_relative_location, "Suspended" ); }, Err(e) => { tracing::debug!( target: LOG_TARGET, error=?e, ?bridge_id, originated_by=?bridge.bridge_origin_relative_location, "Failed to suspended" ); return; }, } // and remember that we have suspended the bridge Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); } /// Must be called whenever we receive a message delivery confirmation. fn on_bridge_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) { // if the bridge queue is still congested, we don't want to do anything let is_congested = enqueued_messages > OUTBOUND_LANE_UNCONGESTED_THRESHOLD; if is_congested { return; } // if we have not suspended the bridge before (or it is closed), we don't want to do // anything let (bridge_id, bridge) = match Self::bridge_by_lane_id(&lane_id) { Some(bridge) if bridge.1.state == BridgeState::Suspended => bridge, _ => { // if there is no bridge or it has been closed, then we don't need to send resume // signal to the local origin - it has closed bridge itself, so it should have // alrady pruned everything else return; }, }; // else - resume the bridge let bridge_origin_relative_location = (*bridge.bridge_origin_relative_location).try_into(); let bridge_origin_relative_location = match bridge_origin_relative_location { Ok(bridge_origin_relative_location) => bridge_origin_relative_location, Err(e) => { tracing::debug!( target: LOG_TARGET, error=?e, ?bridge_id, ?lane_id, "Failed to convert", ); return; }, }; let resume_result = T::LocalXcmChannelManager::resume_bridge(&bridge_origin_relative_location, bridge_id); match resume_result { Ok(_) => { tracing::debug!( target: LOG_TARGET, ?bridge_id, ?lane_id, originated_by=?bridge_origin_relative_location, "Resumed", ); }, Err(e) => { tracing::debug!( target: LOG_TARGET, error=?e, ?bridge_id, ?lane_id, originated_by=?bridge_origin_relative_location, "Failed to resume" ); return; }, } // and forget that we have previously suspended the bridge Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Opened; }); } } /// Dummy implementation of the `HaulBlob` trait that is never called. /// /// We are using `HaulBlobExporter`, which requires `HaulBlob` implementation. It assumes that /// there's a single channel between two bridge hubs - `HaulBlob` only accepts the blob and nothing /// else. But bridge messages pezpallet may have a dedicated channel (lane) for every pair of bridged /// chains. So we are using our own `ExportXcm` implementation, but to utilize `HaulBlobExporter` we /// still need this `DummyHaulBlob`. pub struct DummyHaulBlob; impl HaulBlob for DummyHaulBlob { fn haul_blob(_blob: XcmAsPlainPayload) -> Result<(), HaulBlobError> { Err(HaulBlobError::Transport("DummyHaulBlob")) } } #[cfg(test)] mod tests { use super::*; use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf}; use pezbp_runtime::RangeInclusiveExt; use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState}; use pezframe_support::{assert_ok, traits::EnsureOrigin}; use pezpallet_bridge_messages::InboundLaneStorage; use xcm_builder::{NetworkExportTable, UnpaidRemoteExporter}; use xcm_executor::traits::{export_xcm, ConvertLocation}; fn universal_source() -> InteriorLocation { SiblingUniversalLocation::get() } fn bridged_relative_destination() -> InteriorLocation { BridgedRelativeDestination::get() } fn bridged_universal_destination() -> InteriorLocation { BridgedUniversalDestination::get() } fn open_lane(origin: RuntimeOrigin) -> (BridgeLocations, TestLaneIdType) { // open expected outbound lane let with = bridged_asset_hub_universal_location(); let locations = XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap(); let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap(); if !Bridges::::contains_key(locations.bridge_id()) { // insert bridge Bridges::::insert( locations.bridge_id(), Bridge { bridge_origin_relative_location: Box::new(SiblingLocation::get().into()), bridge_origin_universal_location: Box::new( locations.bridge_origin_universal_location().clone().into(), ), bridge_destination_universal_location: Box::new( locations.bridge_destination_universal_location().clone().into(), ), state: BridgeState::Opened, bridge_owner_account: LocationToAccountId::convert_location( locations.bridge_origin_relative_location(), ) .expect("valid accountId"), deposit: 0, lane_id, }, ); LaneToBridge::::insert(lane_id, locations.bridge_id()); // create lanes let lanes_manager = LanesManagerOf::::new(); if lanes_manager.create_inbound_lane(lane_id).is_ok() { assert_eq!( 0, lanes_manager .active_inbound_lane(lane_id) .unwrap() .storage() .data() .last_confirmed_nonce ); } if lanes_manager.create_outbound_lane(lane_id).is_ok() { assert!(lanes_manager .active_outbound_lane(lane_id) .unwrap() .queued_messages() .is_empty()); } } assert_ok!(XcmOverBridge::do_try_state()); (*locations, lane_id) } fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) { let (locations, lane_id) = open_lane(OpenBridgeOrigin::sibling_teyrchain_origin()); // now let's try to enqueue message using our `ExportXcm` implementation export_xcm::( BridgedRelayNetwork::get(), 0, locations.bridge_origin_universal_location().clone(), locations.bridge_destination_universal_location().clone(), vec![Instruction::ClearOrigin].into(), ) .unwrap(); (*locations.bridge_id(), lane_id) } #[test] fn exporter_works() { run_test(|| { let (_, lane_id) = open_lane_and_send_regular_message(); // double check that the message has been pushed to the expected lane // (it should already been checked during `send_message` call) assert!(!LanesManagerOf::::new() .active_outbound_lane(lane_id) .unwrap() .queued_messages() .is_empty()); }); } #[test] fn exporter_does_not_suspend_the_bridge_if_outbound_bridge_queue_is_not_congested() { run_test(|| { let (bridge_id, _) = open_lane_and_send_regular_message(); assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } #[test] fn exporter_does_not_suspend_the_bridge_if_it_is_already_suspended() { run_test(|| { let (bridge_id, _) = open_lane_and_send_regular_message(); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD { open_lane_and_send_regular_message(); } open_lane_and_send_regular_message(); assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); }); } #[test] fn exporter_suspends_the_bridge_if_outbound_bridge_queue_is_congested() { run_test(|| { let (bridge_id, _) = open_lane_and_send_regular_message(); for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD { open_lane_and_send_regular_message(); } assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); open_lane_and_send_regular_message(); assert!(TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } #[test] fn bridge_is_not_resumed_if_outbound_bridge_queue_is_still_congested() { run_test(|| { let (bridge_id, lane_id) = open_lane_and_send_regular_message(); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); XcmOverBridge::on_bridge_messages_delivered( lane_id, OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1, ); assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } #[test] fn bridge_is_not_resumed_if_it_was_not_suspended_before() { run_test(|| { let (bridge_id, lane_id) = open_lane_and_send_regular_message(); XcmOverBridge::on_bridge_messages_delivered( lane_id, OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } #[test] fn bridge_is_resumed_when_enough_messages_are_delivered() { run_test(|| { let (bridge_id, lane_id) = open_lane_and_send_regular_message(); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); XcmOverBridge::on_bridge_messages_delivered( lane_id, OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); assert!(TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } #[test] fn export_fails_if_argument_is_missing() { run_test(|| { assert_eq!( XcmOverBridge::validate( BridgedRelayNetwork::get(), 0, &mut None, &mut Some(bridged_relative_destination()), &mut Some(Vec::new().into()), ), Err(SendError::MissingArgument), ); assert_eq!( XcmOverBridge::validate( BridgedRelayNetwork::get(), 0, &mut Some(universal_source()), &mut None, &mut Some(Vec::new().into()), ), Err(SendError::MissingArgument), ); }) } #[test] fn exporter_computes_correct_lane_id() { run_test(|| { assert_ne!(bridged_universal_destination(), bridged_relative_destination()); let locations = BridgeLocations::bridge_locations( UniversalLocation::get(), SiblingLocation::get(), bridged_universal_destination(), BridgedRelayNetwork::get(), ) .unwrap(); let expected_bridge_id = locations.bridge_id(); let expected_lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap(); if LanesManagerOf::::new() .create_outbound_lane(expected_lane_id) .is_ok() { Bridges::::insert( expected_bridge_id, Bridge { bridge_origin_relative_location: Box::new( locations.bridge_origin_relative_location().clone().into(), ), bridge_origin_universal_location: Box::new( locations.bridge_origin_universal_location().clone().into(), ), bridge_destination_universal_location: Box::new( locations.bridge_destination_universal_location().clone().into(), ), state: BridgeState::Opened, bridge_owner_account: [0u8; 32].into(), deposit: 0, lane_id: expected_lane_id, }, ); } let ticket = XcmOverBridge::validate( BridgedRelayNetwork::get(), 0, &mut Some(universal_source()), // Note: The `ExportMessage` expects relative `InteriorLocation` in the // `BridgedRelayNetwork`. &mut Some(bridged_relative_destination()), &mut Some(Vec::new().into()), ) .unwrap() .0; assert_eq!(&ticket.0, expected_bridge_id); assert_eq!(ticket.1.lane_id, expected_lane_id); }); } #[test] fn exporter_is_compatible_with_pallet_xcm_bridge_hub_router() { run_test(|| { // valid routable destination let dest = Location::new(2, BridgedUniversalDestination::get()); // open bridge let origin = OpenBridgeOrigin::sibling_teyrchain_origin(); let origin_as_location = OpenBridgeOriginOf::::try_origin(origin.clone()).unwrap(); let (_, expected_lane_id) = open_lane(origin); // check before - no messages assert_eq!( pezpallet_bridge_messages::Pezpallet::::outbound_lane_data( expected_lane_id ) .unwrap() .queued_messages() .saturating_len(), 0 ); // send `ExportMessage(message)` by `UnpaidRemoteExporter`. ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location); assert_ok!(send_xcm::< UnpaidRemoteExporter< NetworkExportTable, ExecuteXcmOverSendXcm, UniversalLocation, >, >(dest.clone(), Xcm::<()>::default())); // we need to set `UniversalLocation` for `sibling_teyrchain_origin` for // `XcmOverBridgeWrappedWithExportMessageRouterInstance`. ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get())); // send `ExportMessage(message)` by `pezpallet_xcm_bridge_hub_router`. ExecuteXcmOverSendXcm::set_origin_for_execute(SiblingLocation::get()); assert_ok!(send_xcm::( dest.clone(), Xcm::<()>::default() )); // check after - a message ready to be relayed assert_eq!( pezpallet_bridge_messages::Pezpallet::::outbound_lane_data( expected_lane_id ) .unwrap() .queued_messages() .saturating_len(), 2 ); }) } #[test] fn validate_works() { run_test(|| { let xcm: Xcm<()> = vec![ClearOrigin].into(); // check that router does not consume when `NotApplicable` let mut xcm_wrapper = Some(xcm.clone()); let mut universal_source_wrapper = Some(universal_source()); // wrong `NetworkId` let mut dest_wrapper = Some(bridged_relative_destination()); assert_eq!( XcmOverBridge::validate( NetworkId::ByGenesis([0; 32]), 0, &mut universal_source_wrapper, &mut dest_wrapper, &mut xcm_wrapper, ), Err(SendError::NotApplicable), ); // dest and xcm is NOT consumed and untouched assert_eq!(&Some(xcm.clone()), &xcm_wrapper); assert_eq!(&Some(universal_source()), &universal_source_wrapper); assert_eq!(&Some(bridged_relative_destination()), &dest_wrapper); // dest starts with wrong `NetworkId` let mut invalid_dest_wrapper = Some( [GlobalConsensus(NetworkId::ByGenesis([0; 32])), Teyrchain(BRIDGED_ASSET_HUB_ID)] .into(), ); assert_eq!( XcmOverBridge::validate( BridgedRelayNetwork::get(), 0, &mut Some(universal_source()), &mut invalid_dest_wrapper, &mut xcm_wrapper, ), Err(SendError::NotApplicable), ); // dest and xcm is NOT consumed and untouched assert_eq!(&Some(xcm.clone()), &xcm_wrapper); assert_eq!(&Some(universal_source()), &universal_source_wrapper); assert_eq!( &Some( [ GlobalConsensus(NetworkId::ByGenesis([0; 32]),), Teyrchain(BRIDGED_ASSET_HUB_ID) ] .into() ), &invalid_dest_wrapper ); // no opened lane for dest let mut dest_without_lane_wrapper = Some([GlobalConsensus(BridgedRelayNetwork::get()), Teyrchain(5679)].into()); assert_eq!( XcmOverBridge::validate( BridgedRelayNetwork::get(), 0, &mut Some(universal_source()), &mut dest_without_lane_wrapper, &mut xcm_wrapper, ), Err(SendError::NotApplicable), ); // dest and xcm is NOT consumed and untouched assert_eq!(&Some(xcm.clone()), &xcm_wrapper); assert_eq!(&Some(universal_source()), &universal_source_wrapper); assert_eq!( &Some([GlobalConsensus(BridgedRelayNetwork::get(),), Teyrchain(5679)].into()), &dest_without_lane_wrapper ); // ok let _ = open_lane(OpenBridgeOrigin::sibling_teyrchain_origin()); let mut dest_wrapper = Some(bridged_relative_destination()); assert_ok!(XcmOverBridge::validate( BridgedRelayNetwork::get(), 0, &mut Some(universal_source()), &mut dest_wrapper, &mut xcm_wrapper, )); // dest and xcm IS consumed assert_eq!(None, xcm_wrapper); assert_eq!(&Some(universal_source()), &universal_source_wrapper); assert_eq!(None, dest_wrapper); }); } #[test] fn congestion_with_pallet_xcm_bridge_hub_router_works() { run_test(|| { // valid routable destination let dest = Location::new(2, BridgedUniversalDestination::get()); fn router_bridge_state() -> pezpallet_xcm_bridge_hub_router::BridgeState { pezpallet_xcm_bridge_hub_router::Bridge::< TestRuntime, XcmOverBridgeWrappedWithExportMessageRouterInstance, >::get() } // open two bridges let origin = OpenBridgeOrigin::sibling_teyrchain_origin(); let origin_as_location = OpenBridgeOriginOf::::try_origin(origin.clone()).unwrap(); let (bridge_1, expected_lane_id_1) = open_lane(origin); // we need to set `UniversalLocation` for `sibling_teyrchain_origin` for // `XcmOverBridgeWrappedWithExportMessageRouterInstance`. ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get())); // check before // bridges are opened assert_eq!( XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, BridgeState::Opened ); // the router is uncongested assert!(!router_bridge_state().is_congested); assert!(!TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id())); assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); // make bridges congested with sending too much messages for _ in 1..(OUTBOUND_LANE_CONGESTED_THRESHOLD + 2) { // send `ExportMessage(message)` by `pezpallet_xcm_bridge_hub_router`. ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location.clone()); assert_ok!(send_xcm::( dest.clone(), Xcm::<()>::default() )); } // checks after // bridges are suspended assert_eq!( XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, BridgeState::Suspended, ); // the router is congested assert!(router_bridge_state().is_congested); assert!(TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id())); assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); // make bridges uncongested to trigger resume signal XcmOverBridge::on_bridge_messages_delivered( expected_lane_id_1, OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); // bridge is again opened assert_eq!( XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, BridgeState::Opened ); // the router is uncongested assert!(!router_bridge_state().is_congested); assert!(TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); }) } }