Skip to content

Commit 0fd0e7d

Browse files
bkonturacatangiusvyatonik
committed
Transfer reserve asset with dynamic fees and back-pressure.
Change XCM routing configuration for bridging from unpaid to paid version. Paid version means that origin (besides extrinsic fees) pays delivery fees at source Asset Hub and also Asset Hub sovereign account pays for execution of `ExportMessage` instruction at local Bridge Hub. Change XCM bridging router from `UnpaidRemoteExporter` to `ToPolkadotXcmRouter` and `ToKusamaXcmRouter` which are pallet instances of new module `pallet-xcm-bridge-hub-router`. The main thing that the pallet `pallet-xcm-bridge-hub-router` offers is the dynamic message fee, that is computed based on the bridge queues state. It starts exponentially increasing if the queue between this chain and the sibling/child bridge hub is congested. More about dynamic fees and back-preasure for v1 can be found [here](paritytech/parity-bridges-common#2294). Signed-off-by: Branislav Kontur <[email protected]> Signed-off-by: Adrian Catangiu <[email protected]> Signed-off-by: Svyatoslav Nikolsky <[email protected]> Co-authored-by: Adrian Catangiu <[email protected]> Co-authored-by: Svyatoslav Nikolsky <[email protected]>
1 parent 67c48fd commit 0fd0e7d

22 files changed

Lines changed: 1134 additions & 70 deletions

File tree

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cumulus/pallets/xcmp-queue/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ cumulus-primitives-core = { path = "../../primitives/core", default-features = f
2828
# Optional import for benchmarking
2929
frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true}
3030

31+
# Bridges
32+
bp-xcm-bridge-hub-router = { path = "../../bridges/primitives/xcm-bridge-hub-router", default-features = false, optional = true }
33+
3134
[dev-dependencies]
3235

3336
# Substrate
@@ -43,6 +46,7 @@ cumulus-pallet-parachain-system = { path = "../parachain-system" }
4346
[features]
4447
default = [ "std" ]
4548
std = [
49+
"bp-xcm-bridge-hub-router/std",
4650
"codec/std",
4751
"cumulus-primitives-core/std",
4852
"frame-benchmarking?/std",
@@ -77,3 +81,4 @@ try-runtime = [
7781
"polkadot-runtime-common/try-runtime",
7882
"sp-runtime/try-runtime",
7983
]
84+
bridging = [ "bp-xcm-bridge-hub-router" ]
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (C) Parity Technologies (UK) Ltd.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
use crate::pallet;
17+
use cumulus_primitives_core::ParaId;
18+
use frame_support::pallet_prelude::Get;
19+
20+
/// Adapter implementation for `bp_xcm_bridge_hub_router::XcmChannelStatusProvider` which checks
21+
/// both `OutboundXcmpStatus` and `InboundXcmpStatus` for defined `ParaId` if any of those is
22+
/// suspended.
23+
pub struct InboundAndOutboundXcmpChannelCongestionStatusProvider<SiblingBridgeHubParaId, Runtime>(
24+
sp_std::marker::PhantomData<(SiblingBridgeHubParaId, Runtime)>,
25+
);
26+
impl<SiblingBridgeHubParaId: Get<ParaId>, Runtime: crate::Config>
27+
bp_xcm_bridge_hub_router::XcmChannelStatusProvider
28+
for InboundAndOutboundXcmpChannelCongestionStatusProvider<SiblingBridgeHubParaId, Runtime>
29+
{
30+
fn is_congested() -> bool {
31+
// if the outbound channel with recipient is suspended, it means that one of further
32+
// bridge queues (e.g. bridge queue between two bridge hubs) is overloaded, so we shall
33+
// take larger fee for our outbound messages
34+
let sibling_bridge_hub_id: ParaId = SiblingBridgeHubParaId::get();
35+
let outbound_channels = pallet::OutboundXcmpStatus::<Runtime>::get();
36+
let outbound_channel =
37+
outbound_channels.iter().find(|c| c.recipient == sibling_bridge_hub_id);
38+
let is_outbound_channel_suspended =
39+
outbound_channel.map(|c| c.is_suspended()).unwrap_or(false);
40+
if is_outbound_channel_suspended {
41+
return true
42+
}
43+
44+
// if the inbound channel with recipient is suspended, it means that we are unable to
45+
// receive congestion reports from the bridge hub. So we assume the bridge pipeline is
46+
// congested too
47+
let inbound_channels = pallet::InboundXcmpStatus::<Runtime>::get();
48+
let inbound_channel = inbound_channels.iter().find(|c| c.sender == sibling_bridge_hub_id);
49+
let is_inbound_channel_suspended =
50+
inbound_channel.map(|c| c.is_suspended()).unwrap_or(false);
51+
if is_inbound_channel_suspended {
52+
return true
53+
}
54+
55+
// TODO: https://github.com/paritytech/cumulus/pull/2342 - once this PR is merged, we may
56+
// remove the following code
57+
//
58+
// if the outbound channel has at least `N` pages enqueued, let's assume it is congested.
59+
// Normally, the chain with a few opened HRMP channels, will "send" pages at every block.
60+
// Having `N` pages means that for last `N` blocks we either have not sent any messages,
61+
// or have sent signals.
62+
const MAX_OUTBOUND_PAGES_BEFORE_CONGESTION: u16 = 4;
63+
let is_outbound_channel_congested = outbound_channel.map(|c| c.queued_pages()).unwrap_or(0);
64+
is_outbound_channel_congested > MAX_OUTBOUND_PAGES_BEFORE_CONGESTION
65+
}
66+
}
67+
68+
/// Adapter implementation for `bp_xcm_bridge_hub_router::XcmChannelStatusProvider` which checks
69+
/// only `OutboundXcmpStatus` for defined `SiblingParaId` if is suspended.
70+
pub struct OutboundXcmpChannelCongestionStatusProvider<SiblingBridgeHubParaId, Runtime>(
71+
sp_std::marker::PhantomData<(SiblingBridgeHubParaId, Runtime)>,
72+
);
73+
impl<SiblingParaId: Get<ParaId>, Runtime: crate::Config>
74+
bp_xcm_bridge_hub_router::XcmChannelStatusProvider
75+
for OutboundXcmpChannelCongestionStatusProvider<SiblingParaId, Runtime>
76+
{
77+
fn is_congested() -> bool {
78+
// let's find the channel with the sibling parachain
79+
let sibling_para_id: cumulus_primitives_core::ParaId = SiblingParaId::get();
80+
let outbound_channels = pallet::OutboundXcmpStatus::<Runtime>::get();
81+
let channel_with_sibling_parachain =
82+
outbound_channels.iter().find(|c| c.recipient == sibling_para_id);
83+
84+
// no channel => it is empty, so not congested
85+
let channel_with_sibling_parachain = match channel_with_sibling_parachain {
86+
Some(channel_with_sibling_parachain) => channel_with_sibling_parachain,
87+
None => return false,
88+
};
89+
90+
// suspended channel => it is congested
91+
if channel_with_sibling_parachain.is_suspended() {
92+
return true
93+
}
94+
95+
// TODO: the following restriction is arguable, we may live without that, assuming that
96+
// there can't be more than some `N` messages queued at the bridge queue (at the source BH)
97+
// AND before accepting next (or next-after-next) delivery transaction, we'll receive the
98+
// suspension signal from the target parachain and stop accepting delivery transactions
99+
100+
// it takes some time for target parachain to suspend inbound channel with the target BH and
101+
// during that we will keep accepting new message delivery transactions. Let's also reject
102+
// new deliveries if there are too many "pages" (concatenated XCM messages) in the target BH
103+
// -> target parachain queue.
104+
const MAX_QUEUED_PAGES_BEFORE_DEACTIVATION: u16 = 4;
105+
if channel_with_sibling_parachain.queued_pages() > MAX_QUEUED_PAGES_BEFORE_DEACTIVATION {
106+
return true
107+
}
108+
109+
true
110+
}
111+
}
112+
113+
#[cfg(feature = "runtime-benchmarks")]
114+
pub fn suspend_channel_for_benchmarks<T: crate::Config>(target: ParaId) {
115+
pallet::Pallet::<T>::suspend_channel(target)
116+
}

cumulus/pallets/xcmp-queue/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ mod tests;
3535

3636
#[cfg(feature = "runtime-benchmarks")]
3737
mod benchmarking;
38+
#[cfg(feature = "bridging")]
39+
pub mod bridging;
3840
pub mod weights;
3941
pub use weights::WeightInfo;
4042

@@ -400,6 +402,13 @@ pub struct InboundChannelDetails {
400402
message_metadata: Vec<(RelayBlockNumber, XcmpMessageFormat)>,
401403
}
402404

405+
impl InboundChannelDetails {
406+
#[cfg(feature = "bridging")]
407+
pub(crate) fn is_suspended(&self) -> bool {
408+
self.state == InboundState::Suspended
409+
}
410+
}
411+
403412
/// Struct containing detailed information about the outbound channel.
404413
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
405414
pub struct OutboundChannelDetails {
@@ -435,6 +444,16 @@ impl OutboundChannelDetails {
435444
self.state = OutboundState::Suspended;
436445
self
437446
}
447+
448+
#[cfg(feature = "bridging")]
449+
pub(crate) fn is_suspended(&self) -> bool {
450+
self.state == OutboundState::Suspended
451+
}
452+
453+
#[cfg(feature = "bridging")]
454+
pub(crate) fn queued_pages(&self) -> u16 {
455+
self.last_index.saturating_sub(self.first_index)
456+
}
438457
}
439458

440459
#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]

cumulus/parachains/common/src/xcm_config.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,22 @@
1414
// limitations under the License.
1515

1616
use crate::impls::AccountIdOf;
17+
use codec::Decode;
1718
use core::marker::PhantomData;
1819
use frame_support::{
19-
traits::{fungibles::Inspect, tokens::ConversionToAssetBalance, ContainsPair},
20+
ensure,
21+
traits::{
22+
fungibles::Inspect, tokens::ConversionToAssetBalance, Contains, ContainsPair,
23+
ProcessMessageError,
24+
},
2025
weights::Weight,
2126
DefaultNoBound,
2227
};
2328
use log;
2429
use sp_runtime::traits::Get;
25-
use xcm::latest::prelude::*;
26-
use xcm_builder::ExporterFor;
30+
use xcm::{latest::prelude::*, DoubleEncoded};
31+
use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm};
32+
use xcm_executor::traits::ShouldExecute;
2733

2834
/// A `ChargeFeeInFungibles` implementation that converts the output of
2935
/// a given WeightToFee implementation an amount charged in
@@ -171,6 +177,59 @@ impl<
171177
}
172178
}
173179

