Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Transfer asset via bridge pallet xcm with dynamic fees and back-pressure #2997

Open
wants to merge 31 commits into
base: bko-transfer-asset-via-bridge-pallet-xcm
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
429d6b0
Squashed 'bridges/' changes from edf33a2c85..8f86ec78b7
bkontur Aug 10, 2023
6544a5c
Merge commit '429d6b09cab2379e1a118bc4d59dc8f935c5ae2d' into bko-tran…
bkontur Aug 10, 2023
18b425a
[dynfees] Rococo/Wococo does not need congestion and dynamic fees (fo…
bkontur Aug 11, 2023
a399514
Adding paid xcm bridge hub router
bkontur Aug 11, 2023
e5a03a3
[dynfees] Implemented `XcmChannelStatusProvider` for `XcmpQueue`
bkontur Aug 11, 2023
025f109
Allow to change `XcmBridgeHubRouterByteFee` by governance
bkontur Aug 14, 2023
d48d27e
Adding paid xcm bridge hub router to AssetHubPolkadot
bkontur Aug 14, 2023
8b92cd7
Extended script for local run BHK/P
bkontur Aug 14, 2023
43cda64
Docs nits
bkontur Aug 14, 2023
e86d89e
Added XcmBridgeHubRouterCall::report_bridge_status encodings for AHK/P
bkontur Aug 15, 2023
130646c
Added `test_report_bridge_status_call_compatibility` for encodings
bkontur Aug 15, 2023
c82f585
Allowed `xcm::Transact(pallet_xcm_bridge_hub_router::Call::report_bri…
bkontur Aug 15, 2023
05d504a
Added const for `XcmBridgeHubRouterTransactCallMaxWeight`
bkontur Aug 15, 2023
356969d
Added constant for base fee (with sanity check)
bkontur Aug 16, 2023
0a54659
Introduced base delivery fee constants
bkontur Aug 16, 2023
824a480
Congestion messages as Optional to turn on/off `supports_congestion_d…
bkontur Aug 16, 2023
6363103
Fix for BHRo/Wo
bkontur Aug 16, 2023
7fd6d26
Benchmarks for router
bkontur Aug 16, 2023
0f45881
Benchmarks setup for router
bkontur Aug 16, 2023
4bfd8d2
Fix origin for benchmarks
bkontur Aug 16, 2023
22a644e
More fix
bkontur Aug 16, 2023
08642bd
Fix for benchmarks
bkontur Aug 16, 2023
c624475
Fix for benchmarks - need hrmp opened
bkontur Aug 16, 2023
9b045f9
Removed WeightInfo TODO
bkontur Aug 16, 2023
5d7b413
Merge remote-tracking branch 'origin/bko-transfer-asset-via-bridge-pa…
bkontur Aug 16, 2023
7c87577
Fmt
bkontur Aug 16, 2023
969a48f
Merge remote-tracking branch 'origin/bko-transfer-asset-via-bridge-pa…
bkontur Aug 16, 2023
7dd0ea9
Merge remote-tracking branch 'origin/bko-transfer-asset-via-bridge-pa…
bkontur Aug 17, 2023
3f7c7f7
".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime…
Aug 17, 2023
a5f04fe
Apply git patch from job: https://gitlab.parity.io/parity/mirrors/cum…
bkontur Aug 17, 2023
cbcfb6f
Apply suggestions from code review
acatangiu Aug 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 8 additions & 0 deletions pallets/xcmp-queue/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,6 +58,7 @@ std = [
"sp-std/std",
"xcm-executor/std",
"xcm/std",
"bp-xcm-bridge-hub-router/std",
]

runtime-benchmarks = [
Expand All @@ -64,3 +68,7 @@ runtime-benchmarks = [
"xcm-builder/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]

bridging = [
"bp-xcm-bridge-hub-router",
]
116 changes: 116 additions & 0 deletions pallets/xcmp-queue/src/bridging.rs
Original file line number Diff line number Diff line change
@@ -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<SiblingBridgeHubParaId, Runtime>(
sp_std::marker::PhantomData<(SiblingBridgeHubParaId, Runtime)>,
);
impl<SiblingBridgeHubParaId: Get<ParaId>, Runtime: crate::Config>
bp_xcm_bridge_hub_router::XcmChannelStatusProvider
for InboundAndOutboundXcmpChannelCongestionStatusProvider<SiblingBridgeHubParaId, Runtime>
{
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::<Runtime>::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::<Runtime>::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<SiblingBridgeHubParaId, Runtime>(
sp_std::marker::PhantomData<(SiblingBridgeHubParaId, Runtime)>,
);
impl<SiblingParaId: Get<ParaId>, Runtime: crate::Config>
bp_xcm_bridge_hub_router::XcmChannelStatusProvider
for OutboundXcmpChannelCongestionStatusProvider<SiblingParaId, Runtime>
{
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::<Runtime>::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<T: crate::Config>(target: ParaId) {
pallet::Pallet::<T>::suspend_channel(target)
}
19 changes: 19 additions & 0 deletions pallets/xcmp-queue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)]
Expand Down
66 changes: 62 additions & 4 deletions parachains/common/src/xcm_config.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<RuntimeCall, AllowedCall, AllowedOrigin>(
sp_std::marker::PhantomData<(RuntimeCall, AllowedCall, AllowedOrigin)>,
);
impl<
RuntimeCall: Decode,
AllowedCall: Contains<RuntimeCall>,
AllowedOrigin: Contains<MultiLocation>,
> ShouldExecute for AllowUnpaidTransactsFrom<RuntimeCall, AllowedCall, AllowedOrigin>
{
fn should_execute<Call>(
origin: &MultiLocation,
instructions: &mut [Instruction<Call>],
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::<RuntimeCall>::from(encoded_call.clone());
ensure!(
AllowedCall::contains(
decoded_call
.ensure_decoded()
.map_err(|_| ProcessMessageError::BadFormat)?
),
ProcessMessageError::BadFormat,
);
acatangiu marked this conversation as resolved.
Show resolved Hide resolved

Ok(())
},
_ => Err(ProcessMessageError::BadFormat),
})?;

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading