fix: Complete snowbridge pezpallet rebrand and critical bug fixes
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The code that allows to use the pezpallet (`pezpallet-xcm-bridge-hub`) as inbound
|
||||
//! bridge messages dispatcher. Internally, it just forwards inbound blob to the
|
||||
//! XCM-level blob dispatcher, which pushes message to some other queue (e.g.
|
||||
//! to HRMP queue with the sibling target chain).
|
||||
//!
|
||||
//! This code is executed at the target bridge hub.
|
||||
|
||||
use crate::{Config, Pezpallet, LOG_TARGET};
|
||||
|
||||
use bp_messages::target_chain::{DispatchMessage, MessageDispatch};
|
||||
use pezbp_runtime::messages::MessageDispatchResult;
|
||||
use bp_xcm_bridge_hub::{LocalXcmChannelManager, XcmAsPlainPayload};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::{weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound};
|
||||
use pezpallet_bridge_messages::{Config as BridgeMessagesConfig, WeightInfoExt};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::SaturatedConversion;
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{DispatchBlob, DispatchBlobError};
|
||||
|
||||
/// Message dispatch result type for single message.
|
||||
#[derive(
|
||||
CloneNoBound,
|
||||
EqNoBound,
|
||||
PartialEqNoBound,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Debug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub enum XcmBlobMessageDispatchResult {
|
||||
/// We've been unable to decode message payload.
|
||||
InvalidPayload,
|
||||
/// Message has been dispatched.
|
||||
Dispatched,
|
||||
/// Message has **NOT** been dispatched because of given error.
|
||||
NotDispatched(#[codec(skip)] Option<DispatchBlobError>),
|
||||
}
|
||||
|
||||
/// An easy way to access associated messages pezpallet weights.
|
||||
type MessagesPalletWeights<T, I> =
|
||||
<T as BridgeMessagesConfig<<T as Config<I>>::BridgeMessagesPalletInstance>>::WeightInfo;
|
||||
|
||||
impl<T: Config<I>, I: 'static> MessageDispatch for Pezpallet<T, I>
|
||||
where
|
||||
T: BridgeMessagesConfig<T::BridgeMessagesPalletInstance, InboundPayload = XcmAsPlainPayload>,
|
||||
{
|
||||
type DispatchPayload = XcmAsPlainPayload;
|
||||
type DispatchLevelResult = XcmBlobMessageDispatchResult;
|
||||
type LaneId = T::LaneId;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
Pezpallet::<T, I>::bridge_by_lane_id(&lane)
|
||||
.and_then(|(_, bridge)| (*bridge.bridge_origin_relative_location).try_into().ok())
|
||||
.map(|recipient: Location| !T::LocalXcmChannelManager::is_congested(&recipient))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
match message.data.payload {
|
||||
Ok(ref payload) => {
|
||||
let payload_size = payload.encoded_size().saturated_into();
|
||||
MessagesPalletWeights::<T, I>::message_dispatch_weight(payload_size)
|
||||
},
|
||||
Err(_) => Weight::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
message: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
let payload = match message.data.payload {
|
||||
Ok(payload) => payload,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - payload error"
|
||||
);
|
||||
return MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
|
||||
};
|
||||
},
|
||||
};
|
||||
let dispatch_level_result = match T::BlobDispatcher::dispatch_blob(payload) {
|
||||
Ok(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - `DispatchBlob::dispatch_blob` was ok"
|
||||
);
|
||||
XcmBlobMessageDispatchResult::Dispatched
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - `DispatchBlob::dispatch_blob` failed"
|
||||
);
|
||||
XcmBlobMessageDispatchResult::NotDispatched(Some(e))
|
||||
},
|
||||
};
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf};
|
||||
|
||||
use bp_messages::{target_chain::DispatchMessageData, LaneIdType, MessageKey};
|
||||
use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
|
||||
use pezframe_support::assert_ok;
|
||||
use pezpallet_bridge_messages::InboundLaneStorage;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
fn bridge() -> (Box<BridgeLocations>, TestLaneIdType) {
|
||||
let origin = OpenBridgeOrigin::sibling_teyrchain_origin();
|
||||
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();
|
||||
(locations, lane_id)
|
||||
}
|
||||
|
||||
fn run_test_with_opened_bridge(test: impl FnOnce()) {
|
||||
run_test(|| {
|
||||
let (bridge, lane_id) = bridge();
|
||||
|
||||
if !Bridges::<TestRuntime, ()>::contains_key(bridge.bridge_id()) {
|
||||
// insert bridge
|
||||
Bridges::<TestRuntime, ()>::insert(
|
||||
bridge.bridge_id(),
|
||||
Bridge {
|
||||
bridge_origin_relative_location: Box::new(
|
||||
bridge.bridge_origin_relative_location().clone().into(),
|
||||
),
|
||||
bridge_origin_universal_location: Box::new(
|
||||
bridge.bridge_origin_universal_location().clone().into(),
|
||||
),
|
||||
bridge_destination_universal_location: Box::new(
|
||||
bridge.bridge_destination_universal_location().clone().into(),
|
||||
),
|
||||
state: BridgeState::Opened,
|
||||
bridge_owner_account: LocationToAccountId::convert_location(
|
||||
bridge.bridge_origin_relative_location(),
|
||||
)
|
||||
.expect("valid accountId"),
|
||||
deposit: 0,
|
||||
lane_id,
|
||||
},
|
||||
);
|
||||
LaneToBridge::<TestRuntime, ()>::insert(lane_id, bridge.bridge_id());
|
||||
|
||||
// create lanes
|
||||
let lanes_manager = LanesManagerOf::<TestRuntime, ()>::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());
|
||||
|
||||
test();
|
||||
});
|
||||
}
|
||||
|
||||
fn invalid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
|
||||
DispatchMessage {
|
||||
key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
|
||||
data: DispatchMessageData { payload: Err(codec::Error::from("test")) },
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
|
||||
DispatchMessage {
|
||||
key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
|
||||
data: DispatchMessageData { payload: Ok(vec![42]) },
|
||||
}
|
||||
}
|
||||
|
||||
#[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));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatcher_is_active_when_channel_with_target_chain_is_not_congested() {
|
||||
run_test_with_opened_bridge(|| {
|
||||
assert!(XcmOverBridge::is_active(bridge().1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_weight_is_zero_if_we_have_failed_to_decode_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(XcmOverBridge::dispatch_weight(&mut invalid_message()), Weight::zero());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_weight_is_non_zero_if_we_have_decoded_message() {
|
||||
run_test(|| {
|
||||
assert_ne!(XcmOverBridge::dispatch_weight(&mut valid_message()), Weight::zero());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_is_not_dispatched_when_we_have_failed_to_decode_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
XcmOverBridge::dispatch(invalid_message()),
|
||||
MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
|
||||
},
|
||||
);
|
||||
assert!(!TestBlobDispatcher::is_dispatched());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_is_dispatched_when_we_have_decoded_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
XcmOverBridge::dispatch(valid_message()),
|
||||
MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::Dispatched,
|
||||
},
|
||||
);
|
||||
assert!(TestBlobDispatcher::is_dispatched());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,873 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<T, I> = HaulBlobExporter<
|
||||
DummyHaulBlob,
|
||||
<T as Config<I>>::BridgedNetwork,
|
||||
<T as Config<I>>::DestinationVersion,
|
||||
<T as Config<I>>::MessageExportPrice,
|
||||
>;
|
||||
/// An easy way to access associated messages pezpallet.
|
||||
type MessagesPallet<T, I> = BridgeMessagesPallet<T, <T as Config<I>>::BridgeMessagesPalletInstance>;
|
||||
|
||||
impl<T: Config<I>, I: 'static> ExportXcm for Pezpallet<T, I>
|
||||
where
|
||||
T: BridgeMessagesConfig<T::BridgeMessagesPalletInstance, OutboundPayload = XcmAsPlainPayload>,
|
||||
{
|
||||
type Ticket = (
|
||||
BridgeId,
|
||||
BridgeOf<T, I>,
|
||||
<MessagesPallet<T, I> as MessagesBridge<T::OutboundPayload, T::LaneId>>::SendMessageArgs,
|
||||
XcmHash,
|
||||
);
|
||||
|
||||
fn validate(
|
||||
network: NetworkId,
|
||||
channel: u32,
|
||||
universal_source: &mut Option<InteriorLocation>,
|
||||
destination: &mut Option<InteriorLocation>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> 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::<T, I>::validate(
|
||||
network,
|
||||
channel,
|
||||
universal_source,
|
||||
destination,
|
||||
message,
|
||||
)?;
|
||||
|
||||
let bridge_message = MessagesPallet::<T, I>::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<XcmHash, SendError> {
|
||||
let artifacts = MessagesPallet::<T, I>::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<T: Config<I>, I: 'static> OnMessagesDelivered<T::LaneId> for Pezpallet<T, I> {
|
||||
fn on_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) {
|
||||
Self::on_bridge_messages_delivered(lane_id, enqueued_messages);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Called when new message is pushed onto outbound bridge queue.
|
||||
fn on_bridge_message_enqueued(
|
||||
bridge_id: BridgeId,
|
||||
bridge: BridgeOf<T, I>,
|
||||
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::<T, I>::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::<T, I>::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::<TestRuntime, ()>::contains_key(locations.bridge_id()) {
|
||||
// insert bridge
|
||||
Bridges::<TestRuntime, ()>::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::<TestRuntime, ()>::insert(lane_id, locations.bridge_id());
|
||||
|
||||
// create lanes
|
||||
let lanes_manager = LanesManagerOf::<TestRuntime, ()>::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::<XcmOverBridge>(
|
||||
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::<TestRuntime, ()>::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::<TestRuntime, ()>::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::<TestRuntime, ()>::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::<TestRuntime, ()>::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::<TestRuntime, ()>::new()
|
||||
.create_outbound_lane(expected_lane_id)
|
||||
.is_ok()
|
||||
{
|
||||
Bridges::<TestRuntime, ()>::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::<TestRuntime, ()>::try_origin(origin.clone()).unwrap();
|
||||
let (_, expected_lane_id) = open_lane(origin);
|
||||
|
||||
// check before - no messages
|
||||
assert_eq!(
|
||||
pezpallet_bridge_messages::Pezpallet::<TestRuntime, ()>::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<BridgeTable>,
|
||||
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::<XcmOverBridgeWrappedWithExportMessageRouter>(
|
||||
dest.clone(),
|
||||
Xcm::<()>::default()
|
||||
));
|
||||
|
||||
// check after - a message ready to be relayed
|
||||
assert_eq!(
|
||||
pezpallet_bridge_messages::Pezpallet::<TestRuntime, ()>::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::<TestRuntime, ()>::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::<XcmOverBridgeWrappedWithExportMessageRouter>(
|
||||
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()));
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,154 @@
|
||||
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A module that is responsible for migration of storage.
|
||||
|
||||
use crate::{Config, Pezpallet, LOG_TARGET};
|
||||
use pezframe_support::{
|
||||
traits::{Get, OnRuntimeUpgrade, StorageVersion},
|
||||
weights::Weight,
|
||||
};
|
||||
use xcm::prelude::{InteriorLocation, Location};
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
|
||||
|
||||
/// This migration does not modify storage but can be used to open a bridge and link it to the
|
||||
/// specified LaneId. This is useful when we want to open a bridge and use a custom LaneId instead
|
||||
/// of the pre-calculated one provided by the `fn open_bridge extrinsic`.
|
||||
/// Or perhaps if you want to ensure that your runtime (e.g., for testing) always has an open
|
||||
/// bridge.
|
||||
pub struct OpenBridgeForLane<
|
||||
T,
|
||||
I,
|
||||
Lane,
|
||||
CreateLane,
|
||||
SourceRelativeLocation,
|
||||
BridgedUniversalLocation,
|
||||
>(
|
||||
core::marker::PhantomData<(
|
||||
T,
|
||||
I,
|
||||
Lane,
|
||||
CreateLane,
|
||||
SourceRelativeLocation,
|
||||
BridgedUniversalLocation,
|
||||
)>,
|
||||
);
|
||||
impl<
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
Lane: Get<T::LaneId>,
|
||||
CreateLane: Get<bool>,
|
||||
SourceRelativeLocation: Get<Location>,
|
||||
BridgedUniversalLocation: Get<InteriorLocation>,
|
||||
> OnRuntimeUpgrade
|
||||
for OpenBridgeForLane<T, I, Lane, CreateLane, SourceRelativeLocation, BridgedUniversalLocation>
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let bridge_origin_relative_location = SourceRelativeLocation::get();
|
||||
let bridge_destination_universal_location = BridgedUniversalLocation::get();
|
||||
let lane_id = Lane::get();
|
||||
let create_lane = CreateLane::get();
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
?create_lane,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - going to open bridge"
|
||||
);
|
||||
|
||||
let locations = match Pezpallet::<T, I>::bridge_locations(
|
||||
bridge_origin_relative_location.clone(),
|
||||
bridge_destination_universal_location.clone(),
|
||||
) {
|
||||
Ok(locations) => locations,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
"OpenBridgeForLane - on_runtime_upgrade failed to construct bridge_locations"
|
||||
);
|
||||
return T::DbWeight::get().reads(0);
|
||||
},
|
||||
};
|
||||
|
||||
// check if already exists
|
||||
if let Some((bridge_id, bridge)) = Pezpallet::<T, I>::bridge_by_lane_id(&lane_id) {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?bridge,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
"OpenBridgeForLane - already exist!"
|
||||
);
|
||||
if &bridge_id != locations.bridge_id() {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?bridge,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - check you parameters, because a different bridge exist for requested!"
|
||||
);
|
||||
}
|
||||
|
||||
return T::DbWeight::get().reads(2);
|
||||
}
|
||||
|
||||
if let Err(e) = Pezpallet::<T, I>::do_open_bridge(locations, lane_id, create_lane) {
|
||||
tracing::error!(target: LOG_TARGET, error=?e, "OpenBridgeForLane - do_open_bridge failed");
|
||||
T::DbWeight::get().reads(6)
|
||||
} else {
|
||||
tracing::info!(target: LOG_TARGET, "OpenBridgeForLane - do_open_bridge passed!");
|
||||
T::DbWeight::get().reads_writes(6, 4)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(_state: pezsp_std::vec::Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
let bridge_origin_relative_location = SourceRelativeLocation::get();
|
||||
let bridge_destination_universal_location = BridgedUniversalLocation::get();
|
||||
let lane_id = Lane::get();
|
||||
|
||||
// check that requested bridge is stored
|
||||
let Ok(locations) = Pezpallet::<T, I>::bridge_locations(
|
||||
bridge_origin_relative_location.clone(),
|
||||
bridge_destination_universal_location.clone(),
|
||||
) else {
|
||||
return Err(pezsp_runtime::DispatchError::Other("Invalid locations!"));
|
||||
};
|
||||
let Some((bridge_id, _)) = Pezpallet::<T, I>::bridge_by_lane_id(&lane_id) else {
|
||||
return Err(pezsp_runtime::DispatchError::Other("Missing bridge!"));
|
||||
};
|
||||
pezframe_support::ensure!(
|
||||
locations.bridge_id() == &bridge_id,
|
||||
"Bridge is not stored correctly!"
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - post_upgrade found opened bridge"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pezpallet_xcm_bridge_hub;
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, MessageDispatch},
|
||||
ChainWithMessages, HashedLaneId, MessageNonce,
|
||||
};
|
||||
use pezbp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf};
|
||||
use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{
|
||||
assert_ok, derive_impl, parameter_types,
|
||||
traits::{EnsureOrigin, Equals, Everything, Get, OriginTrait},
|
||||
weights::RuntimeDbWeight,
|
||||
};
|
||||
use pezkuwi_teyrchain_primitives::primitives::Sibling;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
testing::Header as BizinikiwiHeader,
|
||||
traits::{BlakeTwo256, ConstU128, ConstU32, IdentityLookup},
|
||||
AccountId32, BuildStorage, StateVersion,
|
||||
};
|
||||
use pezsp_std::cell::RefCell;
|
||||
use xcm::{latest::PEZKUWICHAIN_GENESIS_HASH, prelude::*};
|
||||
use xcm_builder::{
|
||||
AllowUnpaidExecutionFrom, DispatchBlob, DispatchBlobError, FixedWeightBounds,
|
||||
InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset,
|
||||
SiblingTeyrchainConvertsVia,
|
||||
};
|
||||
use xcm_executor::{traits::ConvertOrigin, XcmExecutor};
|
||||
|
||||
pub type AccountId = AccountId32;
|
||||
pub type Balance = u64;
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
pub type TestLaneIdType = HashedLaneId;
|
||||
|
||||
pub const SIBLING_ASSET_HUB_ID: u32 = 2001;
|
||||
pub const THIS_BRIDGE_HUB_ID: u32 = 2002;
|
||||
pub const BRIDGED_ASSET_HUB_ID: u32 = 1001;
|
||||
|
||||
pezframe_support::construct_runtime! {
|
||||
pub enum TestRuntime {
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Balances: pezpallet_balances::{Pezpallet, Event<T>},
|
||||
Messages: pezpallet_bridge_messages::{Pezpallet, Call, Event<T>},
|
||||
XcmOverBridge: pezpallet_xcm_bridge_hub::{Pezpallet, Call, HoldReason, Event<T>},
|
||||
XcmOverBridgeWrappedWithExportMessageRouter: pezpallet_xcm_bridge_hub_router = 57,
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
||||
pub const ExistentialDeposit: Balance = 1;
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type AccountId = AccountId;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
type Block = Block;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = TestMessagesWeights;
|
||||
|
||||
type ThisChain = ThisUnderlyingChain;
|
||||
type BridgedChain = BridgedUnderlyingChain;
|
||||
type BridgedHeaderChain = BridgedHeaderChain;
|
||||
|
||||
type OutboundPayload = Vec<u8>;
|
||||
type InboundPayload = Vec<u8>;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
type DeliveryPayments = ();
|
||||
type DeliveryConfirmationPayments = ();
|
||||
type OnMessagesDelivered = ();
|
||||
|
||||
type MessageDispatch = TestMessageDispatch;
|
||||
}
|
||||
|
||||
pub struct TestMessagesWeights;
|
||||
|
||||
impl pezpallet_bridge_messages::WeightInfo for TestMessagesWeights {
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_n_messages_proof(_: u32) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_n_bytes_message_proof(_: u32) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(_n: u32) -> Weight {
|
||||
Weight::from_parts(1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::WeightInfoExt for TestMessagesWeights {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const RelayNetwork: NetworkId = NetworkId::Kusama;
|
||||
pub UniversalLocation: InteriorLocation = [
|
||||
GlobalConsensus(RelayNetwork::get()),
|
||||
Teyrchain(THIS_BRIDGE_HUB_ID),
|
||||
].into();
|
||||
pub SiblingLocation: Location = Location::new(1, [Teyrchain(SIBLING_ASSET_HUB_ID)]);
|
||||
pub SiblingUniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Teyrchain(SIBLING_ASSET_HUB_ID)].into();
|
||||
|
||||
pub const BridgedRelayNetwork: NetworkId = NetworkId::ByGenesis([1; 32]);
|
||||
pub BridgedRelayNetworkLocation: Location = (Parent, GlobalConsensus(BridgedRelayNetwork::get())).into();
|
||||
pub BridgedRelativeDestination: InteriorLocation = [Teyrchain(BRIDGED_ASSET_HUB_ID)].into();
|
||||
pub BridgedUniversalDestination: InteriorLocation = [GlobalConsensus(BridgedRelayNetwork::get()), Teyrchain(BRIDGED_ASSET_HUB_ID)].into();
|
||||
pub const NonBridgedRelayNetwork: NetworkId = NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH);
|
||||
|
||||
pub const BridgeDeposit: Balance = 100_000;
|
||||
|
||||
// configuration for pezpallet_xcm_bridge_hub_router
|
||||
pub BridgeHubLocation: Location = Here.into();
|
||||
pub BridgeFeeAsset: AssetId = Location::here().into();
|
||||
pub BridgeTable: Vec<NetworkExportTableItem>
|
||||
= vec![
|
||||
NetworkExportTableItem::new(
|
||||
BridgedRelayNetwork::get(),
|
||||
None,
|
||||
BridgeHubLocation::get(),
|
||||
None
|
||||
)
|
||||
];
|
||||
pub UnitWeightCost: Weight = Weight::from_parts(10, 10);
|
||||
}
|
||||
|
||||
/// **Universal** `InteriorLocation` of bridged asset hub.
|
||||
pub fn bridged_asset_hub_universal_location() -> InteriorLocation {
|
||||
BridgedUniversalDestination::get()
|
||||
}
|
||||
|
||||
impl pezpallet_xcm_bridge_hub::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type BridgedNetwork = BridgedRelayNetworkLocation;
|
||||
type BridgeMessagesPalletInstance = ();
|
||||
|
||||
type MessageExportPrice = ();
|
||||
type DestinationVersion = AlwaysLatest;
|
||||
|
||||
type ForceOrigin = pezframe_system::EnsureNever<()>;
|
||||
type OpenBridgeOrigin = OpenBridgeOrigin;
|
||||
type BridgeOriginAccountIdConverter = LocationToAccountId;
|
||||
|
||||
type BridgeDeposit = BridgeDeposit;
|
||||
type Currency = Balances;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type AllowWithoutBridgeDeposit = Equals<ParentRelayChainLocation>;
|
||||
|
||||
type LocalXcmChannelManager = TestLocalXcmChannelManager;
|
||||
|
||||
type BlobDispatcher = TestBlobDispatcher;
|
||||
}
|
||||
|
||||
/// A router instance simulates a scenario where the router is deployed on a different chain than
|
||||
/// the `MessageExporter`. This means that the router sends an `ExportMessage`.
|
||||
pub type XcmOverBridgeWrappedWithExportMessageRouterInstance = ();
|
||||
impl pezpallet_xcm_bridge_hub_router::Config<XcmOverBridgeWrappedWithExportMessageRouterInstance>
|
||||
for TestRuntime
|
||||
{
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
|
||||
type UniversalLocation = ExportMessageOriginUniversalLocation;
|
||||
type SiblingBridgeHubLocation = BridgeHubLocation;
|
||||
type BridgedNetworkId = BridgedRelayNetwork;
|
||||
type Bridges = NetworkExportTable<BridgeTable>;
|
||||
type DestinationVersion = AlwaysLatest;
|
||||
|
||||
// We convert to root `here` location with `BridgeHubLocationXcmOriginAsRoot`
|
||||
type BridgeHubOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
// **Note**: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which
|
||||
// calls the `ExportXcm` implementation of `pezpallet_xcm_bridge_hub` as the
|
||||
// `MessageExporter`.
|
||||
type ToBridgeHubSender = ExecuteXcmOverSendXcm;
|
||||
type LocalXcmChannelManager = TestLocalXcmChannelManager;
|
||||
|
||||
type ByteFee = ConstU128<0>;
|
||||
type FeeAsset = BridgeFeeAsset;
|
||||
}
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl xcm_executor::Config for XcmConfig {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type XcmSender = ();
|
||||
type XcmEventEmitter = ();
|
||||
type AssetTransactor = ();
|
||||
type OriginConverter = BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>;
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = ();
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = AllowUnpaidExecutionFrom<Everything>;
|
||||
type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, ConstU32<100>>;
|
||||
type Trader = ();
|
||||
type ResponseHandler = ();
|
||||
type AssetTrap = ();
|
||||
type AssetClaims = ();
|
||||
type SubscriptionService = ();
|
||||
type PalletInstancesInfo = ();
|
||||
type MaxAssetsIntoHolding = ();
|
||||
type AssetLocker = ();
|
||||
type AssetExchanger = ();
|
||||
type FeeManager = ();
|
||||
// We just set `MessageExporter` as our `pezpallet_xcm_bridge_hub` instance.
|
||||
type MessageExporter = (XcmOverBridge,);
|
||||
type UniversalAliases = ();
|
||||
type CallDispatcher = RuntimeCall;
|
||||
type SafeCallFilter = Everything;
|
||||
type Aliasers = ();
|
||||
type TransactionalProcessor = ();
|
||||
type HrmpNewChannelOpenRequestHandler = ();
|
||||
type HrmpChannelAcceptedHandler = ();
|
||||
type HrmpChannelClosingHandler = ();
|
||||
type XcmRecorder = ();
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static EXECUTE_XCM_ORIGIN: RefCell<Option<Location>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// The `SendXcm` implementation directly executes XCM using `XcmExecutor`.
|
||||
///
|
||||
/// We ensure that the `ExportMessage` produced by `pezpallet_xcm_bridge_hub_router` is compatible with
|
||||
/// the `ExportXcm` implementation of `pezpallet_xcm_bridge_hub`.
|
||||
///
|
||||
/// Note: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which calls the
|
||||
/// `ExportXcm` implementation of `pezpallet_xcm_bridge_hub` as `MessageExporter`.
|
||||
pub struct ExecuteXcmOverSendXcm;
|
||||
impl SendXcm for ExecuteXcmOverSendXcm {
|
||||
type Ticket = Xcm<()>;
|
||||
|
||||
fn validate(
|
||||
_: &mut Option<Location>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
Ok((message.take().unwrap(), Assets::new()))
|
||||
}
|
||||
|
||||
fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
let xcm: Xcm<RuntimeCall> = ticket.into();
|
||||
|
||||
let origin = EXECUTE_XCM_ORIGIN.with(|o| o.borrow().clone().unwrap());
|
||||
let mut hash = xcm.using_encoded(pezsp_io::hashing::blake2_256);
|
||||
let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
|
||||
origin,
|
||||
xcm,
|
||||
&mut hash,
|
||||
Weight::MAX,
|
||||
Weight::zero(),
|
||||
);
|
||||
assert_ok!(outcome.ensure_complete());
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
impl InspectMessageQueues for ExecuteXcmOverSendXcm {
|
||||
fn clear_messages() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
impl ExecuteXcmOverSendXcm {
|
||||
pub fn set_origin_for_execute(origin: Location) {
|
||||
EXECUTE_XCM_ORIGIN.with(|o| *o.borrow_mut() = Some(origin));
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic way to set different universal location for the origin which sends `ExportMessage`.
|
||||
pub struct ExportMessageOriginUniversalLocation;
|
||||
impl ExportMessageOriginUniversalLocation {
|
||||
pub(crate) fn set(universal_location: Option<InteriorLocation>) {
|
||||
EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| *o.borrow_mut() = universal_location);
|
||||
}
|
||||
}
|
||||
impl Get<InteriorLocation> for ExportMessageOriginUniversalLocation {
|
||||
fn get() -> InteriorLocation {
|
||||
EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| {
|
||||
o.borrow()
|
||||
.clone()
|
||||
.expect("`EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION` is not set!")
|
||||
})
|
||||
}
|
||||
}
|
||||
thread_local! {
|
||||
pub static EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION: RefCell<Option<InteriorLocation>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub struct BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>(
|
||||
pezsp_std::marker::PhantomData<RuntimeOrigin>,
|
||||
);
|
||||
impl<RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
|
||||
for BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>
|
||||
{
|
||||
fn convert_origin(
|
||||
origin: impl Into<Location>,
|
||||
kind: OriginKind,
|
||||
) -> Result<RuntimeOrigin, Location> {
|
||||
let origin = origin.into();
|
||||
if kind == OriginKind::Xcm && origin.eq(&BridgeHubLocation::get()) {
|
||||
Ok(RuntimeOrigin::root())
|
||||
} else {
|
||||
Err(origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for specifying how a `Location` can be converted into an `AccountId`. This is used
|
||||
/// when determining ownership of accounts for asset transacting and when attempting to use XCM
|
||||
/// `Transact` in order to determine the dispatch Origin.
|
||||
pub type LocationToAccountId = (
|
||||
// The parent (Relay-chain) origin converts to the parent `AccountId`.
|
||||
ParentIsPreset<AccountId>,
|
||||
// Sibling teyrchain origins convert to AccountId via the `ParaId::into`.
|
||||
SiblingTeyrchainConvertsVia<Sibling, AccountId>,
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub ParentRelayChainLocation: Location = Location { parents: 1, interior: Here };
|
||||
}
|
||||
pub struct OpenBridgeOrigin;
|
||||
|
||||
impl OpenBridgeOrigin {
|
||||
pub fn parent_relay_chain_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([0u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn parent_relay_chain_universal_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([1u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn sibling_teyrchain_origin() -> RuntimeOrigin {
|
||||
let mut account = [0u8; 32];
|
||||
account[..4].copy_from_slice(&SIBLING_ASSET_HUB_ID.encode()[..4]);
|
||||
RuntimeOrigin::signed(account.into())
|
||||
}
|
||||
|
||||
pub fn sibling_teyrchain_universal_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([2u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn origin_without_sovereign_account() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([3u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn disallowed_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([42u8; 32].into())
|
||||
}
|
||||
}
|
||||
|
||||
impl EnsureOrigin<RuntimeOrigin> for OpenBridgeOrigin {
|
||||
type Success = Location;
|
||||
|
||||
fn try_origin(o: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
|
||||
let signer = o.clone().into_signer();
|
||||
if signer == Self::parent_relay_chain_origin().into_signer() {
|
||||
return Ok(ParentRelayChainLocation::get());
|
||||
} else if signer == Self::parent_relay_chain_universal_origin().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 2,
|
||||
interior: GlobalConsensus(RelayNetwork::get()).into(),
|
||||
});
|
||||
} else if signer == Self::sibling_teyrchain_universal_origin().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 2,
|
||||
interior: [GlobalConsensus(RelayNetwork::get()), Teyrchain(SIBLING_ASSET_HUB_ID)]
|
||||
.into(),
|
||||
});
|
||||
} else if signer == Self::origin_without_sovereign_account().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 1,
|
||||
interior: [Teyrchain(SIBLING_ASSET_HUB_ID), OnlyChild].into(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut sibling_account = [0u8; 32];
|
||||
sibling_account[..4].copy_from_slice(&SIBLING_ASSET_HUB_ID.encode()[..4]);
|
||||
if signer == Some(sibling_account.into()) {
|
||||
return Ok(Location { parents: 1, interior: Teyrchain(SIBLING_ASSET_HUB_ID).into() });
|
||||
}
|
||||
|
||||
Err(o)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
|
||||
Ok(Self::parent_relay_chain_origin())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type OpenBridgeOriginOf<T, I> =
|
||||
<T as pezpallet_xcm_bridge_hub::Config<I>>::OpenBridgeOrigin;
|
||||
|
||||
pub struct TestLocalXcmChannelManager;
|
||||
|
||||
impl TestLocalXcmChannelManager {
|
||||
pub fn make_congested() {
|
||||
pezframe_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Congested", &true);
|
||||
}
|
||||
|
||||
fn suspended_key(bridge: &BridgeId) -> Vec<u8> {
|
||||
[b"TestLocalXcmChannelManager.Suspended", bridge.encode().as_slice()].concat()
|
||||
}
|
||||
fn resumed_key(bridge: &BridgeId) -> Vec<u8> {
|
||||
[b"TestLocalXcmChannelManager.Resumed", bridge.encode().as_slice()].concat()
|
||||
}
|
||||
|
||||
pub fn is_bridge_suspended(bridge: &BridgeId) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(&Self::suspended_key(bridge))
|
||||
}
|
||||
|
||||
pub fn is_bridge_resumed(bridge: &BridgeId) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(&Self::resumed_key(bridge))
|
||||
}
|
||||
|
||||
fn build_congestion_message(bridge: &BridgeId, is_congested: bool) -> Vec<Instruction<()>> {
|
||||
use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall;
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, scale_info::TypeInfo)]
|
||||
enum Call {
|
||||
#[codec(index = 57)]
|
||||
XcmOverBridgeWrappedWithExportMessageRouter(XcmBridgeHubRouterCall),
|
||||
}
|
||||
|
||||
pezsp_std::vec![
|
||||
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
||||
Transact {
|
||||
origin_kind: OriginKind::Xcm,
|
||||
fallback_max_weight: None,
|
||||
call: Call::XcmOverBridgeWrappedWithExportMessageRouter(
|
||||
XcmBridgeHubRouterCall::report_bridge_status {
|
||||
bridge_id: bridge.inner(),
|
||||
is_congested,
|
||||
}
|
||||
)
|
||||
.encode()
|
||||
.into(),
|
||||
},
|
||||
ExpectTransactStatus(MaybeErrorCode::Success),
|
||||
]
|
||||
}
|
||||
|
||||
fn report_bridge_status(
|
||||
local_origin: &Location,
|
||||
bridge: &BridgeId,
|
||||
is_congested: bool,
|
||||
key: Vec<u8>,
|
||||
) -> Result<(), SendError> {
|
||||
// send as BridgeHub would send to sibling chain
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(BridgeHubLocation::get());
|
||||
let result = send_xcm::<ExecuteXcmOverSendXcm>(
|
||||
local_origin.clone(),
|
||||
Self::build_congestion_message(&bridge, is_congested).into(),
|
||||
);
|
||||
|
||||
if result.is_ok() {
|
||||
pezframe_support::storage::unhashed::put(&key, &true);
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalXcmChannelManager for TestLocalXcmChannelManager {
|
||||
type Error = SendError;
|
||||
|
||||
fn is_congested(_with: &Location) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Congested")
|
||||
}
|
||||
|
||||
fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
|
||||
Self::report_bridge_status(local_origin, &bridge, true, Self::suspended_key(&bridge))
|
||||
}
|
||||
|
||||
fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
|
||||
Self::report_bridge_status(local_origin, &bridge, false, Self::resumed_key(&bridge))
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_xcm_bridge_hub_router::XcmChannelStatusProvider for TestLocalXcmChannelManager {
|
||||
fn is_congested(with: &Location) -> bool {
|
||||
<Self as LocalXcmChannelManager>::is_congested(with)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestBlobDispatcher;
|
||||
|
||||
impl TestBlobDispatcher {
|
||||
pub fn is_dispatched() -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(b"TestBlobDispatcher.Dispatched")
|
||||
}
|
||||
}
|
||||
|
||||
impl DispatchBlob for TestBlobDispatcher {
|
||||
fn dispatch_blob(_blob: Vec<u8>) -> Result<(), DispatchBlobError> {
|
||||
pezframe_support::storage::unhashed::put(b"TestBlobDispatcher.Dispatched", &true);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThisUnderlyingChain;
|
||||
|
||||
impl Chain for ThisUnderlyingChain {
|
||||
const ID: ChainId = *b"tuch";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BizinikiwiHeader;
|
||||
type AccountId = AccountId;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
u32::MAX
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for ThisUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithThisChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
pub type BridgedHeaderHash = H256;
|
||||
pub type BridgedChainHeader = BizinikiwiHeader;
|
||||
|
||||
pub struct BridgedUnderlyingChain;
|
||||
impl Chain for BridgedUnderlyingChain {
|
||||
const ID: ChainId = *b"bgdc";
|
||||
type BlockNumber = u64;
|
||||
type Hash = BridgedHeaderHash;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = AccountId;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
4096
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for BridgedUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithBridgedChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
pub struct BridgedHeaderChain;
|
||||
impl bp_header_pez_chain::HeaderChain<BridgedUnderlyingChain> for BridgedHeaderChain {
|
||||
fn finalized_header_state_root(
|
||||
_hash: HashOf<BridgedUnderlyingChain>,
|
||||
) -> Option<HashOf<BridgedUnderlyingChain>> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test message dispatcher.
|
||||
pub struct TestMessageDispatch;
|
||||
|
||||
impl TestMessageDispatch {
|
||||
pub fn deactivate(lane: TestLaneIdType) {
|
||||
pezframe_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for TestMessageDispatch {
|
||||
type DispatchPayload = Vec<u8>;
|
||||
type DispatchLevelResult = ();
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
pezframe_support::storage::unhashed::take::<bool>(&(b"inactive", lane).encode()[..]) !=
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
_message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
_: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
||||
}
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
pezsp_io::TestExternalities::new(
|
||||
pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(),
|
||||
)
|
||||
.execute_with(test)
|
||||
}
|
||||
Reference in New Issue
Block a user