180+
/// Allows execution from `origin` if it is contained in `AllowedOrigin`
181+
/// and if it is just a straight `Transact` which contains `AllowedCall`.
182+
pub struct AllowUnpaidTransactsFrom<RuntimeCall, AllowedCall, AllowedOrigin>(
183+
sp_std::marker::PhantomData<(RuntimeCall, AllowedCall, AllowedOrigin)>,
184+
);
185+
impl<
186+
RuntimeCall: Decode,
187+
AllowedCall: Contains<RuntimeCall>,
188+
AllowedOrigin: Contains<MultiLocation>,
189+
> ShouldExecute for AllowUnpaidTransactsFrom<RuntimeCall, AllowedCall, AllowedOrigin>
190+
{
191+
fn should_execute<Call>(
192+
origin: &MultiLocation,
193+
instructions: &mut [Instruction<Call>],
194+
max_weight: Weight,
195+
_properties: &mut xcm_executor::traits::Properties,
196+
) -> Result<(), ProcessMessageError> {
197+
log::trace!(
198+
target: "xcm::barriers",
199+
"AllowUnpaidTransactFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}",
200+
origin, instructions, max_weight, _properties,
201+
);
202+
203+
// we only allow from configured origins
204+
ensure!(AllowedOrigin::contains(origin), ProcessMessageError::Unsupported);
205+
206+
// we expect an XCM program with single `Transact` call
207+
instructions
208+
.matcher()
209+
.assert_remaining_insts(1)?
210+
.match_next_inst(|inst| match inst {
211+
Transact { origin_kind: OriginKind::Xcm, call: encoded_call, .. } => {
212+
// this is a hack - don't know if there's a way to do that properly
213+
// or else we can simply allow all calls
214+
let mut decoded_call = DoubleEncoded::<RuntimeCall>::from(encoded_call.clone());
215+
ensure!(
216+
AllowedCall::contains(
217+
decoded_call
218+
.ensure_decoded()
219+
.map_err(|_| ProcessMessageError::BadFormat)?
220+
),
221+
ProcessMessageError::BadFormat,
222+
);
223+
224+
Ok(())
225+
},
226+
_ => Err(ProcessMessageError::BadFormat),
227+
})?;
228+
229+
Ok(())
230+
}
231+
}
232+
174233
#[cfg(test)]
175234
mod tests {
176235
use super::*;

cumulus/parachains/runtimes/assets/asset-hub-kusama/Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,18 @@ cumulus-pallet-dmp-queue = { path = "../../../../pallets/dmp-queue", default-fea
7171
cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook",] }
7272
cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false}
7373
cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false }
74-
cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false }
74+
cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] }
7575
cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false }
7676
cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false }
7777
pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false }
7878
parachain-info = { path = "../../../pallets/parachain-info", default-features = false }
7979
parachains-common = { path = "../../../common", default-features = false }
8080
assets-common = { path = "../common", default-features = false }
8181

