// 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()));
})
}
}