diff --git a/Cargo.lock b/Cargo.lock index 06eebb0ea2f..5bbab6eb2e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -590,6 +590,7 @@ version = "0.9.420" dependencies = [ "asset-test-utils", "assets-common", + "bp-asset-hub-kusama", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", @@ -629,6 +630,7 @@ dependencies = [ "pallet-utility", "pallet-xcm", "pallet-xcm-benchmarks", + "pallet-xcm-bridge-hub-router", "parachain-info", "parachains-common", "parity-scale-codec", @@ -688,6 +690,7 @@ version = "0.9.420" dependencies = [ "asset-test-utils", "assets-common", + "bp-asset-hub-polkadot", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", @@ -724,6 +727,7 @@ dependencies = [ "pallet-utility", "pallet-xcm", "pallet-xcm-benchmarks", + "pallet-xcm-bridge-hub-router", "parachain-info", "parachains-common", "parity-scale-codec", @@ -871,6 +875,7 @@ dependencies = [ "pallet-collator-selection", "pallet-session", "pallet-xcm", + "pallet-xcm-bridge-hub-router", "parachain-info", "parachains-common", "parachains-runtimes-test-utils", @@ -1272,6 +1277,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bp-asset-hub-kusama" +version = "0.1.0" +dependencies = [ + "bp-xcm-bridge-hub-router", + "frame-support", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "bp-asset-hub-polkadot" +version = "0.1.0" +dependencies = [ + "bp-xcm-bridge-hub-router", + "frame-support", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "bp-bridge-hub-cumulus" version = "0.1.0" @@ -3168,6 +3193,7 @@ dependencies = [ name = "cumulus-pallet-xcmp-queue" version = "0.1.0" dependencies = [ + "bp-xcm-bridge-hub-router", "cumulus-pallet-parachain-system", "cumulus-primitives-core", "frame-benchmarking", @@ -8928,6 +8954,25 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "pallet-xcm-bridge-hub-router" +version = "0.1.0" +dependencies = [ + "bp-xcm-bridge-hub-router", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-builder", +] + [[package]] name = "parachain-info" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4ae4ea191ae..95841e9444d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "bridges/modules/messages", "bridges/modules/parachains", "bridges/modules/relayers", + "bridges/modules/xcm-bridge-hub-router", "client/cli", "client/collator", "client/consensus/aura", diff --git a/pallets/xcmp-queue/Cargo.toml b/pallets/xcmp-queue/Cargo.toml index 842e3ce9af8..994009aa00f 100644 --- a/pallets/xcmp-queue/Cargo.toml +++ b/pallets/xcmp-queue/Cargo.toml @@ -28,6 +28,9 @@ cumulus-primitives-core = { path = "../../primitives/core", default-features = f # Optional import for benchmarking frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "master" } +# Bridges +bp-xcm-bridge-hub-router = { path = "../../bridges/primitives/xcm-bridge-hub-router", default-features = false, optional = true } + [dev-dependencies] # Substrate @@ -55,6 +58,7 @@ std = [ "sp-std/std", "xcm-executor/std", "xcm/std", + "bp-xcm-bridge-hub-router/std", ] runtime-benchmarks = [ @@ -64,3 +68,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] + +bridging = [ + "bp-xcm-bridge-hub-router", +] diff --git a/pallets/xcmp-queue/src/bridging.rs b/pallets/xcmp-queue/src/bridging.rs new file mode 100644 index 00000000000..bd36ded8d62 --- /dev/null +++ b/pallets/xcmp-queue/src/bridging.rs @@ -0,0 +1,116 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::pallet; +use cumulus_primitives_core::ParaId; +use frame_support::pallet_prelude::Get; + +/// Adapter implementation for `bp_xcm_bridge_hub_router::XcmChannelStatusProvider` which checks +/// both `OutboundXcmpStatus` and `InboundXcmpStatus` for defined `ParaId` if any of those is +/// suspended. +pub struct InboundAndOutboundXcmpChannelCongestionStatusProvider( + sp_std::marker::PhantomData<(SiblingBridgeHubParaId, Runtime)>, +); +impl, Runtime: crate::Config> + bp_xcm_bridge_hub_router::XcmChannelStatusProvider + for InboundAndOutboundXcmpChannelCongestionStatusProvider +{ + fn is_congested() -> bool { + // if the outbound channel with recipient is suspended, it means that one of further + // bridge queues (e.g. bridge queue between two bridge hubs) is overloaded, so we shall + // take larger fee for our outbound messages + let sibling_bridge_hub_id: ParaId = SiblingBridgeHubParaId::get(); + let outbound_channels = pallet::OutboundXcmpStatus::::get(); + let outbound_channel = + outbound_channels.iter().find(|c| c.recipient == sibling_bridge_hub_id); + let is_outbound_channel_suspended = + outbound_channel.map(|c| c.is_suspended()).unwrap_or(false); + if is_outbound_channel_suspended { + return true + } + + // if the inbound channel with recipient is suspended, it means that we are unable to + // receive congestion reports from the bridge hub. So we assume the bridge pipeline is + // congested too + let inbound_channels = pallet::InboundXcmpStatus::::get(); + let inbound_channel = inbound_channels.iter().find(|c| c.sender == sibling_bridge_hub_id); + let is_inbound_channel_suspended = + inbound_channel.map(|c| c.is_suspended()).unwrap_or(false); + if is_inbound_channel_suspended { + return true + } + + // TODO: https://github.com/paritytech/cumulus/pull/2342 - once this PR is merged, we may + // remove the following code + // + // if the outbound channel has at least `N` pages enqueued, let's assume it is congested. + // Normally, the chain with a few opened HRMP channels, will "send" pages at every block. + // Having `N` pages means that for last `N` blocks we either have not sent any messages, + // or have sent signals. + const MAX_OUTBOUND_PAGES_BEFORE_CONGESTION: u16 = 4; + let is_outbound_channel_congested = outbound_channel.map(|c| c.queued_pages()).unwrap_or(0); + is_outbound_channel_congested > MAX_OUTBOUND_PAGES_BEFORE_CONGESTION + } +} + +/// Adapter implementation for `bp_xcm_bridge_hub_router::XcmChannelStatusProvider` which checks +/// only `OutboundXcmpStatus` for defined `SiblingParaId` if is suspended. +pub struct OutboundXcmpChannelCongestionStatusProvider( + sp_std::marker::PhantomData<(SiblingBridgeHubParaId, Runtime)>, +); +impl, Runtime: crate::Config> + bp_xcm_bridge_hub_router::XcmChannelStatusProvider + for OutboundXcmpChannelCongestionStatusProvider +{ + fn is_congested() -> bool { + // let's find the channel with the sibling parachain + let sibling_para_id: cumulus_primitives_core::ParaId = SiblingParaId::get(); + let outbound_channels = pallet::OutboundXcmpStatus::::get(); + let channel_with_sibling_parachain = + outbound_channels.iter().find(|c| c.recipient == sibling_para_id); + + // no channel => it is empty, so not congested + let channel_with_sibling_parachain = match channel_with_sibling_parachain { + Some(channel_with_sibling_parachain) => channel_with_sibling_parachain, + None => return false, + }; + + // suspended channel => it is congested + if channel_with_sibling_parachain.is_suspended() { + return true + } + + // TODO: the following restriction is arguable, we may live without that, assuming that + // there can't be more than some `N` messages queued at the bridge queue (at the source BH) + // AND before accepting next (or next-after-next) delivery transaction, we'll receive the + // suspension signal from the target parachain and stop accepting delivery transactions + + // it takes some time for target parachain to suspend inbound channel with the target BH and + // during that we will keep accepting new message delivery transactions. Let's also reject + // new deliveries if there are too many "pages" (concatenated XCM messages) in the target BH + // -> target parachain queue. + const MAX_QUEUED_PAGES_BEFORE_DEACTIVATION: u16 = 4; + if channel_with_sibling_parachain.queued_pages() > MAX_QUEUED_PAGES_BEFORE_DEACTIVATION { + return true + } + + true + } +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn suspend_channel_for_benchmarks(target: ParaId) { + pallet::Pallet::::suspend_channel(target) +} diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 960af9b5b77..e5e4ce28e15 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -35,6 +35,8 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +#[cfg(feature = "bridging")] +pub mod bridging; pub mod weights; pub use weights::WeightInfo; @@ -400,6 +402,13 @@ pub struct InboundChannelDetails { message_metadata: Vec<(RelayBlockNumber, XcmpMessageFormat)>, } +impl InboundChannelDetails { + #[cfg(feature = "bridging")] + pub(crate) fn is_suspended(&self) -> bool { + self.state == InboundState::Suspended + } +} + /// Struct containing detailed information about the outbound channel. #[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] pub struct OutboundChannelDetails { @@ -435,6 +444,16 @@ impl OutboundChannelDetails { self.state = OutboundState::Suspended; self } + + #[cfg(feature = "bridging")] + pub(crate) fn is_suspended(&self) -> bool { + self.state == OutboundState::Suspended + } + + #[cfg(feature = "bridging")] + pub(crate) fn queued_pages(&self) -> u16 { + self.last_index.saturating_sub(self.first_index) + } } #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] diff --git a/parachains/common/src/xcm_config.rs b/parachains/common/src/xcm_config.rs index 57fe453eb7d..a168f5da290 100644 --- a/parachains/common/src/xcm_config.rs +++ b/parachains/common/src/xcm_config.rs @@ -1,14 +1,19 @@ use crate::impls::AccountIdOf; +use codec::Decode; use core::marker::PhantomData; use frame_support::{ - log, - traits::{fungibles::Inspect, tokens::ConversionToAssetBalance, ContainsPair}, + ensure, log, + traits::{ + fungibles::Inspect, tokens::ConversionToAssetBalance, Contains, ContainsPair, + ProcessMessageError, + }, weights::Weight, DefaultNoBound, }; use sp_runtime::traits::Get; -use xcm::latest::prelude::*; -use xcm_builder::ExporterFor; +use xcm::{latest::prelude::*, DoubleEncoded}; +use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm}; +use xcm_executor::traits::ShouldExecute; /// A `ChargeFeeInFungibles` implementation that converts the output of /// a given WeightToFee implementation an amount charged in @@ -156,6 +161,59 @@ impl< } } +/// Allows execution from `origin` if it is contained in `AllowedOrigin` +/// and if it is just a straight `Transact` which contains `AllowedCall`. +pub struct AllowUnpaidTransactsFrom( + sp_std::marker::PhantomData<(RuntimeCall, AllowedCall, AllowedOrigin)>, +); +impl< + RuntimeCall: Decode, + AllowedCall: Contains, + AllowedOrigin: Contains, + > ShouldExecute for AllowUnpaidTransactsFrom +{ + fn should_execute( + origin: &MultiLocation, + instructions: &mut [Instruction], + max_weight: Weight, + _properties: &mut xcm_executor::traits::Properties, + ) -> Result<(), ProcessMessageError> { + log::trace!( + target: "xcm::barriers", + "AllowUnpaidTransactFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}", + origin, instructions, max_weight, _properties, + ); + + // we only allow from configured origins + ensure!(AllowedOrigin::contains(origin), ProcessMessageError::Unsupported); + + // we expect an XCM program with single `Transact` call + instructions + .matcher() + .assert_remaining_insts(1)? + .match_next_inst(|inst| match inst { + Transact { origin_kind: OriginKind::Xcm, call: encoded_call, .. } => { + // this is a hack - don't know if there's a way to do that properly + // or else we can simply allow all calls + let mut decoded_call = DoubleEncoded::::from(encoded_call.clone()); + ensure!( + AllowedCall::contains( + decoded_call + .ensure_decoded() + .map_err(|_| ProcessMessageError::BadFormat)? + ), + ProcessMessageError::BadFormat, + ); + + Ok(()) + }, + _ => Err(ProcessMessageError::BadFormat), + })?; + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/parachains/runtimes/assets/asset-hub-kusama/Cargo.toml b/parachains/runtimes/assets/asset-hub-kusama/Cargo.toml index 21887da09b9..a65811c59cc 100644 --- a/parachains/runtimes/assets/asset-hub-kusama/Cargo.toml +++ b/parachains/runtimes/assets/asset-hub-kusama/Cargo.toml @@ -67,7 +67,7 @@ cumulus-pallet-dmp-queue = { path = "../../../../pallets/dmp-queue", default-fea cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false } cumulus-pallet-session-benchmarking = {path = "../../../../pallets/session-benchmarking", default-features = false, version = "3.0.0"} cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } -cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-timestamp = { path = "../../../../primitives/timestamp", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } @@ -76,6 +76,10 @@ parachain-info = { path = "../../../pallets/parachain-info", default-features = parachains-common = { path = "../../../common", default-features = false } assets-common = { path = "../common", default-features = false } +# Bridges +pallet-xcm-bridge-hub-router = { path = "../../../../bridges/modules/xcm-bridge-hub-router", default-features = false } +bp-asset-hub-kusama = { path = "../../../../bridges/primitives/chain-asset-hub-kusama", default-features = false } + [dev-dependencies] asset-test-utils = { path = "../test-utils"} @@ -116,6 +120,7 @@ runtime-benchmarks = [ "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-state-trie-migration/runtime-benchmarks", "assets-common/runtime-benchmarks", + "pallet-xcm-bridge-hub-router/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", @@ -144,6 +149,7 @@ try-runtime = [ "pallet-xcm/try-runtime", "parachain-info/try-runtime", "pallet-state-trie-migration/try-runtime", + "pallet-xcm-bridge-hub-router/try-runtime", ] std = [ "codec/std", @@ -201,5 +207,7 @@ std = [ "parachain-info/std", "parachains-common/std", "assets-common/std", + "pallet-xcm-bridge-hub-router/std", + "bp-asset-hub-kusama/std", "substrate-wasm-builder", ] diff --git a/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs b/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs index ffc6e43fc7a..8dd9b6a0025 100644 --- a/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs +++ b/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs @@ -733,6 +733,33 @@ impl pallet_nfts::Config for Runtime { type Helper = (); } +/// XCM router instance to BridgeHub with bridging capabilities for `Polkadot` global consensus with +/// dynamic fees and back-pressure. +pub type ToPolkadotXcmRouterInstance = pallet_assets::Instance1; +impl pallet_xcm_bridge_hub_router::Config for Runtime { + type WeightInfo = weights::pallet_xcm_bridge_hub_router::WeightInfo; + + type UniversalLocation = xcm_config::UniversalLocation; + type BridgedNetworkId = xcm_config::bridging::PolkadotNetwork; + type Bridges = xcm_config::bridging::FilteredNetworkExportTable; + + #[cfg(not(feature = "runtime-benchmarks"))] + type BridgeHubOrigin = + EnsureXcm>; + #[cfg(feature = "runtime-benchmarks")] + type BridgeHubOrigin = EnsureRoot; + + type ToBridgeHubSender = XcmpQueue; + type WithBridgeHubChannel = + cumulus_pallet_xcmp_queue::bridging::InboundAndOutboundXcmpChannelCongestionStatusProvider< + xcm_config::bridging::BridgeHubKusamaParaId, + Runtime, + >; + + type ByteFee = xcm_config::bridging::XcmBridgeHubRouterByteFee; + type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -769,6 +796,9 @@ construct_runtime!( Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 42, + // Bridge utilities. + ToPolkadotXcmRouter: pallet_xcm_bridge_hub_router::::{Pallet, Storage, Call} = 43, + // The main stage. Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, @@ -837,6 +867,7 @@ mod benches { [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_xcm_bridge_hub_router, XcmBridgeHubRouterBench] // XCM [pallet_xcm, PolkadotXcm] // NOTE: Make sure you point to the individual modules below. @@ -1052,6 +1083,7 @@ impl_runtime_apis! { use frame_support::traits::StorageInfoTrait; use frame_system_benchmarking::Pallet as SystemBench; use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + use pallet_xcm_bridge_hub_router::benchmarking::Pallet as XcmBridgeHubRouterBench; // This is defined once again in dispatch_benchmark, because list_benchmarks! // and add_benchmarks! are macros exported by define_benchmarks! macros and those types @@ -1094,6 +1126,25 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + use pallet_xcm_bridge_hub_router::benchmarking::{ + Pallet as XcmBridgeHubRouterBench, + Config as XcmBridgeHubRouterConfig, + }; + + impl XcmBridgeHubRouterConfig for Runtime { + fn make_congested() { + cumulus_pallet_xcmp_queue::bridging::suspend_channel_for_benchmarks::( + xcm_config::bridging::BridgeHubKusamaParaId::get().into() + ); + } + fn ensure_bridged_target_destination() -> MultiLocation { + ParachainSystem::open_outbound_hrmp_channel_for_benchmarks( + xcm_config::bridging::BridgeHubKusamaParaId::get().into() + ); + xcm_config::bridging::AssetHubPolkadot::get() + } + } + use xcm::latest::prelude::*; use xcm_config::{KsmLocation, MaxAssetsIntoHolding}; use pallet_xcm_benchmarks::asset_instance_from; diff --git a/parachains/runtimes/assets/asset-hub-kusama/src/weights/mod.rs b/parachains/runtimes/assets/asset-hub-kusama/src/weights/mod.rs index 6948d2b6d53..bea309b3256 100644 --- a/parachains/runtimes/assets/asset-hub-kusama/src/weights/mod.rs +++ b/parachains/runtimes/assets/asset-hub-kusama/src/weights/mod.rs @@ -15,6 +15,7 @@ pub mod pallet_timestamp; pub mod pallet_uniques; pub mod pallet_utility; pub mod pallet_xcm; +pub mod pallet_xcm_bridge_hub_router; pub mod paritydb_weights; pub mod rocksdb_weights; pub mod xcm; diff --git a/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_xcm_bridge_hub_router.rs b/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_xcm_bridge_hub_router.rs new file mode 100644 index 00000000000..8fc21a35bd6 --- /dev/null +++ b/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_xcm_bridge_hub_router.rs @@ -0,0 +1,118 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! Autogenerated weights for `pallet_xcm_bridge_hub_router` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-aahe6cbd-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-kusama-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/cumulus/.git/.artifacts/bench.json +// --pallet=pallet_xcm_bridge_hub_router +// --chain=asset-hub-kusama-dev +// --header=./file_header.txt +// --output=./parachains/runtimes/assets/asset-hub-kusama/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_xcm_bridge_hub_router`. +pub struct WeightInfo(PhantomData); +impl pallet_xcm_bridge_hub_router::WeightInfo for WeightInfo { + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpStatus` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ToPolkadotXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToPolkadotXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + fn on_initialize_when_non_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `1644` + // Minimum execution time: 9_957_000 picoseconds. + Weight::from_parts(10_409_000, 0) + .saturating_add(Weight::from_parts(0, 1644)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn on_initialize_when_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `111` + // Estimated: `1596` + // Minimum execution time: 3_274_000 picoseconds. + Weight::from_parts(3_445_000, 0) + .saturating_add(Weight::from_parts(0, 1596)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `ToPolkadotXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToPolkadotXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `83` + // Estimated: `1502` + // Minimum execution time: 11_282_000 picoseconds. + Weight::from_parts(11_712_000, 0) + .saturating_add(Weight::from_parts(0, 1502)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x0973fe64c85043ba1c965cbc38eb63c7` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x0973fe64c85043ba1c965cbc38eb63c7` (r:1 w:0) + /// Storage: `ToPolkadotXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToPolkadotXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) + /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn send_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3820` + // Minimum execution time: 49_615_000 picoseconds. + Weight::from_parts(50_859_000, 0) + .saturating_add(Weight::from_parts(0, 3820)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} diff --git a/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs b/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs index 4a7144ac915..1ac0b98a37a 100644 --- a/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs +++ b/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs @@ -16,7 +16,7 @@ use super::{ AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ForeignAssets, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - TrustBackedAssetsInstance, WeightToFee, XcmpQueue, + ToPolkadotXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; use crate::ForeignAssetsInstance; use assets_common::matching::{ @@ -28,7 +28,10 @@ use frame_support::{ }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; -use parachains_common::{impls::ToStakingPot, xcm_config::AssetFeeAsExistentialDepositMultiplier}; +use parachains_common::{ + impls::ToStakingPot, + xcm_config::{AllowUnpaidTransactsFrom, AssetFeeAsExistentialDepositMultiplier}, +}; use polkadot_parachain::primitives::Sibling; use sp_runtime::traits::ConvertInto; use xcm::latest::prelude::*; @@ -202,6 +205,14 @@ impl Contains for SafeCallFilter { } } + // Allow to change dedicated storage items (called by governance-like) + match call { + RuntimeCall::System(frame_system::Call::set_storage { items }) + if items.iter().all(|(k, _)| k.eq(&bridging::XcmBridgeHubRouterByteFee::key())) => + return true, + _ => (), + }; + matches!( call, RuntimeCall::PolkadotXcm(pallet_xcm::Call::force_xcm_version { .. }) | @@ -346,6 +357,8 @@ impl Contains for SafeCallFilter { pallet_uniques::Call::set_collection_max_supply { .. } | pallet_uniques::Call::set_price { .. } | pallet_uniques::Call::buy_item { .. } + ) | RuntimeCall::ToPolkadotXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } ) ) } @@ -368,6 +381,8 @@ pub type Barrier = TrailingSetTopicAsId< AllowExplicitUnpaidExecutionFrom, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // Allows Transacts with `report_bridge_status` from sibling BridgeHub. + bridging::AllowUnpaidStatusReportsFromSiblingBridgeHub, ), UniversalLocation, ConstU32<8>, @@ -454,6 +469,7 @@ impl xcm_executor::Config for XcmConfig { type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); + // TODO:check-parameter: change and assert in tests when (https://github.com/paritytech/polkadot/pull/7005) merged type FeeManager = (); type MessageExporter = (); type UniversalAliases = bridging::UniversalAliases; @@ -476,7 +492,12 @@ type LocalXcmRouter = ( /// The means for routing XCM messages which are not for local execution into the right message /// queues. -pub type XcmRouter = WithUniqueTopic<(LocalXcmRouter, bridging::BridgingXcmRouter)>; +pub type XcmRouter = WithUniqueTopic<( + LocalXcmRouter, + // Router which wraps and sends xcm to BridgeHub to be delivered to the Polkadot + // GlobalConsensus + ToPolkadotXcmRouter, +)>; #[cfg(feature = "runtime-benchmarks")] parameter_types! { @@ -548,7 +569,6 @@ pub mod bridging { use assets_common::{matching, matching::*}; use parachains_common::xcm_config::LocationFilter; use sp_std::collections::btree_set::BTreeSet; - use xcm_builder::UnpaidRemoteExporter; parameter_types! { pub BridgeHubKusamaParaId: u32 = 1002; @@ -558,7 +578,14 @@ pub mod bridging { pub AssetHubPolkadot: MultiLocation = MultiLocation::new(2, X2(GlobalConsensus(PolkadotNetwork::get()), Parachain(1000))); pub DotLocation: MultiLocation = MultiLocation::new(2, X1(GlobalConsensus(PolkadotNetwork::get()))); + /// Router expects payment with this `AssetId`. + /// (`AssetId` has to be aligned with `BridgeTable`) + pub XcmBridgeHubRouterFeeAssetId: AssetId = KsmLocation::get().into(); + /// Price per byte - can be adjusted via governance `set_storage` call. + pub storage XcmBridgeHubRouterByteFee: Balance = TransactionByteFee::get(); + /// Set up exporters configuration. + /// `Option` represents static "base fee" which is used for total delivery fee calculation. pub BridgeTable: sp_std::vec::Vec<(NetworkId, LocationFilter, MultiLocation, Option)> = sp_std::vec![ ( PolkadotNetwork::get(), @@ -567,7 +594,11 @@ pub mod bridging { .add_equals(AssetHubPolkadot::get().interior.split_global().expect("invalid configuration for AssetHubPolkadot").1), // and nothing else BridgeHubKusama::get(), - None + // base delivery fee to local `BridgeHub` + Some(( + XcmBridgeHubRouterFeeAssetId::get(), + bp_asset_hub_kusama::BridgeHubKusamaBaseFeeInDots::get(), + ).into()) ) ]; @@ -607,6 +638,7 @@ pub mod bridging { (BridgeHubKusamaWithBridgeHubPolkadotInstance::get(), GlobalConsensus(PolkadotNetwork::get())) ] ); + } impl Contains<(MultiLocation, Junction)> for UniversalAliases { @@ -618,11 +650,6 @@ pub mod bridging { pub type FilteredNetworkExportTable = parachains_common::xcm_config::FilteredNetworkExportTable; - /// Bridge router, which wraps and sends xcm to BridgeHub to be delivered to the different - /// GlobalConsensus - pub type BridgingXcmRouter = - UnpaidRemoteExporter; - /// Reserve locations filter for `xcm_executor::Config::IsReserve`. pub type IsTrustedBridgedReserveLocationForConcreteAsset = matching::IsTrustedBridgedReserveLocationForConcreteAsset< @@ -638,6 +665,22 @@ pub mod bridging { IsNotAllowedConcreteAssetBy, >; + impl Contains for ToPolkadotXcmRouter { + fn contains(call: &RuntimeCall) -> bool { + matches!( + call, + RuntimeCall::ToPolkadotXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } + ) + ) + } + } + + /// Barrier for `pallet_xcm_bridge_hub_router::Pallet` to receive congestion statuses from + /// sibling BridgeHub. + pub type AllowUnpaidStatusReportsFromSiblingBridgeHub = + AllowUnpaidTransactsFrom>; + /// Benchmarks helper for bridging configuration. #[cfg(feature = "runtime-benchmarks")] pub struct BridgingBenchmarksHelper; diff --git a/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs b/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs index b309c5b7431..6c177488dda 100644 --- a/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs +++ b/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs @@ -29,7 +29,8 @@ pub use asset_hub_kusama_runtime::{ }, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, - RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, XcmpQueue, + RuntimeEvent, SessionKeys, System, ToPolkadotXcmRouterInstance, TrustBackedAssetsInstance, + XcmpQueue, }; use asset_test_utils::{CollatorSessionKey, CollatorSessionKeys, ExtBuilder, RuntimeHelper}; use codec::{Decode, Encode}; @@ -672,6 +673,7 @@ fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works() { }), bridging_to_asset_hub_polkadot, WeightLimit::Unlimited, + Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), ) } @@ -688,7 +690,7 @@ fn receive_reserve_asset_deposited_dot_from_asset_hub_polkadot_works() { ExistentialDeposit::get(), AccountId::from([73; 32]), AccountId::from(BLOCK_AUTHOR_ACCOUNT), - // receiving DOTs + // receiving DOTs (MultiLocation { parents: 2, interior: X1(GlobalConsensus(Polkadot)) }, 1000000000000, 1_000_000_000), bridging_to_asset_hub_polkadot, (X1(PalletInstance(53)), GlobalConsensus(Polkadot), X1(Parachain(1000))) @@ -778,3 +780,126 @@ fn xcm_reserve_transfer_filter_works() { } }) } + +#[test] +fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::XcmBridgeHubRouterByteFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + ( + bridging::XcmBridgeHubRouterByteFee::key().to_vec(), + bridging::XcmBridgeHubRouterByteFee::get(), + ) + }, + |old_value| { + if let Some(new_value) = old_value.checked_add(1) { + new_value + } else { + old_value.checked_sub(1).unwrap() + } + }, + ) +} + +#[test] +fn test_report_bridge_status_call_compatibility() { + // if this test fails, make sure `bp_asset_hub_kusama` has valid encoding + assert_eq!( + RuntimeCall::ToPolkadotXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode(), + bp_asset_hub_kusama::Call::ToPolkadotXcmRouter( + bp_asset_hub_kusama::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + ) +} + +#[test] +fn report_bridge_status_from_xcm_bridge_router_works() { + asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< + Runtime, + XcmConfig, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + ToPolkadotXcmRouterInstance, + >( + collator_session_keys(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + bridging_to_asset_hub_polkadot, + WeightLimit::Unlimited, + Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), + || { + sp_std::vec![Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: + bp_asset_hub_kusama::XcmBridgeHubRouterTransactCallMaxWeight::get(), + call: bp_asset_hub_kusama::Call::ToPolkadotXcmRouter( + bp_asset_hub_kusama::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + .into(), + }] + .into() + }, + || { + sp_std::vec![Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: + bp_asset_hub_kusama::XcmBridgeHubRouterTransactCallMaxWeight::get(), + call: bp_asset_hub_kusama::Call::ToPolkadotXcmRouter( + bp_asset_hub_kusama::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: false, + } + ) + .encode() + .into(), + }] + .into() + }, + ) +} + +#[test] +fn check_sane_weight_report_bridge_status() { + use pallet_xcm_bridge_hub_router::WeightInfo; + let actual = >::WeightInfo::report_bridge_status(); + let max_weight = bp_asset_hub_kusama::XcmBridgeHubRouterTransactCallMaxWeight::get(); + assert!( + actual.all_lte(max_weight), + "max_weight: {:?} should be adjusted to actual {:?}", + max_weight, + actual + ); +} diff --git a/parachains/runtimes/assets/asset-hub-polkadot/Cargo.toml b/parachains/runtimes/assets/asset-hub-polkadot/Cargo.toml index 6bbd6c18164..9dc84bc997a 100644 --- a/parachains/runtimes/assets/asset-hub-polkadot/Cargo.toml +++ b/parachains/runtimes/assets/asset-hub-polkadot/Cargo.toml @@ -65,7 +65,7 @@ cumulus-pallet-dmp-queue = { path = "../../../../pallets/dmp-queue", default-fea cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false } cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false, version = "3.0.0" } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } -cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-timestamp = { path = "../../../../primitives/timestamp", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } @@ -74,6 +74,10 @@ parachain-info = { path = "../../../pallets/parachain-info", default-features = parachains-common = { path = "../../../common", default-features = false } assets-common = { path = "../common", default-features = false } +# Bridges +pallet-xcm-bridge-hub-router = { path = "../../../../bridges/modules/xcm-bridge-hub-router", default-features = false } +bp-asset-hub-polkadot = { path = "../../../../bridges/primitives/chain-asset-hub-polkadot", default-features = false } + [dev-dependencies] hex-literal = "0.4.1" asset-test-utils = { path = "../test-utils"} @@ -107,6 +111,7 @@ runtime-benchmarks = [ "cumulus-pallet-xcmp-queue/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", "assets-common/runtime-benchmarks", + "pallet-xcm-bridge-hub-router/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", @@ -133,6 +138,7 @@ try-runtime = [ "pallet-utility/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", + "pallet-xcm-bridge-hub-router/try-runtime", ] std = [ "codec/std", @@ -188,5 +194,7 @@ std = [ "parachain-info/std", "parachains-common/std", "assets-common/std", + "pallet-xcm-bridge-hub-router/std", + "bp-asset-hub-polkadot/std", "substrate-wasm-builder", ] diff --git a/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs b/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs index db9f55c9ba3..6e64e13bc2f 100644 --- a/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs +++ b/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs @@ -721,6 +721,33 @@ impl pallet_nfts::Config for Runtime { type Helper = (); } +/// XCM router instance to BridgeHub with bridging capabilities for `Kusama` global consensus with +/// dynamic fees and back-pressure. +pub type ToKusamaXcmRouterInstance = pallet_assets::Instance1; +impl pallet_xcm_bridge_hub_router::Config for Runtime { + type WeightInfo = weights::pallet_xcm_bridge_hub_router::WeightInfo; + + type UniversalLocation = xcm_config::UniversalLocation; + type BridgedNetworkId = xcm_config::bridging::KusamaNetwork; + type Bridges = xcm_config::bridging::FilteredNetworkExportTable; + + #[cfg(not(feature = "runtime-benchmarks"))] + type BridgeHubOrigin = + EnsureXcm>; + #[cfg(feature = "runtime-benchmarks")] + type BridgeHubOrigin = EnsureRoot; + + type ToBridgeHubSender = XcmpQueue; + type WithBridgeHubChannel = + cumulus_pallet_xcmp_queue::bridging::InboundAndOutboundXcmpChannelCongestionStatusProvider< + xcm_config::bridging::BridgeHubPolkadotParaId, + Runtime, + >; + + type ByteFee = xcm_config::bridging::XcmBridgeHubRouterByteFee; + type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -757,6 +784,9 @@ construct_runtime!( Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 42, + // Bridge utilities. + ToKusamaXcmRouter: pallet_xcm_bridge_hub_router::::{Pallet, Storage, Call} = 43, + // The main stage. Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, @@ -820,6 +850,7 @@ mod benches { [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_xcm_bridge_hub_router, XcmBridgeHubRouterBench] // XCM [pallet_xcm, PolkadotXcm] // NOTE: Make sure you point to the individual modules below. @@ -1035,6 +1066,7 @@ impl_runtime_apis! { use frame_support::traits::StorageInfoTrait; use frame_system_benchmarking::Pallet as SystemBench; use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + use pallet_xcm_bridge_hub_router::benchmarking::Pallet as XcmBridgeHubRouterBench; // This is defined once again in dispatch_benchmark, because list_benchmarks! // and add_benchmarks! are macros exported by define_benchmarks! macros and those types @@ -1076,6 +1108,25 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + use pallet_xcm_bridge_hub_router::benchmarking::{ + Pallet as XcmBridgeHubRouterBench, + Config as XcmBridgeHubRouterConfig, + }; + + impl XcmBridgeHubRouterConfig for Runtime { + fn make_congested() { + cumulus_pallet_xcmp_queue::bridging::suspend_channel_for_benchmarks::( + xcm_config::bridging::BridgeHubPolkadotParaId::get().into() + ); + } + fn ensure_bridged_target_destination() -> MultiLocation { + ParachainSystem::open_outbound_hrmp_channel_for_benchmarks( + xcm_config::bridging::BridgeHubPolkadotParaId::get().into() + ); + xcm_config::bridging::AssetHubKusama::get() + } + } + use xcm::latest::prelude::*; use xcm_config::{DotLocation, MaxAssetsIntoHolding}; use pallet_xcm_benchmarks::asset_instance_from; diff --git a/parachains/runtimes/assets/asset-hub-polkadot/src/weights/mod.rs b/parachains/runtimes/assets/asset-hub-polkadot/src/weights/mod.rs index 2cf514a5598..73027996839 100644 --- a/parachains/runtimes/assets/asset-hub-polkadot/src/weights/mod.rs +++ b/parachains/runtimes/assets/asset-hub-polkadot/src/weights/mod.rs @@ -14,6 +14,7 @@ pub mod pallet_timestamp; pub mod pallet_uniques; pub mod pallet_utility; pub mod pallet_xcm; +pub mod pallet_xcm_bridge_hub_router; pub mod paritydb_weights; pub mod rocksdb_weights; pub mod xcm; diff --git a/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_xcm_bridge_hub_router.rs b/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_xcm_bridge_hub_router.rs new file mode 100644 index 00000000000..2fdc9747f81 --- /dev/null +++ b/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_xcm_bridge_hub_router.rs @@ -0,0 +1,118 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! Autogenerated weights for `pallet_xcm_bridge_hub_router` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-aahe6cbd-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-polkadot-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/cumulus/.git/.artifacts/bench.json +// --pallet=pallet_xcm_bridge_hub_router +// --chain=asset-hub-polkadot-dev +// --header=./file_header.txt +// --output=./parachains/runtimes/assets/asset-hub-polkadot/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_xcm_bridge_hub_router`. +pub struct WeightInfo(PhantomData); +impl pallet_xcm_bridge_hub_router::WeightInfo for WeightInfo { + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpStatus` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ToKusamaXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToKusamaXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + fn on_initialize_when_non_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `193` + // Estimated: `1678` + // Minimum execution time: 9_963_000 picoseconds. + Weight::from_parts(10_410_000, 0) + .saturating_add(Weight::from_parts(0, 1678)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn on_initialize_when_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `111` + // Estimated: `1596` + // Minimum execution time: 3_322_000 picoseconds. + Weight::from_parts(3_513_000, 0) + .saturating_add(Weight::from_parts(0, 1596)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `ToKusamaXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToKusamaXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `117` + // Estimated: `1502` + // Minimum execution time: 11_296_000 picoseconds. + Weight::from_parts(11_778_000, 0) + .saturating_add(Weight::from_parts(0, 1502)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x0973fe64c85043ba1c965cbc38eb63c7` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x0973fe64c85043ba1c965cbc38eb63c7` (r:1 w:0) + /// Storage: `ToKusamaXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToKusamaXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) + /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn send_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3820` + // Minimum execution time: 50_336_000 picoseconds. + Weight::from_parts(51_994_000, 0) + .saturating_add(Weight::from_parts(0, 3820)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} diff --git a/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs b/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs index 56c89dad0b2..396b5696b91 100644 --- a/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs +++ b/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs @@ -16,7 +16,7 @@ use super::{ AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ForeignAssets, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - TrustBackedAssetsInstance, WeightToFee, XcmpQueue, + ToKusamaXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; use crate::ForeignAssetsInstance; use assets_common::matching::{ @@ -28,7 +28,10 @@ use frame_support::{ }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; -use parachains_common::{impls::ToStakingPot, xcm_config::AssetFeeAsExistentialDepositMultiplier}; +use parachains_common::{ + impls::ToStakingPot, + xcm_config::{AllowUnpaidTransactsFrom, AssetFeeAsExistentialDepositMultiplier}, +}; use polkadot_parachain::primitives::Sibling; use sp_runtime::traits::ConvertInto; use xcm::latest::prelude::*; @@ -209,6 +212,14 @@ impl Contains for SafeCallFilter { } } + // Allow to change dedicated storage items (called by governance-like) + match call { + RuntimeCall::System(frame_system::Call::set_storage { items }) + if items.iter().all(|(k, _)| k.eq(&bridging::XcmBridgeHubRouterByteFee::key())) => + return true, + _ => (), + }; + matches!( call, RuntimeCall::PolkadotXcm(pallet_xcm::Call::force_xcm_version { .. }) | @@ -349,7 +360,9 @@ impl Contains for SafeCallFilter { pallet_uniques::Call::set_accept_ownership { .. } | pallet_uniques::Call::set_collection_max_supply { .. } | pallet_uniques::Call::set_price { .. } | - pallet_uniques::Call::buy_item { .. } + pallet_uniques::Call::buy_item { .. }, + ) | RuntimeCall::ToKusamaXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } ) ) } @@ -377,6 +390,8 @@ pub type Barrier = TrailingSetTopicAsId< )>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // Allows Transacts with `report_bridge_status` from sibling BridgeHub. + bridging::AllowUnpaidStatusReportsFromSiblingBridgeHub, ), UniversalLocation, ConstU32<8>, @@ -463,6 +478,7 @@ impl xcm_executor::Config for XcmConfig { type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); + // TODO:check-parameter: change and assert in tests when (https://github.com/paritytech/polkadot/pull/7005) merged type FeeManager = (); type MessageExporter = (); type UniversalAliases = bridging::UniversalAliases; @@ -485,7 +501,11 @@ type LocalXcmRouter = ( /// The means for routing XCM messages which are not for local execution into the right message /// queues. -pub type XcmRouter = WithUniqueTopic<(LocalXcmRouter, bridging::BridgingXcmRouter)>; +pub type XcmRouter = WithUniqueTopic<( + LocalXcmRouter, + // Router which wraps and sends XCM to BridgeHub to be delivered to the Kusama GlobalConsensus + ToKusamaXcmRouter, +)>; #[cfg(feature = "runtime-benchmarks")] parameter_types! { @@ -572,7 +592,6 @@ pub mod bridging { use assets_common::{matching, matching::*}; use parachains_common::xcm_config::LocationFilter; use sp_std::collections::btree_set::BTreeSet; - use xcm_builder::UnpaidRemoteExporter; parameter_types! { pub BridgeHubPolkadotParaId: u32 = 1002; @@ -582,7 +601,14 @@ pub mod bridging { pub AssetHubKusama: MultiLocation = MultiLocation::new(2, X2(GlobalConsensus(KusamaNetwork::get()), Parachain(1000))); pub KsmLocation: MultiLocation = MultiLocation::new(2, X1(GlobalConsensus(KusamaNetwork::get()))); + /// Router expects payment with this `AssetId`. + /// (`AssetId` has to be aligned with `BridgeTable`) + pub XcmBridgeHubRouterFeeAssetId: AssetId = DotLocation::get().into(); + /// Price per byte - can be adjusted via governance `set_storage` call. + pub storage XcmBridgeHubRouterByteFee: Balance = TransactionByteFee::get(); + /// Set up exporters configuration. + /// `Option` represents static "base fee" which is used for total delivery fee calculation. pub BridgeTable: sp_std::vec::Vec<(NetworkId, LocationFilter, MultiLocation, Option)> = sp_std::vec![ ( KusamaNetwork::get(), @@ -591,7 +617,11 @@ pub mod bridging { .add_equals(AssetHubKusama::get().interior.split_global().expect("invalid configuration for AssetHubKusama").1), // and nothing else BridgeHubPolkadot::get(), - None + // base delivery fee to local `BridgeHub` + Some(( + XcmBridgeHubRouterFeeAssetId::get(), + bp_asset_hub_polkadot::BridgeHubPolkadotBaseFeeInDots::get() + ).into()) ) ]; @@ -642,11 +672,6 @@ pub mod bridging { pub type FilteredNetworkExportTable = parachains_common::xcm_config::FilteredNetworkExportTable; - /// Bridge router, which wraps and sends xcm to BridgeHub to be delivered to the different - /// GlobalConsensus - pub type BridgingXcmRouter = - UnpaidRemoteExporter; - /// Reserve locations filter for `xcm_executor::Config::IsReserve`. pub type IsTrustedBridgedReserveLocationForConcreteAsset = matching::IsTrustedBridgedReserveLocationForConcreteAsset< @@ -662,6 +687,22 @@ pub mod bridging { IsNotAllowedConcreteAssetBy, >; + impl Contains for ToKusamaXcmRouter { + fn contains(call: &RuntimeCall) -> bool { + matches!( + call, + RuntimeCall::ToKusamaXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } + ) + ) + } + } + + /// Barrier for `pallet_xcm_bridge_hub_router::Pallet` to receive congestion statuses from + /// sibling BridgeHub. + pub type AllowUnpaidStatusReportsFromSiblingBridgeHub = + AllowUnpaidTransactsFrom>; + /// Benchmarks helper for bridging configuration. #[cfg(feature = "runtime-benchmarks")] pub struct BridgingBenchmarksHelper; diff --git a/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs b/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs index 223de0938fe..0e26a2b7935 100644 --- a/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs +++ b/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs @@ -23,9 +23,10 @@ use asset_hub_polkadot_runtime::xcm_config::{ XcmConfig, }; pub use asset_hub_polkadot_runtime::{ - constants::fee::WeightToFee, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, - ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, - RuntimeCall, RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, XcmpQueue, + constants::fee::WeightToFee, xcm_config, AssetDeposit, Assets, Balances, ExistentialDeposit, + ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, + ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, System, + ToKusamaXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, }; use asset_test_utils::{CollatorSessionKey, CollatorSessionKeys, ExtBuilder, RuntimeHelper}; use codec::{Decode, Encode}; @@ -693,6 +694,7 @@ fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works() { }), bridging_to_asset_hub_kusama, WeightLimit::Unlimited, + Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), ) } @@ -709,7 +711,7 @@ fn receive_reserve_asset_deposited_ksm_from_asset_hub_kusama_works() { ExistentialDeposit::get(), AccountId::from([73; 32]), AccountId::from(BLOCK_AUTHOR_ACCOUNT), - // receiving DOTs + // receiving KSMs (MultiLocation { parents: 2, interior: X1(GlobalConsensus(Kusama)) }, 1000000000000, 10_000_000_000), bridging_to_asset_hub_kusama, (X1(PalletInstance(53)), GlobalConsensus(Kusama), X1(Parachain(1000))) @@ -799,3 +801,124 @@ fn xcm_reserve_transfer_filter_works() { } }) } + +#[test] +fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::XcmBridgeHubRouterByteFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + ( + bridging::XcmBridgeHubRouterByteFee::key().to_vec(), + bridging::XcmBridgeHubRouterByteFee::get(), + ) + }, + |old_value| { + if let Some(new_value) = old_value.checked_add(1) { + new_value + } else { + old_value.checked_sub(1).unwrap() + } + }, + ) +} + +#[test] +fn test_report_bridge_status_call_compatibility() { + // if this test fails, make sure `bp_asset_hub_polkadot` has valid encoding + assert_eq!( + RuntimeCall::ToKusamaXcmRouter(pallet_xcm_bridge_hub_router::Call::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + }) + .encode(), + bp_asset_hub_polkadot::Call::ToKusamaXcmRouter( + bp_asset_hub_polkadot::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + ) +} + +#[test] +fn report_bridge_status_from_xcm_bridge_router_works() { + asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< + Runtime, + XcmConfig, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + ToKusamaXcmRouterInstance, + >( + collator_session_keys(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + bridging_to_asset_hub_kusama, + WeightLimit::Unlimited, + Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), + || { + sp_std::vec![Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: + bp_asset_hub_polkadot::XcmBridgeHubRouterTransactCallMaxWeight::get(), + call: bp_asset_hub_polkadot::Call::ToKusamaXcmRouter( + bp_asset_hub_polkadot::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + .into(), + }] + .into() + }, + || { + sp_std::vec![Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: + bp_asset_hub_polkadot::XcmBridgeHubRouterTransactCallMaxWeight::get(), + call: bp_asset_hub_polkadot::Call::ToKusamaXcmRouter( + bp_asset_hub_polkadot::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: false, + } + ) + .encode() + .into(), + }] + .into() + }, + ) +} + +#[test] +fn check_sane_weight_report_bridge_status() { + use pallet_xcm_bridge_hub_router::WeightInfo; + let actual = >::WeightInfo::report_bridge_status(); + let max_weight = bp_asset_hub_polkadot::XcmBridgeHubRouterTransactCallMaxWeight::get(); + assert!( + actual.all_lte(max_weight), + "max_weight: {:?} should be adjusted to actual {:?}", + max_weight, + actual + ); +} diff --git a/parachains/runtimes/assets/test-utils/Cargo.toml b/parachains/runtimes/assets/test-utils/Cargo.toml index 22d46eac343..92e2f189e6a 100644 --- a/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/parachains/runtimes/assets/test-utils/Cargo.toml @@ -40,6 +40,9 @@ xcm-executor = { git = "https://github.com/paritytech/polkadot", default-feature pallet-xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +# Bridges +pallet-xcm-bridge-hub-router = { path = "../../../../bridges/modules/xcm-bridge-hub-router", default-features = false } + [dev-dependencies] hex-literal = "0.4.1" @@ -75,4 +78,5 @@ std = [ "xcm-executor/std", "cumulus-pallet-xcmp-queue/std", "cumulus-pallet-dmp-queue/std", + "pallet-xcm-bridge-hub-router/std", ] diff --git a/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs b/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs index 10b7899bd79..b5f064588b6 100644 --- a/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs +++ b/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs @@ -56,6 +56,7 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< >, ensure_configuration: fn() -> TestBridgingConfig, weight_limit: WeightLimit, + maybe_paid_export_message: Option, ) where Runtime: frame_system::Config + pallet_balances::Config @@ -160,6 +161,7 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< )); // check alice account decreased by balance_to_transfer + // TODO:check-parameter: change and assert in tests when (https://github.com/paritytech/polkadot/pull/7005) merged assert_eq!( >::free_balance(&alice_account), alice_account_init_balance - balance_to_transfer.into() @@ -202,38 +204,54 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< ); let mut xcm_sent: Xcm<()> = xcm_sent.try_into().expect("versioned xcm"); - // check sent XCM ExportMessage to bridge-hub - xcm_sent - .0 - .matcher() - .match_next_inst(|instr| match instr { - // first instruction is UNpai (because we have explicit unpaid execution on - // bridge-hub now) - UnpaidExecution { weight_limit, check_origin } - if weight_limit == &Unlimited && check_origin.is_none() => - Ok(()), - _ => Err(ProcessMessageError::BadFormat), - }) - .expect("contains UnpaidExecution") - .match_next_inst(|instr| match instr { - // second instruction is ExportMessage - ExportMessage { network, destination, xcm: inner_xcm } => { - assert_eq!(network, &bridged_network); - let (_, target_location_junctions_without_global_consensus) = - target_location_from_different_consensus - .interior - .split_global() - .expect("split works"); - assert_eq!( - destination, - &target_location_junctions_without_global_consensus - ); - assert_matches_pallet_xcm_reserve_transfer_assets_instructions(inner_xcm); - Ok(()) - }, - _ => Err(ProcessMessageError::BadFormat), - }) - .expect("contains ExportMessage"); + // check sent XCM ExportMessage to BridgeHub + + // 1. check paid or unpaid + if let Some(expected_fee_asset_id) = maybe_paid_export_message { + xcm_sent + .0 + .matcher() + .match_next_inst(|instr| match instr { + WithdrawAsset(_) => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("contains WithdrawAsset") + .match_next_inst(|instr| match instr { + BuyExecution { fees, .. } if fees.id.eq(&expected_fee_asset_id) => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("contains BuyExecution") + } else { + xcm_sent + .0 + .matcher() + .match_next_inst(|instr| match instr { + // first instruction could be UnpaidExecution (because we could have + // explicit unpaid execution on BridgeHub) + UnpaidExecution { weight_limit, check_origin } + if weight_limit == &Unlimited && check_origin.is_none() => + Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("contains UnpaidExecution") + } + // 2. check ExportMessage + .match_next_inst(|instr| match instr { + // second instruction is ExportMessage + ExportMessage { network, destination, xcm: inner_xcm } => { + assert_eq!(network, &bridged_network); + let (_, target_location_junctions_without_global_consensus) = + target_location_from_different_consensus + .interior + .split_global() + .expect("split works"); + assert_eq!(destination, &target_location_junctions_without_global_consensus); + assert_matches_pallet_xcm_reserve_transfer_assets_instructions(inner_xcm); + Ok(()) + }, + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("contains ExportMessage"); }) } @@ -479,3 +497,97 @@ fn assert_matches_pallet_xcm_reserve_transfer_assets_instructions( }) .expect("expected instruction DepositAsset"); } + +pub fn report_bridge_status_from_xcm_bridge_router_works< + Runtime, + XcmConfig, + HrmpChannelOpener, + HrmpChannelSource, + LocationToAccountId, + XcmBridgeHubRouterInstance, +>( + collator_session_keys: CollatorSessionKeys, + existential_deposit: BalanceOf, + alice_account: AccountIdOf, + unwrap_pallet_xcm_event: Box) -> Option>>, + unwrap_xcmp_queue_event: Box< + dyn Fn(Vec) -> Option>, + >, + ensure_configuration: fn() -> TestBridgingConfig, + weight_limit: WeightLimit, + maybe_paid_export_message: Option, + congested_message: fn() -> Xcm, + uncongested_message: fn() -> Xcm, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config + + pallet_xcm_bridge_hub_router::Config, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + ::Balance: From + Into, + XcmConfig: xcm_executor::Config, + LocationToAccountId: ConvertLocation>, + ::AccountId: + Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + HrmpChannelOpener: frame_support::inherent::ProvideInherent< + Call = cumulus_pallet_parachain_system::Call, + >, + HrmpChannelSource: XcmpMessageSource, + XcmBridgeHubRouterInstance: 'static, +{ + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_tracing() + .build() + .execute_with(|| { + // check transfer works + limited_reserve_transfer_assets_for_native_asset_works::< + Runtime, + XcmConfig, + HrmpChannelOpener, + HrmpChannelSource, + LocationToAccountId, + >( + collator_session_keys, + existential_deposit, + alice_account, + unwrap_pallet_xcm_event, + unwrap_xcmp_queue_event, + ensure_configuration, + weight_limit, + maybe_paid_export_message, + ); + + let report_brigde_status = |is_congested: bool| { + // prepare bridge config + let TestBridgingConfig { local_bridge_hub_location, .. } = ensure_configuration(); + + // Call received XCM execution + let xcm = if is_congested { congested_message() } else { uncongested_message() }; + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + + // execute xcm as XcmpQueue would do + let outcome = XcmExecutor::::execute_xcm( + local_bridge_hub_location, + xcm, + hash, + RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + ); + assert_eq!(outcome.ensure_complete(), Ok(())); + assert_eq!(is_congested, pallet_xcm_bridge_hub_router::Pallet::::bridge().is_congested); + }; + + report_brigde_status(true); + report_brigde_status(false); + }) +} diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index 03b98dad740..e97e69c022e 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -269,7 +269,9 @@ This initialization does several things: - drips SA for AssetHubKusama on AssetHubPolkadot (and vice versa) which holds reserved assets on source chains ``` ./scripts/bridges_kusama_polkadot.sh init-asset-hub-kusama-local +./scripts/bridges_kusama_polkadot.sh init-bridge-hub-kusama-local ./scripts/bridges_kusama_polkadot.sh init-asset-hub-polkadot-local +./scripts/bridges_kusama_polkadot.sh init-bridge-hub-polkadot-local ``` ### 4. Send messages - transfer asset over bridge (DOTs/KSMs) diff --git a/parachains/runtimes/test-utils/src/lib.rs b/parachains/runtimes/test-utils/src/lib.rs index 4d0ff311f06..8a04d192974 100644 --- a/parachains/runtimes/test-utils/src/lib.rs +++ b/parachains/runtimes/test-utils/src/lib.rs @@ -378,16 +378,20 @@ impl RuntimeHelper unwrap_pallet_xcm_event: &Box) -> Option>>, assert_outcome: fn(Outcome), ) { - let outcome = >::events() + assert_outcome(Self::get_pallet_xcm_event_outcome(unwrap_pallet_xcm_event)); + } + + pub fn get_pallet_xcm_event_outcome( + unwrap_pallet_xcm_event: &Box) -> Option>>, + ) -> Outcome { + >::events() .into_iter() .filter_map(|e| unwrap_pallet_xcm_event(e.event.encode())) .find_map(|e| match e { pallet_xcm::Event::Attempted { outcome } => Some(outcome), _ => None, }) - .expect("No `pallet_xcm::Event::Attempted(outcome)` event found!"); - - assert_outcome(outcome); + .expect("No `pallet_xcm::Event::Attempted(outcome)` event found!") } } diff --git a/scripts/bridges_kusama_polkadot.sh b/scripts/bridges_kusama_polkadot.sh index 43ca5356d2c..a417ef49a9c 100755 --- a/scripts/bridges_kusama_polkadot.sh +++ b/scripts/bridges_kusama_polkadot.sh @@ -36,6 +36,12 @@ ASSET_HUB_POLKADOT_ACCOUNT_ADDRESS_FOR_LOCAL="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNe # &MultiLocation { parents: 2, interior: X2(GlobalConsensus(Kusama), Parachain(1000)) }).unwrap() # ).to_ss58check_with_version(0_u16.into()) # ); +# println!("ASSET_HUB_POLKADOT_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_POLKADOT=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# SiblingParachainConvertsVia::::convert_location( +# &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap() +# ).to_ss58check_with_version(0_u16.into()) +# ); # # // SS58=2 # println!("GLOBAL_CONSENSUS_POLKADOT_SOVEREIGN_ACCOUNT=\"{}\"", @@ -50,11 +56,19 @@ ASSET_HUB_POLKADOT_ACCOUNT_ADDRESS_FOR_LOCAL="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNe # &MultiLocation { parents: 2, interior: X2(GlobalConsensus(Polkadot), Parachain(1000)) }).unwrap() # ).to_ss58check_with_version(2_u16.into()) # ); +# println!("ASSET_HUB_KUSAMA_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_KUSAMA=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# SiblingParachainConvertsVia::::convert_location( +# &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap() +# ).to_ss58check_with_version(2_u16.into()) +# ); # } GLOBAL_CONSENSUS_KUSAMA_SOVEREIGN_ACCOUNT="14zcUAhP5XypiFQWA3b1AnGKrhZqR4XWUo4deWkwuN5y983G" GLOBAL_CONSENSUS_KUSAMA_ASSET_HUB_KUSAMA_1000_SOVEREIGN_ACCOUNT="12GvRkNCmXFuaaziTJ2ZKAfa7MArKfLT2HYvLjQuepP3JuHf" +ASSET_HUB_POLKADOT_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_POLKADOT="13cKp89SgdtqUngo2WiEijPrQWdHFhzYZLf2TJePKRvExk7o" GLOBAL_CONSENSUS_POLKADOT_SOVEREIGN_ACCOUNT="FxqimVubBRPqJ8kTwb3wL7G4q645hEkBEnXPyttLsTrFc5Q" GLOBAL_CONSENSUS_POLKADOT_ASSET_HUB_POLKADOT_1000_SOVEREIGN_ACCOUNT="FwGjEp7GXJXT9NjH8r4sqdyd8XZVogbxSs3iFakx4wFwJ5Y" +ASSET_HUB_KUSAMA_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_KUSAMA="FBeL7EFTDeHnuViqaUHUXvhhUusN5FawDmHgfvzF97DXFr3" function init_ksm_dot() { ensure_relayer @@ -136,6 +150,14 @@ case "$1" in "$GLOBAL_CONSENSUS_POLKADOT_ASSET_HUB_POLKADOT_1000_SOVEREIGN_ACCOUNT" \ $((1000000000 + 50000000000 * 20)) ;; + init-bridge-hub-kusama-local) + # SA of sibling asset hub pays for the execution + transfer_balance \ + "ws://127.0.0.1:8943" \ + "//Alice" \ + "$ASSET_HUB_KUSAMA_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_KUSAMA" \ + $((1000000000 + 50000000000 * 20)) + ;; init-asset-hub-polkadot-local) # create foreign assets for native Polkadot token (governance call on Kusama) force_create_foreign_asset \ @@ -154,6 +176,14 @@ case "$1" in "$GLOBAL_CONSENSUS_KUSAMA_ASSET_HUB_KUSAMA_1000_SOVEREIGN_ACCOUNT" \ $((1000000000 + 50000000000 * 20)) ;; + init-bridge-hub-polkadot-local) + # SA of sibling asset hub pays for the execution + transfer_balance \ + "ws://127.0.0.1:8945" \ + "//Alice" \ + "$ASSET_HUB_POLKADOT_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_POLKADOT" \ + $((1000000000 + 50000000000 * 20)) + ;; reserve-transfer-assets-from-asset-hub-kusama-local) ensure_polkadot_js_api # send KSMs to Alice account on AHP @@ -187,7 +217,9 @@ case "$1" in Local (zombienet) run: - run-relay - init-asset-hub-kusama-local + - init-bridge-hub-kusama-local - init-asset-hub-polkadot-local + - init-bridge-hub-polkadot-local - reserve-transfer-assets-from-asset-hub-kusama-local - reserve-transfer-assets-from-asset-hub-polkadot-local"; exit 1