82+
# Bridges
83+
pallet-xcm-bridge-hub-router = { path = "../../../../bridges/modules/xcm-bridge-hub-router", default-features = false }
84+
bp-asset-hub-kusama = { path = "../../../../bridges/primitives/chain-asset-hub-kusama", default-features = false }
85+
8286
[dev-dependencies]
8387
asset-test-utils = { path = "../test-utils" }
8488

@@ -117,6 +121,7 @@ runtime-benchmarks = [
117121
"pallet-uniques/runtime-benchmarks",
118122
"pallet-utility/runtime-benchmarks",
119123
"pallet-xcm-benchmarks/runtime-benchmarks",
124+
"pallet-xcm-bridge-hub-router/runtime-benchmarks",
120125
"pallet-xcm/runtime-benchmarks",
121126
"polkadot-parachain-primitives/runtime-benchmarks",
122127
"polkadot-runtime-common/runtime-benchmarks",
@@ -151,13 +156,15 @@ try-runtime = [
151156
"pallet-transaction-payment/try-runtime",
152157
"pallet-uniques/try-runtime",
153158
"pallet-utility/try-runtime",
159+
"pallet-xcm-bridge-hub-router/try-runtime",
154160
"pallet-xcm/try-runtime",
155161
"parachain-info/try-runtime",
156162
"polkadot-runtime-common/try-runtime",
157163
"sp-runtime/try-runtime",
158164
]
159165
std = [
160166
"assets-common/std",
167+
"bp-asset-hub-kusama/std",
161168
"codec/std",
162169
"cumulus-pallet-aura-ext/std",
163170
"cumulus-pallet-dmp-queue/std",
@@ -196,6 +203,7 @@ std = [
196203
"pallet-uniques/std",
197204
"pallet-utility/std",
198205
"pallet-xcm-benchmarks?/std",
206+
"pallet-xcm-bridge-hub-router/std",
199207
"pallet-xcm/std",
200208
"parachain-info/std",
201209
"parachains-common/std",

0 commit comments

Comments
 (0)