From 74b48e2cc3bb30124d0b2b1e029b91fa16614809 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 14 Jul 2023 11:30:10 +0300 Subject: [PATCH 01/31] impl backpressure in the XcmBlobHaulerAdapter --- Cargo.lock | 1 + bin/millau/runtime/src/rialto_messages.rs | 6 +- .../runtime/src/rialto_parachain_messages.rs | 6 +- .../runtime/src/millau_messages.rs | 11 +- bin/rialto/runtime/src/millau_messages.rs | 6 +- bin/runtime-common/Cargo.toml | 2 + .../src/messages_xcm_extension.rs | 108 +++++++++++++++++- modules/messages/src/lib.rs | 5 +- primitives/messages/src/source_chain.rs | 4 +- 9 files changed, 139 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 128f6b4f74..ea5077397d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1169,6 +1169,7 @@ dependencies = [ "bp-relayers", "bp-runtime", "bp-test-utils", + "bp-xcm-bridge-hub", "frame-support", "frame-system", "hash-db", diff --git a/bin/millau/runtime/src/rialto_messages.rs b/bin/millau/runtime/src/rialto_messages.rs index 04084e0b95..ab20348689 100644 --- a/bin/millau/runtime/src/rialto_messages.rs +++ b/bin/millau/runtime/src/rialto_messages.rs @@ -114,7 +114,7 @@ impl messages::BridgedChainWithMessages for Rialto {} /// Export XCM messages to be relayed to Rialto. pub type ToRialtoBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter, + XcmBlobHaulerAdapter>, crate::xcm_config::RialtoNetwork, (), >; @@ -131,6 +131,10 @@ impl XcmBlobHauler for ToRialtoXcmBlobHauler { .into() } + fn sending_chain_location() -> MultiLocation { + Here.into() + } + fn xcm_lane() -> LaneId { XCM_LANE } diff --git a/bin/millau/runtime/src/rialto_parachain_messages.rs b/bin/millau/runtime/src/rialto_parachain_messages.rs index 1050f500fe..138f1d5b9d 100644 --- a/bin/millau/runtime/src/rialto_parachain_messages.rs +++ b/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -114,7 +114,7 @@ impl messages::BridgedChainWithMessages for RialtoParachain {} /// Export XCM messages to be relayed to Rialto. pub type ToRialtoParachainBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter, + XcmBlobHaulerAdapter>, crate::xcm_config::RialtoParachainNetwork, (), >; @@ -132,6 +132,10 @@ impl XcmBlobHauler for ToRialtoParachainXcmBlobHauler { .into() } + fn sending_chain_location() -> MultiLocation { + Here.into() + } + fn xcm_lane() -> LaneId { XCM_LANE } diff --git a/bin/rialto-parachain/runtime/src/millau_messages.rs b/bin/rialto-parachain/runtime/src/millau_messages.rs index 68c9cbbec6..359c29ab64 100644 --- a/bin/rialto-parachain/runtime/src/millau_messages.rs +++ b/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -116,8 +116,11 @@ impl messages::UnderlyingChainProvider for Millau { impl messages::BridgedChainWithMessages for Millau {} /// Export XCM messages to be relayed to Millau. -pub type ToMillauBlobExporter = - HaulBlobExporter, crate::MillauNetwork, ()>; +pub type ToMillauBlobExporter = HaulBlobExporter< + XcmBlobHaulerAdapter>, + crate::MillauNetwork, + (), +>; /// To-Millau XCM hauler. pub struct ToMillauXcmBlobHauler; @@ -130,6 +133,10 @@ impl XcmBlobHauler for ToMillauXcmBlobHauler { pallet_xcm::Origin::from(MultiLocation::new(1, crate::UniversalLocation::get())).into() } + fn sending_chain_location() -> MultiLocation { + Here.into() + } + fn xcm_lane() -> LaneId { XCM_LANE } diff --git a/bin/rialto/runtime/src/millau_messages.rs b/bin/rialto/runtime/src/millau_messages.rs index a99243cc3e..48927578fd 100644 --- a/bin/rialto/runtime/src/millau_messages.rs +++ b/bin/rialto/runtime/src/millau_messages.rs @@ -113,7 +113,7 @@ impl messages::BridgedChainWithMessages for Millau {} /// Export XCM messages to be relayed to Millau. pub type ToMillauBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter, + XcmBlobHaulerAdapter>, crate::xcm_config::MillauNetwork, (), >; @@ -130,6 +130,10 @@ impl XcmBlobHauler for ToMillauXcmBlobHauler { .into() } + fn sending_chain_location() -> MultiLocation { + Here.into() + } + fn xcm_lane() -> LaneId { XCM_LANE } diff --git a/bin/runtime-common/Cargo.toml b/bin/runtime-common/Cargo.toml index 98b3bc9925..d416482e5c 100644 --- a/bin/runtime-common/Cargo.toml +++ b/bin/runtime-common/Cargo.toml @@ -21,6 +21,7 @@ bp-parachains = { path = "../../primitives/parachains", default-features = false bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false } bp-relayers = { path = "../../primitives/relayers", default-features = false } bp-runtime = { path = "../../primitives/runtime", default-features = false } +bp-xcm-bridge-hub = { path = "../../primitives/xcm-bridge-hub", default-features = false } pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false } pallet-bridge-messages = { path = "../../modules/messages", default-features = false } pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false } @@ -55,6 +56,7 @@ std = [ "bp-parachains/std", "bp-polkadot-core/std", "bp-runtime/std", + "bp-xcm-bridge-hub/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 96fdf1d501..72c938fab0 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -24,7 +24,7 @@ use bp_messages::{ source_chain::MessagesBridge, target_chain::{DispatchMessage, MessageDispatch}, - LaneId, + LaneId, MessageNonce, }; use bp_runtime::messages::MessageDispatchResult; use codec::{Decode, Encode}; @@ -117,6 +117,8 @@ pub trait XcmBlobHauler { /// Our location within the Consensus Universe. fn message_sender_origin() -> Self::MessageSenderOrigin; + /// Returns the relative XCM location of the message sending chain. + fn sending_chain_location() -> MultiLocation; /// Return message lane (as "point-to-point link") used to deliver XCM messages. fn xcm_lane() -> LaneId; } @@ -135,9 +137,32 @@ impl> HaulBlo log::info!( target: crate::LOG_TARGET_BRIDGE_DISPATCH, "haul_blob result - ok: {:?} on lane: {:?}", - result, + msg_hash, lane - ) + ); + + // suspend the inbound XCM channel with the sender to avoid enqueueing more messages + // at the outbound bridge queue AND turn on internal backpressure mechanism of the + // XCM queue + let is_overloaded = artifacts.enqueued_messages > L::get(); + if is_overloaded { + let sending_chain_location = H::sending_chain_location(); + let suspend_result = C::suspend_inbound_channel(sending_chain_location); + match suspend_result { + Ok(_) => log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Suspended inbound XCM channel with {:?} to avoid overloading lane {:?}", + sending_chain_location, + lane, + ), + Err(_) => log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Failed to suspend inbound XCM channel with {:?} to avoid overloading lane {:?}", + sending_chain_location, + lane, + ), + } + } }) .map_err(|error| { log::error!( @@ -150,3 +175,80 @@ impl> HaulBlo }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::run_test; + + use bp_messages::source_chain::SendMessageArtifacts; + use frame_support::traits::{ConstU64, Get}; + + struct TestMessageSender; + + impl MessagesBridge> for TestMessageSender { + type Error = (); + + fn send_message( + _lane: LaneId, + message: Vec, + ) -> Result { + let nonce = message[0] as _; + Ok(SendMessageArtifacts { nonce, enqueued_messages: nonce }) + } + } + + struct TestBlobHauler; + + impl XcmBlobHauler for TestBlobHauler { + type MessageSender = TestMessageSender; + + fn sending_chain_location() -> MultiLocation { + Here.into() + } + + fn xcm_lane() -> LaneId { + LaneId::new(0, 0) + } + } + + struct TestXcmChannelManager; + + impl TestXcmChannelManager { + fn is_suspended(owner: MultiLocation) -> bool { + frame_support::storage::unhashed::take(&owner.encode()) == Some(42) + } + } + + impl LocalXcmChannelManager for TestXcmChannelManager { + fn suspend_inbound_channel(owner: MultiLocation) -> Result<(), ()> { + frame_support::storage::unhashed::put(&owner.encode(), &42); + Ok(()) + } + + fn resume_inbound_channel(_owner: MultiLocation) -> Result<(), ()> { + unreachable!("") + } + } + + type OverloadedLimit = ConstU64<8>; + type TestAdapter = XcmBlobHaulerAdapter; + + #[test] + fn blob_haulder_adapter_suspends_inbound_xcm_channel_when_bridge_is_overloaded() { + run_test(|| { + // first `OverloadedLimit` messages => no suspension + for nonce in 1..=OverloadedLimit::get() { + TestAdapter::haul_blob(vec![nonce as u8]).unwrap(); + assert!(!TestXcmChannelManager::is_suspended( + TestBlobHauler::sending_chain_location() + )); + } + + // next message => channel is suspended + let overloaded_nonce = >::get() + 1; + TestAdapter::haul_blob(vec![overloaded_nonce as u8]).unwrap(); + assert!(TestXcmChannelManager::is_suspended(TestBlobHauler::sending_chain_location())); + }); + } +} diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 3bfd5bc7e9..a4f66fb7ac 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -708,6 +708,9 @@ fn send_message, I: 'static>( .send_message(encoded_payload) .map_err(Error::::MessageRejectedByPallet)?; + // return number of messages in the queue to let sender know about its state + let enqueued_messages = lane.queued_messages().checked_len().unwrap_or(0); + log::trace!( target: LOG_TARGET, "Accepted message {} to lane {:?}. Message size: {:?}", @@ -718,7 +721,7 @@ fn send_message, I: 'static>( Pallet::::deposit_event(Event::MessageAccepted { lane_id, nonce }); - Ok(SendMessageArtifacts { nonce }) + Ok(SendMessageArtifacts { nonce, enqueued_messages }) } /// Ensure that the pallet is in normal operational mode. diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs index f3c50b84a0..cf2ac3a3df 100644 --- a/primitives/messages/src/source_chain.rs +++ b/primitives/messages/src/source_chain.rs @@ -121,6 +121,8 @@ impl DeliveryConfirmationPayments for () { pub struct SendMessageArtifacts { /// Nonce of the message. pub nonce: MessageNonce, + /// Number of enqueued messages at the lane, after the message is sent. + pub enqueued_messages: MessageNonce, } /// Messages bridge API to be used from other pallets. @@ -150,7 +152,7 @@ impl MessagesBridge for NoopMessag _lane: LaneId, _message: Payload, ) -> Result { - Ok(SendMessageArtifacts { nonce: 0 }) + Ok(SendMessageArtifacts { nonce: 0, enqueued_messages: 0 }) } } From 65787da038e39c627c43db05957d0eea95768202 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 21 Jul 2023 11:19:11 +0300 Subject: [PATCH 02/31] LocalXcmQueueManager + more adapters --- Cargo.lock | 1 - bin/millau/runtime/src/rialto_messages.rs | 2 +- .../runtime/src/rialto_parachain_messages.rs | 2 +- .../runtime/src/millau_messages.rs | 7 +- bin/rialto/runtime/src/millau_messages.rs | 2 +- bin/runtime-common/Cargo.toml | 2 - .../src/messages_xcm_extension.rs | 254 ++++++++++++------ modules/messages/src/lib.rs | 4 +- modules/messages/src/outbound_lane.rs | 9 +- 9 files changed, 185 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea5077397d..128f6b4f74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1169,7 +1169,6 @@ dependencies = [ "bp-relayers", "bp-runtime", "bp-test-utils", - "bp-xcm-bridge-hub", "frame-support", "frame-system", "hash-db", diff --git a/bin/millau/runtime/src/rialto_messages.rs b/bin/millau/runtime/src/rialto_messages.rs index ab20348689..2ee0685734 100644 --- a/bin/millau/runtime/src/rialto_messages.rs +++ b/bin/millau/runtime/src/rialto_messages.rs @@ -114,7 +114,7 @@ impl messages::BridgedChainWithMessages for Rialto {} /// Export XCM messages to be relayed to Rialto. pub type ToRialtoBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter>, + XcmBlobHaulerAdapter, crate::xcm_config::RialtoNetwork, (), >; diff --git a/bin/millau/runtime/src/rialto_parachain_messages.rs b/bin/millau/runtime/src/rialto_parachain_messages.rs index 138f1d5b9d..10e6cf26dd 100644 --- a/bin/millau/runtime/src/rialto_parachain_messages.rs +++ b/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -114,7 +114,7 @@ impl messages::BridgedChainWithMessages for RialtoParachain {} /// Export XCM messages to be relayed to Rialto. pub type ToRialtoParachainBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter>, + XcmBlobHaulerAdapter, crate::xcm_config::RialtoParachainNetwork, (), >; diff --git a/bin/rialto-parachain/runtime/src/millau_messages.rs b/bin/rialto-parachain/runtime/src/millau_messages.rs index 359c29ab64..8819870747 100644 --- a/bin/rialto-parachain/runtime/src/millau_messages.rs +++ b/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -116,11 +116,8 @@ impl messages::UnderlyingChainProvider for Millau { impl messages::BridgedChainWithMessages for Millau {} /// Export XCM messages to be relayed to Millau. -pub type ToMillauBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter>, - crate::MillauNetwork, - (), ->; +pub type ToMillauBlobExporter = + HaulBlobExporter, crate::MillauNetwork, ()>; /// To-Millau XCM hauler. pub struct ToMillauXcmBlobHauler; diff --git a/bin/rialto/runtime/src/millau_messages.rs b/bin/rialto/runtime/src/millau_messages.rs index 48927578fd..c776c6ae22 100644 --- a/bin/rialto/runtime/src/millau_messages.rs +++ b/bin/rialto/runtime/src/millau_messages.rs @@ -113,7 +113,7 @@ impl messages::BridgedChainWithMessages for Millau {} /// Export XCM messages to be relayed to Millau. pub type ToMillauBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter>, + XcmBlobHaulerAdapter, crate::xcm_config::MillauNetwork, (), >; diff --git a/bin/runtime-common/Cargo.toml b/bin/runtime-common/Cargo.toml index d416482e5c..98b3bc9925 100644 --- a/bin/runtime-common/Cargo.toml +++ b/bin/runtime-common/Cargo.toml @@ -21,7 +21,6 @@ bp-parachains = { path = "../../primitives/parachains", default-features = false bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false } bp-relayers = { path = "../../primitives/relayers", default-features = false } bp-runtime = { path = "../../primitives/runtime", default-features = false } -bp-xcm-bridge-hub = { path = "../../primitives/xcm-bridge-hub", default-features = false } pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false } pallet-bridge-messages = { path = "../../modules/messages", default-features = false } pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false } @@ -56,7 +55,6 @@ std = [ "bp-parachains/std", "bp-polkadot-core/std", "bp-runtime/std", - "bp-xcm-bridge-hub/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 72c938fab0..9e9c57f234 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -27,11 +27,19 @@ use bp_messages::{ LaneId, MessageNonce, }; use bp_runtime::messages::MessageDispatchResult; -use codec::{Decode, Encode}; -use frame_support::{dispatch::Weight, CloneNoBound, EqNoBound, PartialEqNoBound}; +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use frame_support::{ + dispatch::Weight, + traits::{ProcessMessage, ProcessMessageError, QueuePausedQuery}, + weights::WeightMeter, + CloneNoBound, EqNoBound, PartialEqNoBound, +}; use pallet_bridge_messages::WeightInfoExt as MessagesPalletWeights; use scale_info::TypeInfo; +use sp_io::hashing::blake2_256; use sp_runtime::SaturatedConversion; +use sp_std::{boxed::Box, fmt::Debug, marker::PhantomData, vec::Vec}; +use xcm::prelude::*; use xcm_builder::{DispatchBlob, DispatchBlobError, HaulBlob, HaulBlobError}; /// Plain "XCM" payload, which we transfer through bridge @@ -114,10 +122,9 @@ pub trait XcmBlobHauler { /// Runtime message sender origin, which is used by [`Self::MessageSender`]. type MessageSenderOrigin; - /// Our location within the Consensus Universe. + /// Runtime origin for our (i.e. this bridge hub) location within the Consensus Universe. fn message_sender_origin() -> Self::MessageSenderOrigin; - - /// Returns the relative XCM location of the message sending chain. + /// Location of the sending chain (i.e. sibling asset hub) within the Consensus universe. fn sending_chain_location() -> MultiLocation; /// Return message lane (as "point-to-point link") used to deliver XCM messages. fn xcm_lane() -> LaneId; @@ -132,37 +139,20 @@ impl> HaulBlo fn haul_blob(blob: sp_std::prelude::Vec) -> Result<(), HaulBlobError> { let lane = H::xcm_lane(); H::MessageSender::send_message(H::message_sender_origin(), lane, blob) - .map(|artifacts| (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256)) - .map(|result| { + .map(|artifacts| { log::info!( target: crate::LOG_TARGET_BRIDGE_DISPATCH, "haul_blob result - ok: {:?} on lane: {:?}", - msg_hash, + artifacts.nonce, lane ); - // suspend the inbound XCM channel with the sender to avoid enqueueing more messages - // at the outbound bridge queue AND turn on internal backpressure mechanism of the - // XCM queue - let is_overloaded = artifacts.enqueued_messages > L::get(); - if is_overloaded { - let sending_chain_location = H::sending_chain_location(); - let suspend_result = C::suspend_inbound_channel(sending_chain_location); - match suspend_result { - Ok(_) => log::info!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "Suspended inbound XCM channel with {:?} to avoid overloading lane {:?}", - sending_chain_location, - lane, - ), - Err(_) => log::info!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "Failed to suspend inbound XCM channel with {:?} to avoid overloading lane {:?}", - sending_chain_location, - lane, - ), - } - } + // notify XCM queue manager about new message + LocalXcmQueueManager::on_bridge_message_enqueued( + Box::new(H::sending_chain_location()), + lane, + artifacts.enqueued_messages, + ); }) .map_err(|error| { log::error!( @@ -176,79 +166,173 @@ impl> HaulBlo } } -#[cfg(test)] -mod tests { - use super::*; - use crate::mock::run_test; +/// Manager of local XCM queues (and indirectly - underlying transport channels) that +/// controls the queue state. +pub struct LocalXcmQueueManager; + +/// Prefix for storage keys, written by the `LocalXcmQueueManager`. +/// +/// We don't have a separate pallet with available storage entries for managing XCM queues +/// in this (internediate) version of dynamic fees implementation. So we write to the runtime +/// storage directly with this prefix. +const LOCAL_XCM_QUEUE_MANAGER_STORAGE_PREFIX: &[u8] = b"LocalXcmQueueManager"; + +/// Name of "virtual" storage map that holds entries for every suspended queue. +const SUSPENDED_QUEUE_MAP_STORAGE_PREFIX: &[u8] = b"SuspendedQueues"; + +/// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we +/// stop processing XCM messages from the sending chain (asset hub) that "owns" the lane. +const MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE: MessageNonce = 300; + +impl LocalXcmQueueManager { + /// Must be called whenever we push a message to the bridge lane. + pub fn on_bridge_message_enqueued( + sending_chain_location: Box, + lane: LaneId, + enqueued_messages: MessageNonce, + ) { + // suspend the inbound XCM queue with the sender to avoid queueing more messages + // at the outbound bridge queue AND turn on internal backpressure mechanism of the + // XCM queue + let is_overloaded = enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE; + if !is_overloaded { + return + } - use bp_messages::source_chain::SendMessageArtifacts; - use frame_support::traits::{ConstU64, Get}; + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Suspending inbound XCM queue with {:?} to avoid overloading lane {:?}: there are\ + {} messages queued at the bridge queue", + sending_chain_location, + lane, + enqueued_messages, + ); - struct TestMessageSender; + Self::suspend_inbound_queue(sending_chain_location); + } - impl MessagesBridge> for TestMessageSender { - type Error = (); + /// Must be called whenever we receive a message delivery confirmation. + pub fn on_bridge_messages_delivered( + sending_chain_location: Box, + lane: LaneId, + enqueued_messages: MessageNonce, + ) { + // the queue before this call may be either suspended or not. If the lane is still + // overloaded, we win't need to do anything + let is_overloaded = enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE; + if is_overloaded { + return + } - fn send_message( - _lane: LaneId, - message: Vec, - ) -> Result { - let nonce = message[0] as _; - Ok(SendMessageArtifacts { nonce, enqueued_messages: nonce }) + // else - resume the inbound queue + if !Self::is_inbound_queue_suspended(sending_chain_location.clone()) { + return } - } - struct TestBlobHauler; + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Resuming inbound XCM queue with {:?} using lane {:?}: there are\ + {} messages queued at the bridge queue", + sending_chain_location, + lane, + enqueued_messages, + ); - impl XcmBlobHauler for TestBlobHauler { - type MessageSender = TestMessageSender; + Self::resume_inbound_queue(sending_chain_location); + } - fn sending_chain_location() -> MultiLocation { - Here.into() - } + /// Returns true if XCM message queue with given location is currently suspended. + pub fn is_inbound_queue_suspended(with: Box) -> bool { + frame_support::storage::unhashed::get_or_default(&Self::suspended_queue_map_storage_key( + with, + )) + } - fn xcm_lane() -> LaneId { - LaneId::new(0, 0) - } + fn suspend_inbound_queue(with: Box) { + frame_support::storage::unhashed::put(&Self::suspended_queue_map_storage_key(with), &true); } - struct TestXcmChannelManager; + fn resume_inbound_queue(with: Box) { + frame_support::storage::unhashed::kill(&Self::suspended_queue_map_storage_key(with)); + } - impl TestXcmChannelManager { - fn is_suspended(owner: MultiLocation) -> bool { - frame_support::storage::unhashed::take(&owner.encode()) == Some(42) + fn suspended_queue_map_storage_key(with: Box) -> Vec { + let with: VersionedMultiLocation = (*with).into(); + let with_hashed = with.using_encoded(blake2_256); + + // let's emulate real map here - it'd be easier to kill all entries later + let mut final_key = Vec::with_capacity( + LOCAL_XCM_QUEUE_MANAGER_STORAGE_PREFIX.len() + + SUSPENDED_QUEUE_MAP_STORAGE_PREFIX.len() + + with_hashed.len(), + ); + + final_key.extend_from_slice(LOCAL_XCM_QUEUE_MANAGER_STORAGE_PREFIX); + final_key.extend_from_slice(SUSPENDED_QUEUE_MAP_STORAGE_PREFIX); + final_key.extend_from_slice(&with_hashed); + + final_key + } +} + +/// A structure that implements [`frame_support:traits::messages::ProcessMessage`] and may +/// be used in the `pallet-message-queue` configuration to stop processing messages when the +/// bridge queue is overloaded. +pub struct LocalXcmQueueMessageProcessor(PhantomData<(Origin, Inner)>); + +impl ProcessMessage for LocalXcmQueueMessageProcessor +where + Origin: Clone + + Into + + FullCodec + + MaxEncodedLen + + Clone + + Eq + + PartialEq + + TypeInfo + + Debug, + Inner: ProcessMessage, +{ + type Origin = Origin; + + fn process_message( + message: &[u8], + origin: Self::Origin, + meter: &mut WeightMeter, + id: &mut [u8; 32], + ) -> Result { + // if the queue is suspended, yield immediately + if LocalXcmQueueManager::is_inbound_queue_suspended(Box::new(origin.clone().into())) { + return Err(ProcessMessageError::Yield) } + + // else pass message to backed processor + Inner::process_message(message, origin, meter, id) } +} - impl LocalXcmChannelManager for TestXcmChannelManager { - fn suspend_inbound_channel(owner: MultiLocation) -> Result<(), ()> { - frame_support::storage::unhashed::put(&owner.encode(), &42); - Ok(()) +/// A structure that implements [`frame_support:traits::messages::QueuePausedQuery`] and may +/// be used in the `pallet-message-queue` configuration to stop processing messages when the +/// bridge queue is overloaded. +pub struct LocalXcmQueueSuspender(PhantomData<(Origin, Inner)>); + +impl QueuePausedQuery for LocalXcmQueueSuspender +where + Origin: Clone + Into, + Inner: QueuePausedQuery, +{ + fn is_paused(origin: &Origin) -> bool { + // give priority to inner status + if Inner::is_paused(origin) { + return true } - fn resume_inbound_channel(_owner: MultiLocation) -> Result<(), ()> { - unreachable!("") + // if we have suspended the queue before, do not even start processing its messages + if LocalXcmQueueManager::is_inbound_queue_suspended(Box::new(origin.clone().into())) { + return true } - } - type OverloadedLimit = ConstU64<8>; - type TestAdapter = XcmBlobHaulerAdapter; - - #[test] - fn blob_haulder_adapter_suspends_inbound_xcm_channel_when_bridge_is_overloaded() { - run_test(|| { - // first `OverloadedLimit` messages => no suspension - for nonce in 1..=OverloadedLimit::get() { - TestAdapter::haul_blob(vec![nonce as u8]).unwrap(); - assert!(!TestXcmChannelManager::is_suspended( - TestBlobHauler::sending_chain_location() - )); - } - - // next message => channel is suspended - let overloaded_nonce = >::get() + 1; - TestAdapter::haul_blob(vec![overloaded_nonce as u8]).unwrap(); - assert!(TestXcmChannelManager::is_suspended(TestBlobHauler::sending_chain_location())); - }); + // else process message + false } } diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index a4f66fb7ac..8fad3c6871 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -63,7 +63,9 @@ use bp_messages::{ MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, UnrewardedRelayersState, VerificationError, }; -use bp_runtime::{BasicOperatingMode, ChainId, OwnedBridgeModule, PreComputedSize, Size}; +use bp_runtime::{ + BasicOperatingMode, ChainId, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, Size, +}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound}; use sp_runtime::traits::UniqueSaturatedFrom; diff --git a/modules/messages/src/outbound_lane.rs b/modules/messages/src/outbound_lane.rs index 0cf0c4ccb4..1cf4aae6ef 100644 --- a/modules/messages/src/outbound_lane.rs +++ b/modules/messages/src/outbound_lane.rs @@ -29,7 +29,7 @@ use frame_support::{ }; use num_traits::Zero; use scale_info::TypeInfo; -use sp_std::collections::vec_deque::VecDeque; +use sp_std::{collections::vec_deque::VecDeque, ops::RangeInclusive}; /// Outbound lane storage. pub trait OutboundLaneStorage { @@ -87,6 +87,13 @@ impl OutboundLane { self.storage.data() } + /// Return nonces of all currently queued messages (i.e. messages that we believe + /// are not delivered yet). + pub fn queued_messages(&self) -> RangeInclusive { + let data = self.storage.data(); + (data.latest_received_nonce + 1)..=data.latest_generated_nonce + } + /// Send message over lane. /// /// Returns new message nonce. From 3c98c245ac48112d81ed31fed23dd6dd5e62c80a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 21 Jul 2023 11:41:21 +0300 Subject: [PATCH 03/31] OnMessageDelviered callback --- bin/millau/runtime/src/lib.rs | 2 ++ bin/rialto-parachain/runtime/src/lib.rs | 1 + bin/rialto/runtime/src/lib.rs | 1 + .../src/messages_xcm_extension.rs | 18 ++++++++++++++++-- bin/runtime-common/src/mock.rs | 1 + modules/messages/src/lib.rs | 11 ++++++++++- modules/messages/src/mock.rs | 1 + primitives/messages/src/source_chain.rs | 13 +++++++++++++ 8 files changed, 45 insertions(+), 3 deletions(-) diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index dfd6fc322e..c0fff1895b 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -464,6 +464,7 @@ impl pallet_bridge_messages::Config for Runtime { WithRialtoMessagesInstance, frame_support::traits::ConstU64<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = crate::rialto_messages::RialtoAsSourceHeaderChain; type MessageDispatch = crate::rialto_messages::FromRialtoMessageDispatch; @@ -495,6 +496,7 @@ impl pallet_bridge_messages::Config for Run WithRialtoParachainMessagesInstance, frame_support::traits::ConstU64<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = crate::rialto_parachain_messages::RialtoParachainAsSourceHeaderChain; type MessageDispatch = crate::rialto_parachain_messages::FromRialtoParachainMessageDispatch; diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs index 18c915819d..70810cc534 100644 --- a/bin/rialto-parachain/runtime/src/lib.rs +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -584,6 +584,7 @@ impl pallet_bridge_messages::Config for Runtime { WithMillauMessagesInstance, frame_support::traits::ConstU128<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = crate::millau_messages::MillauAsSourceHeaderChain; type MessageDispatch = crate::millau_messages::FromMillauMessageDispatch; diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs index 1d93d24951..e0cb6e9b30 100644 --- a/bin/rialto/runtime/src/lib.rs +++ b/bin/rialto/runtime/src/lib.rs @@ -442,6 +442,7 @@ impl pallet_bridge_messages::Config for Runtime { WithMillauMessagesInstance, frame_support::traits::ConstU128<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = crate::millau_messages::MillauAsSourceHeaderChain; type MessageDispatch = crate::millau_messages::FromMillauMessageDispatch; diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 9e9c57f234..a7cecc3282 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -22,7 +22,7 @@ //! `XcmRouter` <- `MessageDispatch` <- `InboundMessageQueue` use bp_messages::{ - source_chain::MessagesBridge, + source_chain::{MessagesBridge, OnMessagesDelivered}, target_chain::{DispatchMessage, MessageDispatch}, LaneId, MessageNonce, }; @@ -133,6 +133,7 @@ pub trait XcmBlobHauler { /// XCM bridge adapter which connects [`XcmBlobHauler`] with [`XcmBlobHauler::MessageSender`] and /// makes sure that XCM blob is sent to the [`pallet_bridge_messages`] queue to be relayed. pub struct XcmBlobHaulerAdapter(sp_std::marker::PhantomData); + impl> HaulBlob for XcmBlobHaulerAdapter { @@ -147,7 +148,7 @@ impl> HaulBlo lane ); - // notify XCM queue manager about new message + // notify XCM queue manager about updated lane state LocalXcmQueueManager::on_bridge_message_enqueued( Box::new(H::sending_chain_location()), lane, @@ -166,6 +167,19 @@ impl> HaulBlo } } +impl> OnMessagesDelivered + for XcmBlobHaulerAdapter +{ + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { + // notify XCM queue manager about updated lane state + LocalXcmQueueManager::on_bridge_messages_delivered( + Box::new(H::sending_chain_location()), + lane, + enqueued_messages, + ); + } +} + /// Manager of local XCM queues (and indirectly - underlying transport channels) that /// controls the queue state. pub struct LocalXcmQueueManager; diff --git a/bin/runtime-common/src/mock.rs b/bin/runtime-common/src/mock.rs index 6b5edabc88..58fbb7aaff 100644 --- a/bin/runtime-common/src/mock.rs +++ b/bin/runtime-common/src/mock.rs @@ -245,6 +245,7 @@ impl pallet_bridge_messages::Config for TestRuntime { (), ConstU64<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = SourceHeaderChainAdapter; type MessageDispatch = ForbidInboundMessages<(), FromBridgedChainMessagePayload>; diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 8fad3c6871..4fea9e52ac 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -53,7 +53,8 @@ use crate::{ use bp_messages::{ source_chain::{ - DeliveryConfirmationPayments, LaneMessageVerifier, SendMessageArtifacts, TargetHeaderChain, + DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, + SendMessageArtifacts, TargetHeaderChain, }, target_chain::{ DeliveryPayments, DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, @@ -158,6 +159,8 @@ pub mod pallet { type LaneMessageVerifier: LaneMessageVerifier; /// Delivery confirmation payments. type DeliveryConfirmationPayments: DeliveryConfirmationPayments; + /// Delivery confirmation callback. + type OnMessagesDelivered: OnMessagesDelivered; // Types that are used by inbound_lane (on target chain). @@ -489,6 +492,12 @@ pub mod pallet { lane_id, ); + // notify others about messages delivery + T::OnMessagesDelivered::on_messages_delivered( + lane_id, + lane.queued_messages().checked_len().unwrap_or(0), + ); + // because of lags, the inbound lane state (`lane_data`) may have entries for // already rewarded relayers and messages (if all entries are duplicated, then // this transaction must be filtered out by our signed extension) diff --git a/modules/messages/src/mock.rs b/modules/messages/src/mock.rs index 62bc76c5e0..6c5da6aaf3 100644 --- a/modules/messages/src/mock.rs +++ b/modules/messages/src/mock.rs @@ -161,6 +161,7 @@ impl Config for TestRuntime { type TargetHeaderChain = TestTargetHeaderChain; type LaneMessageVerifier = TestLaneMessageVerifier; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; + type OnMessagesDelivered = (); type SourceHeaderChain = TestSourceHeaderChain; type MessageDispatch = TestMessageDispatch; diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs index cf2ac3a3df..fa067b611d 100644 --- a/primitives/messages/src/source_chain.rs +++ b/primitives/messages/src/source_chain.rs @@ -116,6 +116,19 @@ impl DeliveryConfirmationPayments for () { } } +/// Callback that is called at the source chain (bridge hub) when we get delivery confrimation +/// for new messages. +pub trait OnMessagesDelivered { + /// New messages delivery has been confimed. + /// + /// The only argument of the function is the number of yet undelivered messages + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce); +} + +impl OnMessagesDelivered for () { + fn on_messages_delivered(_lane: LaneId, _enqueued_messages: MessageNonce) {} +} + /// Send message artifacts. #[derive(Eq, RuntimeDebug, PartialEq)] pub struct SendMessageArtifacts { From 1fdac85a14a9c429ac46b737a5953a237916f23a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 21 Jul 2023 12:19:45 +0300 Subject: [PATCH 04/31] forbid mesage delivery transactions when the channel between target bridge hub and target asset hub is suspended --- bin/millau/runtime/src/rialto_messages.rs | 3 ++- .../runtime/src/rialto_parachain_messages.rs | 3 ++- .../runtime/src/millau_messages.rs | 3 ++- bin/rialto/runtime/src/millau_messages.rs | 3 ++- bin/runtime-common/src/messages_call_ext.rs | 16 +++++++++--- .../src/messages_xcm_extension.rs | 26 +++++++++++++++---- modules/messages/src/lib.rs | 5 ++++ modules/messages/src/mock.rs | 4 +++ primitives/messages/src/target_chain.rs | 10 +++++++ 9 files changed, 61 insertions(+), 12 deletions(-) diff --git a/bin/millau/runtime/src/rialto_messages.rs b/bin/millau/runtime/src/rialto_messages.rs index 2ee0685734..d5ada43a4a 100644 --- a/bin/millau/runtime/src/rialto_messages.rs +++ b/bin/millau/runtime/src/rialto_messages.rs @@ -25,7 +25,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, }; -use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; +use frame_support::{parameter_types, traits::ConstBool, weights::Weight, RuntimeDebug}; use pallet_bridge_relayers::WeightInfoExt as _; use xcm::latest::prelude::*; use xcm_builder::HaulBlobExporter; @@ -67,6 +67,7 @@ pub type FromRialtoMessageDispatch = bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch< crate::xcm_config::OnMillauBlobDispatcher, (), + ConstBool, >; /// Maximal outbound payload size of Millau -> Rialto messages. diff --git a/bin/millau/runtime/src/rialto_parachain_messages.rs b/bin/millau/runtime/src/rialto_parachain_messages.rs index 10e6cf26dd..12a956a4be 100644 --- a/bin/millau/runtime/src/rialto_parachain_messages.rs +++ b/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -27,7 +27,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, }; -use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; +use frame_support::{parameter_types, traits::ConstBool, weights::Weight, RuntimeDebug}; use pallet_bridge_relayers::WeightInfoExt as _; use xcm::latest::prelude::*; use xcm_builder::HaulBlobExporter; @@ -62,6 +62,7 @@ pub type FromRialtoParachainMessageDispatch = bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch< crate::xcm_config::OnMillauBlobDispatcher, (), + ConstBool, >; /// Maximal outbound payload size of Millau -> RialtoParachain messages. diff --git a/bin/rialto-parachain/runtime/src/millau_messages.rs b/bin/rialto-parachain/runtime/src/millau_messages.rs index 8819870747..3da9affcc4 100644 --- a/bin/rialto-parachain/runtime/src/millau_messages.rs +++ b/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -28,7 +28,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, }; -use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; +use frame_support::{parameter_types, traits::ConstBool, weights::Weight, RuntimeDebug}; use xcm::latest::prelude::*; use xcm_builder::HaulBlobExporter; @@ -62,6 +62,7 @@ pub type FromMillauMessageDispatch = bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch< crate::OnRialtoParachainBlobDispatcher, (), + ConstBool, >; /// Messages proof for Millau -> RialtoParachain messages. diff --git a/bin/rialto/runtime/src/millau_messages.rs b/bin/rialto/runtime/src/millau_messages.rs index c776c6ae22..df639b4c75 100644 --- a/bin/rialto/runtime/src/millau_messages.rs +++ b/bin/rialto/runtime/src/millau_messages.rs @@ -25,7 +25,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, }; -use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; +use frame_support::{parameter_types, traits::ConstBool, weights::Weight, RuntimeDebug}; use xcm::latest::prelude::*; use xcm_builder::HaulBlobExporter; @@ -59,6 +59,7 @@ pub type FromMillauMessageDispatch = bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch< crate::xcm_config::OnRialtoBlobDispatcher, (), + ConstBool, >; /// Messages proof for Millau -> Rialto messages. diff --git a/bin/runtime-common/src/messages_call_ext.rs b/bin/runtime-common/src/messages_call_ext.rs index 8b4a50a0f3..11d8174b5c 100644 --- a/bin/runtime-common/src/messages_call_ext.rs +++ b/bin/runtime-common/src/messages_call_ext.rs @@ -17,7 +17,7 @@ use crate::messages::{ source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, }; -use bp_messages::{InboundLaneData, LaneId, MessageNonce}; +use bp_messages::{target_chain::MessageDispatch, InboundLaneData, LaneId, MessageNonce}; use frame_support::{ dispatch::CallableCallFor, traits::{Get, IsSubType}, @@ -77,7 +77,15 @@ impl ReceiveMessagesProofInfo { /// /// - or there are no bundled messages, but the inbound lane is blocked by too many unconfirmed /// messages and/or unrewarded relayers. - fn is_obsolete(&self) -> bool { + fn is_obsolete(&self, is_dispatcher_active: bool) -> bool { + // TODO: maybe rename method to `is_accepted`, because it isn't about **obsolete** messages + // anymore + + // if dispatcher is inactive, we don't accept any delivery transactions + if !is_dispatcher_active { + return true + } + // transactions with zero bundled nonces are not allowed, unless they're message // delivery transactions, which brings reward confirmations required to unblock // the lane @@ -275,7 +283,9 @@ impl< fn check_obsolete_call(&self) -> TransactionValidity { match self.call_info() { - Some(CallInfo::ReceiveMessagesProof(proof_info)) if proof_info.is_obsolete() => { + Some(CallInfo::ReceiveMessagesProof(proof_info)) + if proof_info.is_obsolete(T::MessageDispatch::is_active()) => + { log::trace!( target: pallet_bridge_messages::LOG_TARGET, "Rejecting obsolete messages delivery transaction: {:?}", diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index a7cecc3282..2f94902dc7 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -30,7 +30,7 @@ use bp_runtime::messages::MessageDispatchResult; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ dispatch::Weight, - traits::{ProcessMessage, ProcessMessageError, QueuePausedQuery}, + traits::{Get, ProcessMessage, ProcessMessageError, QueuePausedQuery}, weights::WeightMeter, CloneNoBound, EqNoBound, PartialEqNoBound, }; @@ -54,16 +54,32 @@ pub enum XcmBlobMessageDispatchResult { } /// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages -pub struct XcmBlobMessageDispatch { - _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights)>, +pub struct XcmBlobMessageDispatch { + _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, IsChannelActive)>, } -impl MessageDispatch - for XcmBlobMessageDispatch +impl> + MessageDispatch for XcmBlobMessageDispatch { type DispatchPayload = XcmAsPlainPayload; type DispatchLevelResult = XcmBlobMessageDispatchResult; + fn is_active() -> bool { + // TODO: we can only implement `IsChannelActive` in Cumulus. Assuming that all messages + // will only be sent to taget asset hub: + // + // ```rust + // pub struct IsChannelWithAssetHubActive; + // + // impl Get for IsChannelWithAssetHubActive { + // fn get() -> bool { + // !cumulus_pallet_xcmp_queue::InboundXcmpSuspended::get().contains(&SIBLNG_ASSET_HUB_PARA_ID) + // } + // } + // ``` + IsChannelActive::get() + } + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { match message.data.payload { Ok(ref payload) => { diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 4fea9e52ac..1ab619bcc1 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -286,6 +286,9 @@ pub mod pallet { Error::::TooManyMessagesInTheProof ); + // if message dispatcher is currently inactive, we won't accept any messages + ensure!(T::MessageDispatch::is_active(), Error::::MessageDispatchInactive); + // why do we need to know the weight of this (`receive_messages_proof`) call? Because // we may want to return some funds for not-dispatching (or partially dispatching) some // messages to the call origin (relayer). And this is done by returning actual weight @@ -529,6 +532,8 @@ pub mod pallet { NotOperatingNormally, /// The outbound lane is inactive. InactiveOutboundLane, + /// The inbound message dispatcher is inactive. + MessageDispatchInactive, /// Message has been treated as invalid by chain verifier. MessageRejectedByChainVerifier(VerificationError), /// Message has been treated as invalid by lane verifier. diff --git a/modules/messages/src/mock.rs b/modules/messages/src/mock.rs index 6c5da6aaf3..9db8cec02a 100644 --- a/modules/messages/src/mock.rs +++ b/modules/messages/src/mock.rs @@ -412,6 +412,10 @@ impl MessageDispatch for TestMessageDispatch { type DispatchPayload = TestPayload; type DispatchLevelResult = TestDispatchLevelResult; + fn is_active() -> bool { + true + } + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { match message.data.payload.as_ref() { Ok(payload) => payload.declared_weight, diff --git a/primitives/messages/src/target_chain.rs b/primitives/messages/src/target_chain.rs index 385bc4ac37..d5bdc3cf54 100644 --- a/primitives/messages/src/target_chain.rs +++ b/primitives/messages/src/target_chain.rs @@ -91,6 +91,12 @@ pub trait MessageDispatch { /// Fine-grained result of single message dispatch (for better diagnostic purposes) type DispatchLevelResult: Clone + sp_std::fmt::Debug + Eq; + /// Returns `true` if dispatcher is ready to accept additional messages. The `false` should + /// be treated as a hint by both dispatcher and its consumers - i.e. dispatcher shall not + /// simply drop messages if it returns `false`. The consumer may still call the `dispatch` + /// if dispatcher has returned `false`. + fn is_active() -> bool; + /// Estimate dispatch weight. /// /// This function must return correct upper bound of dispatch weight. The return value @@ -186,6 +192,10 @@ impl MessageDispatch type DispatchPayload = DispatchPayload; type DispatchLevelResult = (); + fn is_active() -> bool { + false + } + fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { Weight::MAX } From eea610a875564ab57a2c846c01f9b837afa8950e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 24 Jul 2023 11:01:40 +0300 Subject: [PATCH 05/31] pallet-xcm-bridge-hub-router --- Cargo.lock | 19 ++ Cargo.toml | 2 + .../src/messages_xcm_extension.rs | 158 ++++++++++++++ modules/xcm-bridge-hub-router/Cargo.toml | 45 ++++ modules/xcm-bridge-hub-router/src/lib.rs | 192 ++++++++++++++++++ primitives/xcm-bridge-hub-router/Cargo.toml | 11 + primitives/xcm-bridge-hub-router/src/lib.rs | 25 +++ 7 files changed, 452 insertions(+) create mode 100644 modules/xcm-bridge-hub-router/Cargo.toml create mode 100644 modules/xcm-bridge-hub-router/src/lib.rs create mode 100644 primitives/xcm-bridge-hub-router/Cargo.toml create mode 100644 primitives/xcm-bridge-hub-router/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 128f6b4f74..114b2cf1bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,10 @@ dependencies = [ "sp-api", ] +[[package]] +name = "bp-xcm-bridge-hub-router" +version = "0.1.0" + [[package]] name = "bridge-runtime-common" version = "0.1.0" @@ -7687,6 +7691,21 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "pallet-xcm-bridge-hub-router" +version = "0.1.0" +dependencies = [ + "bp-xcm-bridge-hub-router", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std 8.0.0", + "xcm", + "xcm-builder", +] + [[package]] name = "parachain-info" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 8048b9fcdc..ec92cddb07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "modules/parachains", "modules/relayers", "modules/shift-session-manager", + "modules/xcm-bridge-hub-router", "primitives/beefy", "primitives/chain-bridge-hub-cumulus", "primitives/chain-bridge-hub-kusama", @@ -37,6 +38,7 @@ members = [ "primitives/relayers", "primitives/runtime", "primitives/test-utils", + "primitives/xcm-bridge-hub-router", "relays/bin-substrate", "relays/client-bridge-hub-kusama", "relays/client-bridge-hub-polkadot", diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 2f94902dc7..f265b61179 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -54,6 +54,8 @@ pub enum XcmBlobMessageDispatchResult { } /// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages +/// +/// It needs to be used at the target bridge hub. pub struct XcmBlobMessageDispatch { _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, IsChannelActive)>, } @@ -148,6 +150,8 @@ pub trait XcmBlobHauler { /// XCM bridge adapter which connects [`XcmBlobHauler`] with [`XcmBlobHauler::MessageSender`] and /// makes sure that XCM blob is sent to the [`pallet_bridge_messages`] queue to be relayed. +/// +/// It needs to be used at the source bridge hub. pub struct XcmBlobHaulerAdapter(sp_std::marker::PhantomData); impl> HaulBlob @@ -198,6 +202,8 @@ impl> OnMessa /// Manager of local XCM queues (and indirectly - underlying transport channels) that /// controls the queue state. +/// +/// It needs to be used at the source bridge hub. pub struct LocalXcmQueueManager; /// Prefix for storage keys, written by the `LocalXcmQueueManager`. @@ -212,6 +218,7 @@ const SUSPENDED_QUEUE_MAP_STORAGE_PREFIX: &[u8] = b"SuspendedQueues"; /// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we /// stop processing XCM messages from the sending chain (asset hub) that "owns" the lane. +// TODO: should be some factor of `MaxUnconfirmedMessagesAtInboundLane` at bridged side? const MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE: MessageNonce = 300; impl LocalXcmQueueManager { @@ -308,6 +315,8 @@ impl LocalXcmQueueManager { /// A structure that implements [`frame_support:traits::messages::ProcessMessage`] and may /// be used in the `pallet-message-queue` configuration to stop processing messages when the /// bridge queue is overloaded. +/// +/// It needs to be used at the source bridge hub. pub struct LocalXcmQueueMessageProcessor(PhantomData<(Origin, Inner)>); impl ProcessMessage for LocalXcmQueueMessageProcessor @@ -344,6 +353,8 @@ where /// A structure that implements [`frame_support:traits::messages::QueuePausedQuery`] and may /// be used in the `pallet-message-queue` configuration to stop processing messages when the /// bridge queue is overloaded. +/// +/// It needs to be used at the source bridge hub. pub struct LocalXcmQueueSuspender(PhantomData<(Origin, Inner)>); impl QueuePausedQuery for LocalXcmQueueSuspender @@ -366,3 +377,150 @@ where false } } +/* +/// Local XCM queue +pub trait LocalXcmQueue { + /// Returns true if the queue is currently overloaded. + fn is_overloaded() -> bool; +} + +/// Configuration of the dynamic bridge fee mechanism at the sending chain (source asset hub). +pub trait DynamicBridgeFeeConfig { + /// Universal location of this runtime. + type UniversalLocation: Get; + /// Relative location of the sibling bridge hub. + type SiblingBridgeHubLocation: Get; + /// The bridged network that this config is for. + type BridgedNetworkId: Get; + + /// Actual message sender to the sibling bridge hub location. + type ToBridgeHubSender: SendXcm + LocalXcmQueue; + + /// Asset that is used to paid bridge fee. + type FeeAsset: Get; + + /// Base bridge fee that is paid for every outbound message. + type BaseFee: Get; + /// Additional fee that is paid for every byte of the outbound message. + type ByteFee: Get; + + /// How much every kb of the message sent affects the fee factor. + type FeeFactorIncreasePerKb: Get; +} + + +/// We'll be using `SovereignPaidRemoteExporter` internally to inject fee into messages +/// sent over sibling bridge hub. +type ViaBridgeHubExporter = SovereignPaidRemoteExporter< + ToSiblingBridgeHubRouter, + ::ToBridgeHubSender, + ::UniversalLocation, +>; + +/// This structure must be used at the source asset hub for sending messages to the sibling +/// bridge hub instead of XCMP pallet (which is `Config::ToBridgeHubSender` supposed to be). This +/// allows us to inject dynamic fees into XCM messages going to the bridged network. +/// +/// It works exactly as `SovereignPaidRemoteExporter` (actually it is used inside). So the sender +/// pays the fee here and instructions to pay for the execution at the sibling bridge hub. In +/// addition, it maintains the dynamic fee factor, which is increased and decreased when required. +/// +/// It needs to be used at the source asset hub. +pub struct ToSiblingBridgeHubRouter(PhantomData); + +impl ToSiblingBridgeHubRouter where + Config: DynamicBridgeFeeConfig, +{ + /// Called when new bridge message to the sibling bridge hub is enqueued. + fn on_bridge_message_sent(message_size: u32) { + // if outbound queue is not overloaded, do nothing + if !Config::ToBridgeHubSender::is_overloaded() { + return; + } + + // ok - we need to increase the fee factor, let's do that + let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) + .saturating_mul(Config::FeeFactorIncreasePerKb::get()); + let total_factor = Config::FeeFactorExponentialBase::get().saturating_add(message_size_factor); + let fee_factor = Self::fee_factor().saturating_mul(total_factor); + Self::set_fee_factor(fee_factor); + } + + /// Called from the block `on_initialize`. + fn on_bridge_queue_maybe_pruned() -> Weight { + // TODO: we don't have our pallets at asset hub (now), who will call it. Maybe + unimplemented!("TODO") + } + + /// Return current fee factor used in fee computations. + fn fee_factor() -> FixedU128 { + unimplemented!("TODO") + } + + /// Set fee factor value. + fn set_fee_factor(new_factor: FixedU128) { + unimplemented!("TODO") + } +} + +impl ExporterFor for ToSiblingBridgeHubRouter where + Config: DynamicBridgeFeeConfig, +{ + fn exporter_for( + network: &NetworkId, + _remote_location: &InteriorMultiLocation, + message: &Xcm<()>, + ) -> Option<(MultiLocation, Option)> { + // ensure that the message is sent to the expected bridged network + if *network != Config::BridgedNetworkId::get() { + return None + } + + // compute fee amount + let mesage_size = message.encoded_size(); + let message_fee = (mesage_size as u128).saturating_mul(Config::ByteFee::get()); + let fee_sum = Config::BaseFee::get().saturating_add(message_fee); + let fee = Self::fee_factor().saturating_mul_int(fee_sum); + + Some((Config::SiblingBridgeHubLocation::get(), Some((Config::FeeAsset::get(), fee).into()))) + } +} + +impl SendXcm for ToSiblingBridgeHubRouter where + Config: DynamicBridgeFeeConfig, +{ + type Ticket = (u32, ::Ticket); + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + // we won't have an access to `dest` and `xcm` in the `delvier` method, so precompute + // everything required here + let message_size = xcm.as_ref().map(|xcm| xcm.encoded_size() as _).ok_or(SendError::MissingArgument)?; + + // TODO: we can reject large messages here (now they're just dropped at the bridge hub)!!! + + // just use exporter to validate destination and insert instructions to pay message fee + // at the sibling/child bridge hub + // + // the cost will include both cost of: (1) to-sibling bridg hub delivery (returned by + // the `Config::ToBridgeHubSender`) and (2) to-bridged bridge hub delivery (returned by + // `Self::exporter_for`) + ViaBridgeHubExporter::::validate(dest, xcm) + .map(|(ticket, cost)| ((message_size, ticket), cost)) + } + + fn deliver(ticket: Self::Ticket) -> Result { + // use router to enqueue message to the sibling/child bridge hub. This also should handle + // payment for passing through this queue. + let (message_size, ticket) = ticket; + let xcm_hash = Config::ToBridgeHubSender::deliver(ticket)?; + + // TODO: increase fee factor if: (1) outbound channel is suspended AND/OR if (2) outbound + // channel has many queued messages + + Ok(xcm_hash) + } +} +*/ diff --git a/modules/xcm-bridge-hub-router/Cargo.toml b/modules/xcm-bridge-hub-router/Cargo.toml new file mode 100644 index 0000000000..2500b045c8 --- /dev/null +++ b/modules/xcm-bridge-hub-router/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "pallet-xcm-bridge-hub-router" +description = "Bridge hub interface for sibling/parent chains with dynamic fees support." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +scale-info = { version = "2.8.0", default-features = false, features = ["bit-vec", "derive", "serde"] } + +# Bridge dependencies + +bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Polkadot Dependencies + +xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-xcm-bridge-hub-router/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "xcm/std", + "xcm-builder/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs new file mode 100644 index 0000000000..6d787dd133 --- /dev/null +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -0,0 +1,192 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Pallet that may be used instead of `SovereignPaidRemoteExporter` in the XCM router +//! configuration. The main thing that the pallet 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 overloaded. +//! +//! All other bridge hub queues offer some backpressure mechanisms. So if at least one +//! of all queues is overloaded, it will eventually lead to the growth of the queue at +//! this chain. + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_xcm_bridge_hub_router::LocalXcmQueue; +use codec::Encode; +use frame_support::traits::Get; +use sp_runtime::{traits::One, FixedPointNumber, FixedU128, Saturating}; +use xcm::prelude::*; +use xcm_builder::{ExporterFor, SovereignPaidRemoteExporter}; + +pub use pallet::*; + +/// The factor that is used to increase current message fee factor when bridge experiencing +/// some lags. +const EXPONENTIAL_FEE_BASE: FixedU128 = FixedU128::from_rational(105, 100); // 1.05 +/// The factor that is used to increase current message fee factor for every sent kilobyte. +const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0.001 + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Universal location of this runtime. + type UniversalLocation: Get; + /// Relative location of the sibling bridge hub. + type SiblingBridgeHubLocation: Get; + /// The bridged network that this config is for. + type BridgedNetworkId: Get; + + /// Actual message sender (XCMP/DMP) to the sibling bridge hub location. + type ToBridgeHubSender: SendXcm + LocalXcmQueue; + + /// Base bridge fee that is paid for every outbound message. + type BaseFee: Get; + /// Additional fee that is paid for every byte of the outbound message. + type ByteFee: Get; + /// Asset that is used to paid bridge fee. + type FeeAsset: Get; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + // if XCM queue is still overloaded, we don't change anything + if T::ToBridgeHubSender::is_overloaded() { + return Weight::zero() // TODO: benchmarks + } + + DeliveryFeeFactor::::mutate(|f| { + *f = InitialFactor::get().max(*f / EXPONENTIAL_FEE_BASE); + *f + }); + + Weight::zero() // TODO: benchmarks + } + } + + /// Initialization value for the delivery fee factor. + #[pallet::type_value] + pub fn InitialFactor() -> FixedU128 { + FixedU128::one() + } + + /// The number to multiply the base delivery fee by. + #[pallet::storage] + #[pallet::getter(fn delivery_fee_factor)] + pub type DeliveryFeeFactor, I: 'static = ()> = + StorageValue<_, FixedU128, ValueQuery, InitialFactor>; + + impl, I: 'static> Pallet { + /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. + pub(crate) fn on_message_sent_to_bridge(message_size: u32) { + // if outbound queue is not overloaded, do nothing + if !T::ToBridgeHubSender::is_overloaded() { + return + } + + // ok - we need to increase the fee factor, let's do that + let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) + .saturating_mul(MESSAGE_SIZE_FEE_BASE); + let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); + DeliveryFeeFactor::::mutate(|f| { + *f = f.saturating_mul(total_factor); + *f + }); + } + } +} + +/// We'll be using `SovereignPaidRemoteExporter` to send remote messages over the sibling/child +/// bridge hub. +type ViaBridgeHubExporter = SovereignPaidRemoteExporter< + Pallet, + >::ToBridgeHubSender, + >::UniversalLocation, +>; + +// This pallet acts as the `ExporterFor` for the `SovereignPaidRemoteExporter` to compute +// message fee using fee factor. +impl, I: 'static> ExporterFor for Pallet { + fn exporter_for( + network: &NetworkId, + _remote_location: &InteriorMultiLocation, + message: &Xcm<()>, + ) -> Option<(MultiLocation, Option)> { + // ensure that the message is sent to the expected bridged network + if *network != T::BridgedNetworkId::get() { + return None + } + + // compute fee amount + let mesage_size = message.encoded_size(); + let message_fee = (mesage_size as u128).saturating_mul(T::ByteFee::get()); + let fee_sum = T::BaseFee::get().saturating_add(message_fee); + let fee = Self::delivery_fee_factor().saturating_mul_int(fee_sum); + + Some((T::SiblingBridgeHubLocation::get(), Some((T::FeeAsset::get(), fee).into()))) + } +} + +// This pallet acts as the `SendXcm` to the sibling/child bridge hub instead of regular +// XCMP/DMP transport. This allows injecting dynamic message fees into XCM programs that +// are going to the bridged network. +impl, I: 'static> SendXcm for Pallet { + type Ticket = (u32, ::Ticket); + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + // we won't have an access to `dest` and `xcm` in the `delvier` method, so precompute + // everything required here + let message_size = xcm + .as_ref() + .map(|xcm| xcm.encoded_size() as _) + .ok_or(SendError::MissingArgument)?; + + // TODO: we can reject large messages here (now they're just dropped at the bridge hub)!!! + + // just use exporter to validate destination and insert instructions to pay message fee + // at the sibling/child bridge hub + // + // the cost will include both cost of: (1) to-sibling bridg hub delivery (returned by + // the `Config::ToBridgeHubSender`) and (2) to-bridged bridge hub delivery (returned by + // `Self::exporter_for`) + ViaBridgeHubExporter::::validate(dest, xcm) + .map(|(ticket, cost)| ((message_size, ticket), cost)) + } + + fn deliver(ticket: Self::Ticket) -> Result { + // use router to enqueue message to the sibling/child bridge hub. This also should handle + // payment for passing through this queue. + let (message_size, ticket) = ticket; + let xcm_hash = T::ToBridgeHubSender::deliver(ticket)?; + + // increase delivery fee factor if required + Self::on_message_sent_to_bridge(message_size); + + Ok(xcm_hash) + } +} diff --git a/primitives/xcm-bridge-hub-router/Cargo.toml b/primitives/xcm-bridge-hub-router/Cargo.toml new file mode 100644 index 0000000000..95b232fffd --- /dev/null +++ b/primitives/xcm-bridge-hub-router/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bp-xcm-bridge-hub-router" +description = "Primitives of the xcm-bridge-hub fee pallet." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[features] +default = ["std"] +std = [] diff --git a/primitives/xcm-bridge-hub-router/src/lib.rs b/primitives/xcm-bridge-hub-router/src/lib.rs new file mode 100644 index 0000000000..78d3290fdd --- /dev/null +++ b/primitives/xcm-bridge-hub-router/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Primitives of the `xcm-bridge-hub-router` pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +/// Local XCM queue. +pub trait LocalXcmQueue { + /// Returns true if the queue is currently overloaded. + fn is_overloaded() -> bool; +} From c24301374a60b50eb85991c90e9d548a2319aecb Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 24 Jul 2023 11:02:56 +0300 Subject: [PATCH 06/31] removed commented code --- .../src/messages_xcm_extension.rs | 147 ------------------ 1 file changed, 147 deletions(-) diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index f265b61179..fb2b8c902d 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -377,150 +377,3 @@ where false } } -/* -/// Local XCM queue -pub trait LocalXcmQueue { - /// Returns true if the queue is currently overloaded. - fn is_overloaded() -> bool; -} - -/// Configuration of the dynamic bridge fee mechanism at the sending chain (source asset hub). -pub trait DynamicBridgeFeeConfig { - /// Universal location of this runtime. - type UniversalLocation: Get; - /// Relative location of the sibling bridge hub. - type SiblingBridgeHubLocation: Get; - /// The bridged network that this config is for. - type BridgedNetworkId: Get; - - /// Actual message sender to the sibling bridge hub location. - type ToBridgeHubSender: SendXcm + LocalXcmQueue; - - /// Asset that is used to paid bridge fee. - type FeeAsset: Get; - - /// Base bridge fee that is paid for every outbound message. - type BaseFee: Get; - /// Additional fee that is paid for every byte of the outbound message. - type ByteFee: Get; - - /// How much every kb of the message sent affects the fee factor. - type FeeFactorIncreasePerKb: Get; -} - - -/// We'll be using `SovereignPaidRemoteExporter` internally to inject fee into messages -/// sent over sibling bridge hub. -type ViaBridgeHubExporter = SovereignPaidRemoteExporter< - ToSiblingBridgeHubRouter, - ::ToBridgeHubSender, - ::UniversalLocation, ->; - -/// This structure must be used at the source asset hub for sending messages to the sibling -/// bridge hub instead of XCMP pallet (which is `Config::ToBridgeHubSender` supposed to be). This -/// allows us to inject dynamic fees into XCM messages going to the bridged network. -/// -/// It works exactly as `SovereignPaidRemoteExporter` (actually it is used inside). So the sender -/// pays the fee here and instructions to pay for the execution at the sibling bridge hub. In -/// addition, it maintains the dynamic fee factor, which is increased and decreased when required. -/// -/// It needs to be used at the source asset hub. -pub struct ToSiblingBridgeHubRouter(PhantomData); - -impl ToSiblingBridgeHubRouter where - Config: DynamicBridgeFeeConfig, -{ - /// Called when new bridge message to the sibling bridge hub is enqueued. - fn on_bridge_message_sent(message_size: u32) { - // if outbound queue is not overloaded, do nothing - if !Config::ToBridgeHubSender::is_overloaded() { - return; - } - - // ok - we need to increase the fee factor, let's do that - let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) - .saturating_mul(Config::FeeFactorIncreasePerKb::get()); - let total_factor = Config::FeeFactorExponentialBase::get().saturating_add(message_size_factor); - let fee_factor = Self::fee_factor().saturating_mul(total_factor); - Self::set_fee_factor(fee_factor); - } - - /// Called from the block `on_initialize`. - fn on_bridge_queue_maybe_pruned() -> Weight { - // TODO: we don't have our pallets at asset hub (now), who will call it. Maybe - unimplemented!("TODO") - } - - /// Return current fee factor used in fee computations. - fn fee_factor() -> FixedU128 { - unimplemented!("TODO") - } - - /// Set fee factor value. - fn set_fee_factor(new_factor: FixedU128) { - unimplemented!("TODO") - } -} - -impl ExporterFor for ToSiblingBridgeHubRouter where - Config: DynamicBridgeFeeConfig, -{ - fn exporter_for( - network: &NetworkId, - _remote_location: &InteriorMultiLocation, - message: &Xcm<()>, - ) -> Option<(MultiLocation, Option)> { - // ensure that the message is sent to the expected bridged network - if *network != Config::BridgedNetworkId::get() { - return None - } - - // compute fee amount - let mesage_size = message.encoded_size(); - let message_fee = (mesage_size as u128).saturating_mul(Config::ByteFee::get()); - let fee_sum = Config::BaseFee::get().saturating_add(message_fee); - let fee = Self::fee_factor().saturating_mul_int(fee_sum); - - Some((Config::SiblingBridgeHubLocation::get(), Some((Config::FeeAsset::get(), fee).into()))) - } -} - -impl SendXcm for ToSiblingBridgeHubRouter where - Config: DynamicBridgeFeeConfig, -{ - type Ticket = (u32, ::Ticket); - - fn validate( - dest: &mut Option, - xcm: &mut Option>, - ) -> SendResult { - // we won't have an access to `dest` and `xcm` in the `delvier` method, so precompute - // everything required here - let message_size = xcm.as_ref().map(|xcm| xcm.encoded_size() as _).ok_or(SendError::MissingArgument)?; - - // TODO: we can reject large messages here (now they're just dropped at the bridge hub)!!! - - // just use exporter to validate destination and insert instructions to pay message fee - // at the sibling/child bridge hub - // - // the cost will include both cost of: (1) to-sibling bridg hub delivery (returned by - // the `Config::ToBridgeHubSender`) and (2) to-bridged bridge hub delivery (returned by - // `Self::exporter_for`) - ViaBridgeHubExporter::::validate(dest, xcm) - .map(|(ticket, cost)| ((message_size, ticket), cost)) - } - - fn deliver(ticket: Self::Ticket) -> Result { - // use router to enqueue message to the sibling/child bridge hub. This also should handle - // payment for passing through this queue. - let (message_size, ticket) = ticket; - let xcm_hash = Config::ToBridgeHubSender::deliver(ticket)?; - - // TODO: increase fee factor if: (1) outbound channel is suspended AND/OR if (2) outbound - // channel has many queued messages - - Ok(xcm_hash) - } -} -*/ From d9a0c2e468baae828638080bbd67e917ec2feac5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 27 Jul 2023 15:18:30 +0300 Subject: [PATCH 07/31] improvements and tests for palle-xcm-bridge-router --- Cargo.lock | 3 + modules/xcm-bridge-hub-router/Cargo.toml | 7 + modules/xcm-bridge-hub-router/src/lib.rs | 221 ++++++++++++++++++-- modules/xcm-bridge-hub-router/src/mock.rs | 142 +++++++++++++ primitives/xcm-bridge-hub-router/src/lib.rs | 8 +- 5 files changed, 364 insertions(+), 17 deletions(-) create mode 100644 modules/xcm-bridge-hub-router/src/mock.rs diff --git a/Cargo.lock b/Cargo.lock index 114b2cf1bb..7950603712 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7698,8 +7698,11 @@ dependencies = [ "bp-xcm-bridge-hub-router", "frame-support", "frame-system", + "log", "parity-scale-codec", "scale-info", + "sp-core", + "sp-io", "sp-runtime", "sp-std 8.0.0", "xcm", diff --git a/modules/xcm-bridge-hub-router/Cargo.toml b/modules/xcm-bridge-hub-router/Cargo.toml index 2500b045c8..8d47ef307c 100644 --- a/modules/xcm-bridge-hub-router/Cargo.toml +++ b/modules/xcm-bridge-hub-router/Cargo.toml @@ -8,6 +8,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +log = { version = "0.4.19", default-features = false } scale-info = { version = "2.8.0", default-features = false, features = ["bit-vec", "derive", "serde"] } # Bridge dependencies @@ -26,6 +27,11 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", d xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" } + [features] default = ["std"] std = [ @@ -33,6 +39,7 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", + "log/std", "scale-info/std", "sp-runtime/std", "sp-std/std", diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 6d787dd133..d8fa55efa7 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -17,15 +17,15 @@ //! Pallet that may be used instead of `SovereignPaidRemoteExporter` in the XCM router //! configuration. The main thing that the pallet 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 overloaded. +//! if the queue between this chain and the sibling/child bridge hub is congested. //! //! All other bridge hub queues offer some backpressure mechanisms. So if at least one -//! of all queues is overloaded, it will eventually lead to the growth of the queue at +//! of all queues is congested, it will eventually lead to the growth of the queue at //! this chain. #![cfg_attr(not(feature = "std"), no_std)] -use bp_xcm_bridge_hub_router::LocalXcmQueue; +use bp_xcm_bridge_hub_router::LocalXcmChannel; use codec::Encode; use frame_support::traits::Get; use sp_runtime::{traits::One, FixedPointNumber, FixedU128, Saturating}; @@ -34,12 +34,23 @@ use xcm_builder::{ExporterFor, SovereignPaidRemoteExporter}; pub use pallet::*; +mod mock; + /// The factor that is used to increase current message fee factor when bridge experiencing /// some lags. const EXPONENTIAL_FEE_BASE: FixedU128 = FixedU128::from_rational(105, 100); // 1.05 /// The factor that is used to increase current message fee factor for every sent kilobyte. const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0.001 +/// Maximal size of the XCM message that may be sent over bridge. +/// +/// This should be less than the maximal size, allowed by the messages pallet, because +/// the message itself is wrapped in other structs and is double encoded. +pub const HARD_MESSAGE_SIZE_LIMIT: u32 = 32 * 1024; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-hub-router"; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -56,7 +67,10 @@ pub mod pallet { type BridgedNetworkId: Get; /// Actual message sender (XCMP/DMP) to the sibling bridge hub location. - type ToBridgeHubSender: SendXcm + LocalXcmQueue; + type ToBridgeHubSender: SendXcm; + /// Underlying channel with the sibling bridge hub. It must match the channel, used + /// by the `Self::ToBridgeHubSender`. + type WithBridgeHubChannel: LocalXcmChannel; /// Base bridge fee that is paid for every outbound message. type BaseFee: Get; @@ -72,13 +86,22 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - // if XCM queue is still overloaded, we don't change anything - if T::ToBridgeHubSender::is_overloaded() { + // if XCM queue is still congested, we don't change anything + if T::WithBridgeHubChannel::is_congested() { return Weight::zero() // TODO: benchmarks } DeliveryFeeFactor::::mutate(|f| { + let previous_factor = *f; *f = InitialFactor::get().max(*f / EXPONENTIAL_FEE_BASE); + if previous_factor != *f { + log::info!( + target: LOG_TARGET, + "Bridge queue is uncongested. Decreased fee factor from {} to {}", + previous_factor, + f, + ); + } *f }); @@ -101,8 +124,8 @@ pub mod pallet { impl, I: 'static> Pallet { /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. pub(crate) fn on_message_sent_to_bridge(message_size: u32) { - // if outbound queue is not overloaded, do nothing - if !T::ToBridgeHubSender::is_overloaded() { + // if outbound queue is not congested, do nothing + if !T::WithBridgeHubChannel::is_congested() { return } @@ -111,7 +134,14 @@ pub mod pallet { .saturating_mul(MESSAGE_SIZE_FEE_BASE); let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); DeliveryFeeFactor::::mutate(|f| { + let previous_factor = *f; *f = f.saturating_mul(total_factor); + log::info!( + target: LOG_TARGET, + "Bridge queue is congested. Increased fee factor from {} to {}", + previous_factor, + f, + ); *f }); } @@ -139,11 +169,22 @@ impl, I: 'static> ExporterFor for Pallet { return None } - // compute fee amount - let mesage_size = message.encoded_size(); - let message_fee = (mesage_size as u128).saturating_mul(T::ByteFee::get()); + // compute fee amount. Keep in mind that this is only the bridge fee. The fee for sending + // message from this chain to child/sibling bridge hub is determined by the + // `Config::ToBridgeHubSender` + let message_size = message.encoded_size(); + let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get()); let fee_sum = T::BaseFee::get().saturating_add(message_fee); - let fee = Self::delivery_fee_factor().saturating_mul_int(fee_sum); + let fee_factor = Self::delivery_fee_factor(); + let fee = fee_factor.saturating_mul_int(fee_sum); + + log::info!( + target: LOG_TARGET, + "Going to send message ({} bytes) over bridge. Computed bridge fee {} using fee factor {}", + fee, + fee_factor, + message_size, + ); Some((T::SiblingBridgeHubLocation::get(), Some((T::FeeAsset::get(), fee).into()))) } @@ -166,7 +207,11 @@ impl, I: 'static> SendXcm for Pallet { .map(|xcm| xcm.encoded_size() as _) .ok_or(SendError::MissingArgument)?; - // TODO: we can reject large messages here (now they're just dropped at the bridge hub)!!! + // bridge doesn't support oversized/overweight messages now. So it is better to drop such + // messages here than at the bridge hub. Let's check the message size. + if message_size > HARD_MESSAGE_SIZE_LIMIT { + return Err(SendError::ExceedsMaxMessageSize) + } // just use exporter to validate destination and insert instructions to pay message fee // at the sibling/child bridge hub @@ -190,3 +235,153 @@ impl, I: 'static> SendXcm for Pallet { Ok(xcm_hash) } } + +#[cfg(test)] +mod tests { + use super::*; + use mock::*; + + use frame_support::traits::Hooks; + + #[test] + fn initial_fee_factor_is_one() { + run_test(|| { + assert_eq!(DeliveryFeeFactor::::get(), FixedU128::one()); + }) + } + + #[test] + fn fee_factor_is_not_decreased_from_on_initialize_when_queue_is_congested() { + run_test(|| { + DeliveryFeeFactor::::put(FixedU128::from_rational(125, 100)); + TestWithBridgeHubChannel::make_congested(); + + // it should not decrease, because queue is congested + let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + XcmBridgeHubRouter::on_initialize(One::one()); + assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), old_delivery_fee_factor); + }) + } + + #[test] + fn fee_factor_is_decreased_from_on_initialize_when_queue_is_uncongested() { + run_test(|| { + DeliveryFeeFactor::::put(FixedU128::from_rational(125, 100)); + + // it shold eventually decreased to one + while XcmBridgeHubRouter::delivery_fee_factor() > FixedU128::one() { + XcmBridgeHubRouter::on_initialize(One::one()); + } + + // verify that it doesn't decreases anymore + XcmBridgeHubRouter::on_initialize(One::one()); + assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), FixedU128::one()); + }) + } + + #[test] + fn not_applicable_if_destination_is_within_other_network() { + run_test(|| { + assert_eq!( + send_xcm::( + MultiLocation::new(2, X2(GlobalConsensus(Rococo), Parachain(1000))), + vec![].into(), + ), + Err(SendError::NotApplicable), + ); + }); + } + + #[test] + fn exceeds_max_message_size_if_size_is_above_hard_limit() { + run_test(|| { + assert_eq!( + send_xcm::( + MultiLocation::new(2, X2(GlobalConsensus(Rococo), Parachain(1000))), + vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into(), + ), + Err(SendError::ExceedsMaxMessageSize), + ); + }); + } + + #[test] + fn returns_proper_delivery_price() { + run_test(|| { + let dest = MultiLocation::new(2, X1(GlobalConsensus(BridgedNetworkId::get()))); + let xcm: Xcm<()> = vec![ClearOrigin].into(); + let msg_size = xcm.encoded_size(); + + // initially the base fee is used: `BASE_FEE + BYTE_FEE * msg_size + HRMP_FEE` + let expected_fee = BASE_FEE + BYTE_FEE * (msg_size as u128) + HRMP_FEE; + assert_eq!( + XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm.clone())) + .unwrap() + .1 + .get(0), + Some(&(BridgeFeeAsset::get(), expected_fee).into()), + ); + + // but when factor is larger than one, it increases the fee, so it becomes: + // `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE` + let factor = FixedU128::from_rational(125, 100); + DeliveryFeeFactor::::put(factor); + let expected_fee = + (FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) * + factor) + .into_inner() / FixedU128::DIV + + HRMP_FEE; + assert_eq!( + XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm.clone())) + .unwrap() + .1 + .get(0), + Some(&(BridgeFeeAsset::get(), expected_fee).into()), + ); + }); + } + + #[test] + fn sent_message_doesnt_increase_factor_if_queue_is_uncongested() { + run_test(|| { + let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + assert_eq!( + send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop), + Ok(()), + ); + + assert!(TestToBridgeHubSender::is_message_sent()); + assert_eq!(old_delivery_fee_factor, XcmBridgeHubRouter::delivery_fee_factor()); + }); + } + + #[test] + fn sent_message_increases_factor_if_queue_is_congested() { + run_test(|| { + TestWithBridgeHubChannel::make_congested(); + + let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + assert_eq!( + send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop), + Ok(()), + ); + + assert!(TestToBridgeHubSender::is_message_sent()); + assert!(old_delivery_fee_factor < XcmBridgeHubRouter::delivery_fee_factor()); + }); + } +} diff --git a/modules/xcm-bridge-hub-router/src/mock.rs b/modules/xcm-bridge-hub-router/src/mock.rs new file mode 100644 index 0000000000..2c64f0984d --- /dev/null +++ b/modules/xcm-bridge-hub-router/src/mock.rs @@ -0,0 +1,142 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +#![cfg(test)] + +use crate as pallet_xcm_bridge_hub_router; + +use bp_xcm_bridge_hub_router::LocalXcmChannel; +use frame_support::{construct_runtime, parameter_types}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, ConstU128, IdentityLookup}, + BuildStorage, +}; +use xcm::prelude::*; + +pub type AccountId = u64; +type Block = frame_system::mocking::MockBlock; + +/// HRMP fee. +pub const HRMP_FEE: u128 = 500; +/// Base bridge fee. +pub const BASE_FEE: u128 = 1_000_000; +/// Byte bridge fee. +pub const BYTE_FEE: u128 = 1_000; + +construct_runtime! { + pub enum TestRuntime + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + XcmBridgeHubRouter: pallet_xcm_bridge_hub_router::{Pallet, Storage}, + } +} + +parameter_types! { + pub ThisNetworkId: NetworkId = Polkadot; + pub BridgedNetworkId: NetworkId = Kusama; + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(ThisNetworkId::get()), Parachain(1000)); + pub SiblingBridgeHubLocation: MultiLocation = ParentThen(X1(Parachain(1002))).into(); + pub BridgeFeeAsset: AssetId = MultiLocation::parent().into(); +} + +impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { + type UniversalLocation = UniversalLocation; + type SiblingBridgeHubLocation = SiblingBridgeHubLocation; + type BridgedNetworkId = BridgedNetworkId; + + type ToBridgeHubSender = TestToBridgeHubSender; + type WithBridgeHubChannel = TestWithBridgeHubChannel; + + type BaseFee = ConstU128; + type ByteFee = ConstU128; + type FeeAsset = BridgeFeeAsset; +} + +pub struct TestToBridgeHubSender; + +impl TestToBridgeHubSender { + pub fn is_message_sent() -> bool { + frame_support::storage::unhashed::get_or_default(b"TestToBridgeHubSender.Sent") + } +} + +impl SendXcm for TestToBridgeHubSender { + type Ticket = (); + + fn validate( + _destination: &mut Option, + _message: &mut Option>, + ) -> SendResult { + Ok(((), (BridgeFeeAsset::get(), HRMP_FEE).into())) + } + + fn deliver(_ticket: Self::Ticket) -> Result { + frame_support::storage::unhashed::put(b"TestToBridgeHubSender.Sent", &true); + Ok([0u8; 32].into()) + } +} + +pub struct TestWithBridgeHubChannel; + +impl TestWithBridgeHubChannel { + pub fn make_congested() { + frame_support::storage::unhashed::put(b"TestWithBridgeHubChannel.Congested", &true); + } +} + +impl LocalXcmChannel for TestWithBridgeHubChannel { + fn is_congested() -> bool { + frame_support::storage::unhashed::get_or_default(b"TestWithBridgeHubChannel.Congested") + } +} + +/// Return test externalities to use in tests. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + sp_io::TestExternalities::new(t) +} + +/// Run pallet test. +pub fn run_test(test: impl FnOnce() -> T) -> T { + new_test_ext().execute_with(|| test()) +} diff --git a/primitives/xcm-bridge-hub-router/src/lib.rs b/primitives/xcm-bridge-hub-router/src/lib.rs index 78d3290fdd..f8160085d2 100644 --- a/primitives/xcm-bridge-hub-router/src/lib.rs +++ b/primitives/xcm-bridge-hub-router/src/lib.rs @@ -18,8 +18,8 @@ #![cfg_attr(not(feature = "std"), no_std)] -/// Local XCM queue. -pub trait LocalXcmQueue { - /// Returns true if the queue is currently overloaded. - fn is_overloaded() -> bool; +/// Local XCM channel that may report whether it is congested or not. +pub trait LocalXcmChannel { + /// Returns true if the queue is currently congested. + fn is_congested() -> bool; } From d9515f7317b03e025549fd2a2310a8092bf96265 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 27 Jul 2023 15:28:32 +0300 Subject: [PATCH 08/31] use LocalXcmChannel in XcmBlobMessageDispatch --- bin/millau/runtime/src/rialto_messages.rs | 2 +- .../runtime/src/rialto_parachain_messages.rs | 2 +- .../runtime/src/millau_messages.rs | 2 +- bin/rialto/runtime/src/millau_messages.rs | 2 +- bin/runtime-common/Cargo.toml | 2 ++ .../src/messages_xcm_extension.rs | 23 +++++-------------- primitives/xcm-bridge-hub-router/src/lib.rs | 6 +++++ 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/bin/millau/runtime/src/rialto_messages.rs b/bin/millau/runtime/src/rialto_messages.rs index d5ada43a4a..fd6ab6f46b 100644 --- a/bin/millau/runtime/src/rialto_messages.rs +++ b/bin/millau/runtime/src/rialto_messages.rs @@ -67,7 +67,7 @@ pub type FromRialtoMessageDispatch = bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch< crate::xcm_config::OnMillauBlobDispatcher, (), - ConstBool, + (), >; /// Maximal outbound payload size of Millau -> Rialto messages. diff --git a/bin/millau/runtime/src/rialto_parachain_messages.rs b/bin/millau/runtime/src/rialto_parachain_messages.rs index 12a956a4be..bde4169aeb 100644 --- a/bin/millau/runtime/src/rialto_parachain_messages.rs +++ b/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -62,7 +62,7 @@ pub type FromRialtoParachainMessageDispatch = bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch< crate::xcm_config::OnMillauBlobDispatcher, (), - ConstBool, + (), >; /// Maximal outbound payload size of Millau -> RialtoParachain messages. diff --git a/bin/rialto-parachain/runtime/src/millau_messages.rs b/bin/rialto-parachain/runtime/src/millau_messages.rs index 3da9affcc4..889b5c2bff 100644 --- a/bin/rialto-parachain/runtime/src/millau_messages.rs +++ b/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -62,7 +62,7 @@ pub type FromMillauMessageDispatch = bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch< crate::OnRialtoParachainBlobDispatcher, (), - ConstBool, + (), >; /// Messages proof for Millau -> RialtoParachain messages. diff --git a/bin/rialto/runtime/src/millau_messages.rs b/bin/rialto/runtime/src/millau_messages.rs index df639b4c75..bf35bc33d3 100644 --- a/bin/rialto/runtime/src/millau_messages.rs +++ b/bin/rialto/runtime/src/millau_messages.rs @@ -59,7 +59,7 @@ pub type FromMillauMessageDispatch = bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch< crate::xcm_config::OnRialtoBlobDispatcher, (), - ConstBool, + (), >; /// Messages proof for Millau -> Rialto messages. diff --git a/bin/runtime-common/Cargo.toml b/bin/runtime-common/Cargo.toml index 98b3bc9925..9ca6a4c5df 100644 --- a/bin/runtime-common/Cargo.toml +++ b/bin/runtime-common/Cargo.toml @@ -21,6 +21,7 @@ bp-parachains = { path = "../../primitives/parachains", default-features = false bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false } bp-relayers = { path = "../../primitives/relayers", default-features = false } bp-runtime = { path = "../../primitives/runtime", default-features = false } +bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false } pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false } pallet-bridge-messages = { path = "../../modules/messages", default-features = false } pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false } @@ -55,6 +56,7 @@ std = [ "bp-parachains/std", "bp-polkadot-core/std", "bp-runtime/std", + "bp-xcm-bridge-hub-router/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index fb2b8c902d..120cdb4b63 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -27,6 +27,7 @@ use bp_messages::{ LaneId, MessageNonce, }; use bp_runtime::messages::MessageDispatchResult; +use bp_xcm_bridge_hub_router::LocalXcmChannel; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ dispatch::Weight, @@ -56,30 +57,18 @@ pub enum XcmBlobMessageDispatchResult { /// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages /// /// It needs to be used at the target bridge hub. -pub struct XcmBlobMessageDispatch { - _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, IsChannelActive)>, +pub struct XcmBlobMessageDispatch { + _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, LocalXcmChannel)>, } -impl> - MessageDispatch for XcmBlobMessageDispatch +impl> + MessageDispatch for XcmBlobMessageDispatch { type DispatchPayload = XcmAsPlainPayload; type DispatchLevelResult = XcmBlobMessageDispatchResult; fn is_active() -> bool { - // TODO: we can only implement `IsChannelActive` in Cumulus. Assuming that all messages - // will only be sent to taget asset hub: - // - // ```rust - // pub struct IsChannelWithAssetHubActive; - // - // impl Get for IsChannelWithAssetHubActive { - // fn get() -> bool { - // !cumulus_pallet_xcmp_queue::InboundXcmpSuspended::get().contains(&SIBLNG_ASSET_HUB_PARA_ID) - // } - // } - // ``` - IsChannelActive::get() + !LocalXcmChannel::is_congested() } fn dispatch_weight(message: &mut DispatchMessage) -> Weight { diff --git a/primitives/xcm-bridge-hub-router/src/lib.rs b/primitives/xcm-bridge-hub-router/src/lib.rs index f8160085d2..e3d627660a 100644 --- a/primitives/xcm-bridge-hub-router/src/lib.rs +++ b/primitives/xcm-bridge-hub-router/src/lib.rs @@ -23,3 +23,9 @@ pub trait LocalXcmChannel { /// Returns true if the queue is currently congested. fn is_congested() -> bool; } + +impl LocalXcmChannel for () { + fn is_congested() -> bool { + false + } +} From 084f551bb6289abab27eab0f3138249e884c47de Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 27 Jul 2023 16:03:43 +0300 Subject: [PATCH 09/31] new tests for logic changes in messages pallet --- Cargo.lock | 1 + modules/messages/src/lib.rs | 40 +++++++++++++++++++++---- modules/messages/src/mock.rs | 36 +++++++++++++++++++--- primitives/messages/src/source_chain.rs | 7 ++--- primitives/messages/src/target_chain.rs | 3 ++ 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7950603712..e9ee2f5ac2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1173,6 +1173,7 @@ dependencies = [ "bp-relayers", "bp-runtime", "bp-test-utils", + "bp-xcm-bridge-hub-router", "frame-support", "frame-system", "hash-db", diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 1ab619bcc1..932d16c205 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -905,8 +905,9 @@ mod tests { mock::{ inbound_unrewarded_relayers_state, message, message_payload, run_test, unrewarded_relayer, AccountId, DbWeight, RuntimeEvent as TestEvent, RuntimeOrigin, - TestDeliveryConfirmationPayments, TestDeliveryPayments, TestMessagesDeliveryProof, - TestMessagesProof, TestRelayer, TestRuntime, TestWeightInfo, MAX_OUTBOUND_PAYLOAD_SIZE, + TestDeliveryConfirmationPayments, TestDeliveryPayments, TestMessageDispatch, + TestMessagesDeliveryProof, TestMessagesProof, TestOnMessagesDelivered, TestRelayer, + TestRuntime, TestWeightInfo, MAX_OUTBOUND_PAYLOAD_SIZE, PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_LANE_ID_2, TEST_LANE_ID_3, TEST_RELAYER_A, TEST_RELAYER_B, }, @@ -932,10 +933,16 @@ mod tests { fn send_regular_message() { get_ready_for_events(); - let message_nonce = - outbound_lane::(TEST_LANE_ID).data().latest_generated_nonce + 1; - send_message::(RuntimeOrigin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD) - .expect("send_message has failed"); + let outbound_lane = outbound_lane::(TEST_LANE_ID); + let message_nonce = outbound_lane.data().latest_generated_nonce + 1; + let prev_enqueud_messages = outbound_lane.queued_messages().checked_len().unwrap_or(0); + let artifacts = send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + REGULAR_PAYLOAD, + ) + .expect("send_message has failed"); + assert_eq!(artifacts.enqueued_messages, prev_enqueud_messages + 1); // check event with assigned nonce assert_eq!( @@ -977,6 +984,8 @@ mod tests { }, )); + assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0))); + assert_eq!( System::::events(), vec![EventRecord { @@ -1245,6 +1254,23 @@ mod tests { }); } + #[test] + fn receive_messages_fails_if_dispatcher_is_inactive() { + run_test(|| { + TestMessageDispatch::deactivate(); + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), + 1, + REGULAR_PAYLOAD.declared_weight, + ), + Error::::MessageDispatchInactive, + ); + }); + } + #[test] fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() { run_test(|| { @@ -1356,6 +1382,7 @@ mod tests { ); assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); + assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 1))); // this reports delivery of both message 1 and message 2 => reward is paid only to // TEST_RELAYER_B @@ -1398,6 +1425,7 @@ mod tests { ); assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); + assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0))); }); } diff --git a/modules/messages/src/mock.rs b/modules/messages/src/mock.rs index 9db8cec02a..aac83998bb 100644 --- a/modules/messages/src/mock.rs +++ b/modules/messages/src/mock.rs @@ -21,7 +21,9 @@ use crate::Config; use bp_messages::{ calc_relayers_rewards, - source_chain::{DeliveryConfirmationPayments, LaneMessageVerifier, TargetHeaderChain}, + source_chain::{ + DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, TargetHeaderChain, + }, target_chain::{ DeliveryPayments, DispatchMessage, DispatchMessageData, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, @@ -161,7 +163,7 @@ impl Config for TestRuntime { type TargetHeaderChain = TestTargetHeaderChain; type LaneMessageVerifier = TestLaneMessageVerifier; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; - type OnMessagesDelivered = (); + type OnMessagesDelivered = TestOnMessagesDelivered; type SourceHeaderChain = TestSourceHeaderChain; type MessageDispatch = TestMessageDispatch; @@ -404,16 +406,24 @@ impl SourceHeaderChain for TestSourceHeaderChain { } } -/// Source header chain that is used in tests. +/// Test message dispatcher. #[derive(Debug)] pub struct TestMessageDispatch; +impl TestMessageDispatch { + pub fn deactivate() { + frame_support::storage::unhashed::put(b"TestMessageDispatch.IsCongested", &true) + } +} + impl MessageDispatch for TestMessageDispatch { type DispatchPayload = TestPayload; type DispatchLevelResult = TestDispatchLevelResult; fn is_active() -> bool { - true + !frame_support::storage::unhashed::get_or_default::( + b"TestMessageDispatch.IsCongested", + ) } fn dispatch_weight(message: &mut DispatchMessage) -> Weight { @@ -433,6 +443,24 @@ impl MessageDispatch for TestMessageDispatch { } } +/// Test callback, called during message delivery confirmation transaction. +pub struct TestOnMessagesDelivered; + +impl TestOnMessagesDelivered { + pub fn call_arguments() -> Option<(LaneId, MessageNonce)> { + frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered") + } +} + +impl OnMessagesDelivered for TestOnMessagesDelivered { + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { + frame_support::storage::unhashed::put( + b"TestOnMessagesDelivered.OnMessagesDelivered", + &(lane, enqueued_messages), + ); + } +} + /// Return test lane message with given nonce and payload. pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { Message { key: MessageKey { lane_id: TEST_LANE_ID, nonce }, payload: payload.encode() } diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs index fa067b611d..bf0964d89a 100644 --- a/primitives/messages/src/source_chain.rs +++ b/primitives/messages/src/source_chain.rs @@ -116,12 +116,11 @@ impl DeliveryConfirmationPayments for () { } } -/// Callback that is called at the source chain (bridge hub) when we get delivery confrimation -/// for new messages. +/// Callback that is called at the source chain (bridge hub) when we get new delivery confrimation. pub trait OnMessagesDelivered { - /// New messages delivery has been confimed. + /// Messages delivery has been confimed. /// - /// The only argument of the function is the number of yet undelivered messages + /// The only argument of the function is the number of yet undelivered messages. fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce); } diff --git a/primitives/messages/src/target_chain.rs b/primitives/messages/src/target_chain.rs index d5bdc3cf54..0c824664a0 100644 --- a/primitives/messages/src/target_chain.rs +++ b/primitives/messages/src/target_chain.rs @@ -95,6 +95,9 @@ pub trait MessageDispatch { /// be treated as a hint by both dispatcher and its consumers - i.e. dispatcher shall not /// simply drop messages if it returns `false`. The consumer may still call the `dispatch` /// if dispatcher has returned `false`. + /// + /// We check it in the messages delivery transaction prolgoue. So if it becomes `false` + /// after some portion of messages is already dispatched, it doesn't fail the whole transaction. fn is_active() -> bool; /// Estimate dispatch weight. From d99420e14c104ea4644bd95a343b896bfd03cb0c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 27 Jul 2023 16:30:03 +0300 Subject: [PATCH 10/31] tests for LocalXcmQueueSuspender --- bin/runtime-common/src/messages_call_ext.rs | 3 - .../src/messages_xcm_extension.rs | 88 +++++++++++++++---- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/bin/runtime-common/src/messages_call_ext.rs b/bin/runtime-common/src/messages_call_ext.rs index 11d8174b5c..4ff933cfad 100644 --- a/bin/runtime-common/src/messages_call_ext.rs +++ b/bin/runtime-common/src/messages_call_ext.rs @@ -78,9 +78,6 @@ impl ReceiveMessagesProofInfo { /// - or there are no bundled messages, but the inbound lane is blocked by too many unconfirmed /// messages and/or unrewarded relayers. fn is_obsolete(&self, is_dispatcher_active: bool) -> bool { - // TODO: maybe rename method to `is_accepted`, because it isn't about **obsolete** messages - // anymore - // if dispatcher is inactive, we don't accept any delivery transactions if !is_dispatcher_active { return true diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 120cdb4b63..7b71ce60a3 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -31,7 +31,7 @@ use bp_xcm_bridge_hub_router::LocalXcmChannel; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ dispatch::Weight, - traits::{Get, ProcessMessage, ProcessMessageError, QueuePausedQuery}, + traits::{ProcessMessage, ProcessMessageError, QueuePausedQuery}, weights::WeightMeter, CloneNoBound, EqNoBound, PartialEqNoBound, }; @@ -57,18 +57,18 @@ pub enum XcmBlobMessageDispatchResult { /// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages /// /// It needs to be used at the target bridge hub. -pub struct XcmBlobMessageDispatch { - _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, LocalXcmChannel)>, +pub struct XcmBlobMessageDispatch { + _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, Channel)>, } -impl> - MessageDispatch for XcmBlobMessageDispatch +impl + MessageDispatch for XcmBlobMessageDispatch { type DispatchPayload = XcmAsPlainPayload; type DispatchLevelResult = XcmBlobMessageDispatchResult; fn is_active() -> bool { - !LocalXcmChannel::is_congested() + !Channel::is_congested() } fn dispatch_weight(message: &mut DispatchMessage) -> Weight { @@ -207,8 +207,10 @@ const SUSPENDED_QUEUE_MAP_STORAGE_PREFIX: &[u8] = b"SuspendedQueues"; /// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we /// stop processing XCM messages from the sending chain (asset hub) that "owns" the lane. -// TODO: should be some factor of `MaxUnconfirmedMessagesAtInboundLane` at bridged side? -const MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE: MessageNonce = 300; +/// +/// The value is a maximal number of messages that can be delivered in a single message +/// delivery transaction, used on initial bridge hubs. +const MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE: MessageNonce = 4096; impl LocalXcmQueueManager { /// Must be called whenever we push a message to the bridge lane. @@ -220,14 +222,14 @@ impl LocalXcmQueueManager { // suspend the inbound XCM queue with the sender to avoid queueing more messages // at the outbound bridge queue AND turn on internal backpressure mechanism of the // XCM queue - let is_overloaded = enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE; - if !is_overloaded { + let is_congested = enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE; + if !is_congested { return } log::info!( target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "Suspending inbound XCM queue with {:?} to avoid overloading lane {:?}: there are\ + "Suspending inbound XCM queue with {:?} to avoid congesting lane {:?}: there are\ {} messages queued at the bridge queue", sending_chain_location, lane, @@ -244,9 +246,9 @@ impl LocalXcmQueueManager { enqueued_messages: MessageNonce, ) { // the queue before this call may be either suspended or not. If the lane is still - // overloaded, we win't need to do anything - let is_overloaded = enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE; - if is_overloaded { + // congested, we win't need to do anything + let is_congested = enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE; + if is_congested { return } @@ -274,6 +276,9 @@ impl LocalXcmQueueManager { )) } + // since dynamic fees for v1 is a temporary solution, we're using direct storage writes here. + // In v2, the separate pallet (`palle-xcm-bridge-hub`) will store this data. + fn suspend_inbound_queue(with: Box) { frame_support::storage::unhashed::put(&Self::suspended_queue_map_storage_key(with), &true); } @@ -303,7 +308,7 @@ impl LocalXcmQueueManager { /// A structure that implements [`frame_support:traits::messages::ProcessMessage`] and may /// be used in the `pallet-message-queue` configuration to stop processing messages when the -/// bridge queue is overloaded. +/// bridge queue is congested. /// /// It needs to be used at the source bridge hub. pub struct LocalXcmQueueMessageProcessor(PhantomData<(Origin, Inner)>); @@ -341,7 +346,7 @@ where /// A structure that implements [`frame_support:traits::messages::QueuePausedQuery`] and may /// be used in the `pallet-message-queue` configuration to stop processing messages when the -/// bridge queue is overloaded. +/// bridge queue is congested. /// /// It needs to be used at the source bridge hub. pub struct LocalXcmQueueSuspender(PhantomData<(Origin, Inner)>); @@ -366,3 +371,54 @@ where false } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + + use sp_runtime::traits::{ConstBool, Get}; + + struct TestInnerXcmQueueSuspender(PhantomData); + impl> QueuePausedQuery + for TestInnerXcmQueueSuspender + { + fn is_paused(_: &MultiLocation) -> bool { + IsSuspended::get() + } + } + + type TestLocalXcmQueueSuspender = + LocalXcmQueueSuspender>>; + + fn test_origin_location() -> MultiLocation { + Here.into() + } + + fn test_origin() -> MultiLocation { + test_origin_location() + } + + #[test] + fn local_xcm_queue_is_paused_when_inner_suspender_returns_paused() { + run_test(|| { + assert!(LocalXcmQueueSuspender::< + MultiLocation, + TestInnerXcmQueueSuspender>, + >::is_paused(&test_origin())) + }) + } + + #[test] + fn local_xcm_queue_is_paused_when_bridge_queue_is_congested() { + run_test(|| { + LocalXcmQueueManager::suspend_inbound_queue(Box::new(test_origin_location())); + assert!(TestLocalXcmQueueSuspender::is_paused(&test_origin())) + }); + } + + #[test] + fn local_xcm_queue_is_not_paused_normally() { + run_test(|| assert!(!TestLocalXcmQueueSuspender::is_paused(&test_origin()))); + } +} From 4c741714cb737d8553f4b23ba5f0b94aecdeaa20 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 27 Jul 2023 16:37:41 +0300 Subject: [PATCH 11/31] tests for LocalXcmQueueMessageProcessor --- .../src/messages_xcm_extension.rs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 7b71ce60a3..3994d5ebca 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -379,6 +379,20 @@ mod tests { use sp_runtime::traits::{ConstBool, Get}; + struct TestInnerXcmQueueMessageProcessor; + impl ProcessMessage for TestInnerXcmQueueMessageProcessor { + type Origin = MultiLocation; + + fn process_message( + _message: &[u8], + _origin: Self::Origin, + _meter: &mut WeightMeter, + _id: &mut [u8; 32], + ) -> Result { + Ok(true) + } + } + struct TestInnerXcmQueueSuspender(PhantomData); impl> QueuePausedQuery for TestInnerXcmQueueSuspender @@ -388,6 +402,8 @@ mod tests { } } + type TestLocalXcmQueueMessageProcessor = + LocalXcmQueueMessageProcessor; type TestLocalXcmQueueSuspender = LocalXcmQueueSuspender>>; @@ -399,6 +415,37 @@ mod tests { test_origin_location() } + #[test] + fn inbound_xcm_message_from_sibling_is_not_processed_when_bridge_queue_is_congested() { + run_test(|| { + LocalXcmQueueManager::suspend_inbound_queue(Box::new(test_origin_location())); + assert_eq!( + TestLocalXcmQueueMessageProcessor::process_message( + &[42], + test_origin(), + &mut WeightMeter::max_limit(), + &mut [0u8; 32], + ), + Err(ProcessMessageError::Yield), + ); + }) + } + + #[test] + fn inbound_xcm_message_from_sibling_is_processed_normally() { + run_test(|| { + assert_eq!( + TestLocalXcmQueueMessageProcessor::process_message( + &[42], + test_origin(), + &mut WeightMeter::max_limit(), + &mut [0u8; 32], + ), + Ok(true), + ); + }) + } + #[test] fn local_xcm_queue_is_paused_when_inner_suspender_returns_paused() { run_test(|| { From b75e64fdf7c9cee1789fe323bed8aadf3913f5af Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 27 Jul 2023 16:53:20 +0300 Subject: [PATCH 12/31] tests for new logic in the XcmBlobHaulerAdapter --- .../src/messages_xcm_extension.rs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 3994d5ebca..e68a07bbb2 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -379,6 +379,24 @@ mod tests { use sp_runtime::traits::{ConstBool, Get}; + struct TestXcmBlobHauler; + impl XcmBlobHauler for TestXcmBlobHauler { + type MessageSender = BridgeMessages; + type MessageSenderOrigin = RuntimeOrigin; + + fn message_sender_origin() -> Self::MessageSenderOrigin { + RuntimeOrigin::root() + } + + fn sending_chain_location() -> MultiLocation { + ParentThen(X1(Parachain(1000))).into() + } + + fn xcm_lane() -> LaneId { + TEST_LANE_ID + } + } + struct TestInnerXcmQueueMessageProcessor; impl ProcessMessage for TestInnerXcmQueueMessageProcessor { type Origin = MultiLocation; @@ -402,6 +420,7 @@ mod tests { } } + type TestXcmBlobHaulerAdapter = XcmBlobHaulerAdapter; type TestLocalXcmQueueMessageProcessor = LocalXcmQueueMessageProcessor; type TestLocalXcmQueueSuspender = @@ -415,6 +434,48 @@ mod tests { test_origin_location() } + #[test] + fn inbound_xcm_queue_with_sending_chain_is_managed_by_blob_hauler() { + run_test(|| { + // while we enqueue `MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE` messages to the bridge + // queue, the inbound channel with the sending chain stays opened + for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE { + TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert!(!LocalXcmQueueManager::is_inbound_queue_suspended(Box::new( + TestXcmBlobHauler::sending_chain_location() + ))); + } + + // then when we enqueue more messages, we suspend inbound queue. Note that messages + // are not dropped - they're enqueued at the bridge queue + TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert!(LocalXcmQueueManager::is_inbound_queue_suspended(Box::new( + TestXcmBlobHauler::sending_chain_location() + ))); + + // okay - let's now emulate delivery confirmation. If we still have more than + // `MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE` in the bridge queue, the inbound + // channel stays suspended + TestXcmBlobHaulerAdapter::on_messages_delivered( + TEST_LANE_ID, + MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + 1, + ); + assert!(LocalXcmQueueManager::is_inbound_queue_suspended(Box::new( + TestXcmBlobHauler::sending_chain_location() + ))); + + // and when there's lte than `MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE` messages in the + // bridge queue, we resume the inbound channel + TestXcmBlobHaulerAdapter::on_messages_delivered( + TEST_LANE_ID, + MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE, + ); + assert!(!LocalXcmQueueManager::is_inbound_queue_suspended(Box::new( + TestXcmBlobHauler::sending_chain_location() + ))); + }); + } + #[test] fn inbound_xcm_message_from_sibling_is_not_processed_when_bridge_queue_is_congested() { run_test(|| { From 38cd8f3df340865c000e8e6de1ed4047db0fc870 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 27 Jul 2023 16:56:45 +0300 Subject: [PATCH 13/31] fix other tests in the bridge-runtime-common --- bin/runtime-common/src/mock.rs | 37 +++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/bin/runtime-common/src/mock.rs b/bin/runtime-common/src/mock.rs index 58fbb7aaff..a342979bf2 100644 --- a/bin/runtime-common/src/mock.rs +++ b/bin/runtime-common/src/mock.rs @@ -33,10 +33,13 @@ use crate::messages::{ }; use bp_header_chain::{ChainWithGrandpa, HeaderChain}; -use bp_messages::{target_chain::ForbidInboundMessages, LaneId, MessageNonce}; +use bp_messages::{ + target_chain::{DispatchMessage, MessageDispatch}, + LaneId, MessageNonce, +}; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_relayers::PayRewardFromAccount; -use bp_runtime::{Chain, ChainId, Parachain, UnderlyingChainProvider}; +use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Parachain, UnderlyingChainProvider}; use codec::{Decode, Encode}; use frame_support::{ parameter_types, @@ -248,7 +251,7 @@ impl pallet_bridge_messages::Config for TestRuntime { type OnMessagesDelivered = (); type SourceHeaderChain = SourceHeaderChainAdapter; - type MessageDispatch = ForbidInboundMessages<(), FromBridgedChainMessagePayload>; + type MessageDispatch = DummyMessageDispatch; type BridgedChainId = BridgedChainId; } @@ -260,6 +263,34 @@ impl pallet_bridge_relayers::Config for TestRuntime { type WeightInfo = (); } +/// Dummy message dispatcher. +pub struct DummyMessageDispatch; + +impl DummyMessageDispatch { + pub fn deactivate() { + frame_support::storage::unhashed::put(&b"inactive"[..], &false); + } +} + +impl MessageDispatch for DummyMessageDispatch { + type DispatchPayload = Vec; + type DispatchLevelResult = (); + + fn is_active() -> bool { + frame_support::storage::unhashed::take::(&b"inactive"[..]) != Some(false) + } + + fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + Weight::zero() + } + + fn dispatch( + _: DispatchMessage, + ) -> MessageDispatchResult { + MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } + } +} + /// Bridge that is deployed on `ThisChain` and allows sending/receiving messages to/from /// `BridgedChain`. #[derive(Debug, PartialEq, Eq)] From 958243564d8ea3cdec61d93e45c000394516e018 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 27 Jul 2023 17:02:26 +0300 Subject: [PATCH 14/31] extension_reject_call_when_dispatcher_is_inactive --- bin/runtime-common/src/messages_call_ext.rs | 16 ++++++++++++++-- bin/runtime-common/src/mock.rs | 4 +++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/bin/runtime-common/src/messages_call_ext.rs b/bin/runtime-common/src/messages_call_ext.rs index 4ff933cfad..0a1d724362 100644 --- a/bin/runtime-common/src/messages_call_ext.rs +++ b/bin/runtime-common/src/messages_call_ext.rs @@ -334,8 +334,8 @@ mod tests { }, messages_call_ext::MessagesCallSubType, mock::{ - MaxUnconfirmedMessagesAtInboundLane, MaxUnrewardedRelayerEntriesAtInboundLane, - TestRuntime, ThisChainRuntimeCall, + DummyMessageDispatch, MaxUnconfirmedMessagesAtInboundLane, + MaxUnrewardedRelayerEntriesAtInboundLane, TestRuntime, ThisChainRuntimeCall, }, }; use bp_messages::{DeliveredMessages, UnrewardedRelayer, UnrewardedRelayersState}; @@ -442,6 +442,18 @@ mod tests { }); } + #[test] + fn extension_reject_call_when_dispatcher_is_inactive() { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + // when current best delivered is message#10 and we're trying to deliver message 11..=15 + // => tx is accepted, but we have inactive dispatcher, so... + deliver_message_10(); + + DummyMessageDispatch::deactivate(); + assert!(!validate_message_delivery(11, 15)); + }); + } + #[test] fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots( ) { diff --git a/bin/runtime-common/src/mock.rs b/bin/runtime-common/src/mock.rs index a342979bf2..9c41d17fa9 100644 --- a/bin/runtime-common/src/mock.rs +++ b/bin/runtime-common/src/mock.rs @@ -39,7 +39,9 @@ use bp_messages::{ }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_relayers::PayRewardFromAccount; -use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Parachain, UnderlyingChainProvider}; +use bp_runtime::{ + messages::MessageDispatchResult, Chain, ChainId, Parachain, UnderlyingChainProvider, +}; use codec::{Decode, Encode}; use frame_support::{ parameter_types, From 522bbc7ec4f41385073db74087f4143507a5dadd Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 28 Jul 2023 10:47:04 +0300 Subject: [PATCH 15/31] benchmarks for pallet-xcm-bridge-hub-router --- .gitlab-ci.yml | 1 + Cargo.lock | 3 + bin/millau/runtime/Cargo.toml | 5 + bin/millau/runtime/src/lib.rs | 36 ++++- bin/millau/runtime/src/rialto_messages.rs | 2 +- .../runtime/src/rialto_parachain_messages.rs | 2 +- bin/millau/runtime/src/xcm_config.rs | 19 +++ .../runtime/src/millau_messages.rs | 2 +- bin/rialto/runtime/src/millau_messages.rs | 2 +- modules/xcm-bridge-hub-router/Cargo.toml | 6 + .../xcm-bridge-hub-router/src/benchmarking.rs | 49 +++++++ modules/xcm-bridge-hub-router/src/lib.rs | 21 ++- modules/xcm-bridge-hub-router/src/mock.rs | 2 + modules/xcm-bridge-hub-router/src/weights.rs | 128 ++++++++++++++++++ scripts/update-weights.sh | 12 ++ 15 files changed, 279 insertions(+), 11 deletions(-) create mode 100644 modules/xcm-bridge-hub-router/src/benchmarking.rs create mode 100644 modules/xcm-bridge-hub-router/src/weights.rs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 31fb9868b8..4b212dba42 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -194,6 +194,7 @@ benchmarks-test: - time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet --chain=dev --steps=2 --repeat=1 --pallet=pallet_bridge_grandpa --extrinsic=* --execution=wasm --wasm-execution=Compiled --heap-pages=4096 - time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet --chain=dev --steps=2 --repeat=1 --pallet=pallet_bridge_parachains --extrinsic=* --execution=wasm --wasm-execution=Compiled --heap-pages=4096 - time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet --chain=dev --steps=2 --repeat=1 --pallet=pallet_bridge_relayers --extrinsic=* --execution=wasm --wasm-execution=Compiled --heap-pages=4096 + - time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet --chain=dev --steps=2 --repeat=1 --pallet=pallet_xcm_bridge_hub_router --extrinsic=* --execution=wasm --wasm-execution=Compiled --heap-pages=4096 # we may live with failing benchmarks, it is just a signal for us allow_failure: true diff --git a/Cargo.lock b/Cargo.lock index e9ee2f5ac2..cd59bf9f98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5955,6 +5955,7 @@ dependencies = [ "bp-rialto-parachain", "bp-runtime", "bp-westend", + "bp-xcm-bridge-hub-router", "bridge-runtime-common", "env_logger", "frame-benchmarking", @@ -5981,6 +5982,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-utility", "pallet-xcm", + "pallet-xcm-bridge-hub-router", "parity-scale-codec", "scale-info", "sp-api", @@ -7697,6 +7699,7 @@ name = "pallet-xcm-bridge-hub-router" version = "0.1.0" dependencies = [ "bp-xcm-bridge-hub-router", + "frame-benchmarking", "frame-support", "frame-system", "log", diff --git a/bin/millau/runtime/Cargo.toml b/bin/millau/runtime/Cargo.toml index c92698edae..375700918d 100644 --- a/bin/millau/runtime/Cargo.toml +++ b/bin/millau/runtime/Cargo.toml @@ -22,12 +22,14 @@ bp-rialto = { path = "../../../primitives/chain-rialto", default-features = fals bp-rialto-parachain = { path = "../../../primitives/chain-rialto-parachain", default-features = false } bp-runtime = { path = "../../../primitives/runtime", default-features = false } bp-westend = { path = "../../../primitives/chain-westend", default-features = false } +bp-xcm-bridge-hub-router = { path = "../../../primitives/xcm-bridge-hub-router", default-features = false } bridge-runtime-common = { path = "../../runtime-common", default-features = false } pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false } pallet-bridge-messages = { path = "../../../modules/messages", default-features = false } pallet-bridge-parachains = { path = "../../../modules/parachains", default-features = false } pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false } pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false } +pallet-xcm-bridge-hub-router = { path = "../../../modules/xcm-bridge-hub-router", default-features = false } # Substrate Dependencies @@ -89,6 +91,7 @@ std = [ "bp-rialto-parachain/std", "bp-runtime/std", "bp-westend/std", + "bp-xcm-bridge-hub-router/std", "bridge-runtime-common/std", "codec/std", "frame-executive/std", @@ -113,6 +116,7 @@ std = [ "pallet-transaction-payment/std", "pallet-utility/std", "pallet-xcm/std", + "pallet-xcm-bridge-hub-router/std", "scale-info/std", "sp-api/std", "sp-block-builder/std", @@ -139,6 +143,7 @@ runtime-benchmarks = [ "pallet-bridge-parachains/runtime-benchmarks", "pallet-bridge-relayers/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", + "pallet-xcm-bridge-hub-router/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", ] diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index c0fff1895b..bec6121455 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -65,8 +65,8 @@ pub use frame_support::{ dispatch::DispatchClass, parameter_types, traits::{ - ConstBool, ConstU32, ConstU64, ConstU8, Currency, ExistenceRequirement, Imbalance, - KeyOwnerProofSystem, + ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Currency, ExistenceRequirement, + Imbalance, KeyOwnerProofSystem, }, weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, IdentityFee, RuntimeDbWeight, @@ -546,6 +546,23 @@ impl pallet_utility::Config for Runtime { type WeightInfo = (); } +// this config is totally incorrect - the pallet is not actually used at this runtime. We need +// it only to be able to run benchmarks and make required traits (and default weights for tests). +impl pallet_xcm_bridge_hub_router::Config for Runtime { + type WeightInfo = (); + + type UniversalLocation = xcm_config::UniversalLocation; + type SiblingBridgeHubLocation = xcm_config::TokenLocation; + type BridgedNetworkId = xcm_config::RialtoNetwork; + + type ToBridgeHubSender = xcm_config::XcmRouter; + type WithBridgeHubChannel = xcm_config::EmulatedSiblingXcmpChannel; + + type BaseFee = ConstU128<1_000_000_000>; + type ByteFee = ConstU128<1_000>; + type FeeAsset = xcm_config::TokenAssetId; +} + construct_runtime!( pub enum Runtime { System: frame_system::{Pallet, Call, Config, Storage, Event}, @@ -584,6 +601,9 @@ construct_runtime!( // Pallet for sending XCM. XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 99, + + // Pallets that are not actually used here (yet?), but we need to run benchmarks on it. + XcmBridgeHubRouter: pallet_xcm_bridge_hub_router::{Pallet, Storage} = 200, } ); @@ -656,6 +676,7 @@ mod benches { [pallet_bridge_grandpa, BridgeRialtoGrandpa] [pallet_bridge_parachains, ParachainsBench::] [pallet_bridge_relayers, RelayersBench::] + [pallet_xcm_bridge_hub_router, XcmBridgeHubRouterBench::] ); } @@ -972,6 +993,7 @@ impl_runtime_apis! { use pallet_bridge_messages::benchmarking::Pallet as MessagesBench; use pallet_bridge_parachains::benchmarking::Pallet as ParachainsBench; use pallet_bridge_relayers::benchmarking::Pallet as RelayersBench; + use pallet_xcm_bridge_hub_router::benchmarking::Pallet as XcmBridgeHubRouterBench; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1018,6 +1040,10 @@ impl_runtime_apis! { Pallet as RelayersBench, Config as RelayersConfig, }; + use pallet_xcm_bridge_hub_router::benchmarking::{ + Pallet as XcmBridgeHubRouterBench, + Config as XcmBridgeHubRouterConfig, + }; use rialto_messages::WithRialtoMessageBridge; use rialto_parachain_messages::WithRialtoParachainMessageBridge; @@ -1128,6 +1154,12 @@ impl_runtime_apis! { } } + impl XcmBridgeHubRouterConfig<()> for Runtime { + fn make_congested() { + xcm_config::EmulatedSiblingXcmpChannel::make_congested() + } + } + let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/bin/millau/runtime/src/rialto_messages.rs b/bin/millau/runtime/src/rialto_messages.rs index fd6ab6f46b..b6b64d73f9 100644 --- a/bin/millau/runtime/src/rialto_messages.rs +++ b/bin/millau/runtime/src/rialto_messages.rs @@ -25,7 +25,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, }; -use frame_support::{parameter_types, traits::ConstBool, weights::Weight, RuntimeDebug}; +use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; use pallet_bridge_relayers::WeightInfoExt as _; use xcm::latest::prelude::*; use xcm_builder::HaulBlobExporter; diff --git a/bin/millau/runtime/src/rialto_parachain_messages.rs b/bin/millau/runtime/src/rialto_parachain_messages.rs index bde4169aeb..57aafe5913 100644 --- a/bin/millau/runtime/src/rialto_parachain_messages.rs +++ b/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -27,7 +27,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, }; -use frame_support::{parameter_types, traits::ConstBool, weights::Weight, RuntimeDebug}; +use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; use pallet_bridge_relayers::WeightInfoExt as _; use xcm::latest::prelude::*; use xcm_builder::HaulBlobExporter; diff --git a/bin/millau/runtime/src/xcm_config.rs b/bin/millau/runtime/src/xcm_config.rs index 623c821901..a7abbdf255 100644 --- a/bin/millau/runtime/src/xcm_config.rs +++ b/bin/millau/runtime/src/xcm_config.rs @@ -42,6 +42,8 @@ parameter_types! { /// chain, we make it synonymous with it and thus it is the `Here` location, which means "equivalent to /// the context". pub const TokenLocation: MultiLocation = Here.into_location(); + /// Token asset identifier. + pub TokenAssetId: AssetId = TokenLocation::get().into(); /// The Millau network ID. pub const ThisNetwork: NetworkId = CustomNetworkId::Millau.as_network_id(); /// The Rialto network ID. @@ -235,6 +237,23 @@ impl ExportXcm for ToRialtoOrRialtoParachainSwitchExporter { } } +/// Emulating XCMP channel with sibling chain. We don't have required infra here, at Millau, +/// so we have to provide at least something to be able to run benchmarks. +pub struct EmulatedSiblingXcmpChannel; + +impl EmulatedSiblingXcmpChannel { + /// Start emulating congested channel. + pub fn make_congested() { + frame_support::storage::unhashed::put(b"EmulatedSiblingXcmpChannel.Congested", &true); + } +} + +impl bp_xcm_bridge_hub_router::LocalXcmChannel for EmulatedSiblingXcmpChannel { + fn is_congested() -> bool { + frame_support::storage::unhashed::get_or_default(b"EmulatedSiblingXcmpChannel.Congested") + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/bin/rialto-parachain/runtime/src/millau_messages.rs b/bin/rialto-parachain/runtime/src/millau_messages.rs index 889b5c2bff..2e29e1a8e2 100644 --- a/bin/rialto-parachain/runtime/src/millau_messages.rs +++ b/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -28,7 +28,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, }; -use frame_support::{parameter_types, traits::ConstBool, weights::Weight, RuntimeDebug}; +use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; use xcm::latest::prelude::*; use xcm_builder::HaulBlobExporter; diff --git a/bin/rialto/runtime/src/millau_messages.rs b/bin/rialto/runtime/src/millau_messages.rs index bf35bc33d3..82a4dc7850 100644 --- a/bin/rialto/runtime/src/millau_messages.rs +++ b/bin/rialto/runtime/src/millau_messages.rs @@ -25,7 +25,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, }; -use frame_support::{parameter_types, traits::ConstBool, weights::Weight, RuntimeDebug}; +use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; use xcm::latest::prelude::*; use xcm_builder::HaulBlobExporter; diff --git a/modules/xcm-bridge-hub-router/Cargo.toml b/modules/xcm-bridge-hub-router/Cargo.toml index 8d47ef307c..5a498271da 100644 --- a/modules/xcm-bridge-hub-router/Cargo.toml +++ b/modules/xcm-bridge-hub-router/Cargo.toml @@ -17,6 +17,7 @@ bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", de # Substrate Dependencies +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -37,6 +38,7 @@ default = ["std"] std = [ "bp-xcm-bridge-hub-router/std", "codec/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "log/std", @@ -46,6 +48,10 @@ std = [ "xcm/std", "xcm-builder/std", ] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", diff --git a/modules/xcm-bridge-hub-router/src/benchmarking.rs b/modules/xcm-bridge-hub-router/src/benchmarking.rs new file mode 100644 index 0000000000..1a4338a6fc --- /dev/null +++ b/modules/xcm-bridge-hub-router/src/benchmarking.rs @@ -0,0 +1,49 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! XCM bridge hub router pallet benchmarks. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{DeliveryFeeFactor, InitialFactor}; + +use frame_benchmarking::benchmarks_instance_pallet; +use frame_support::traits::{Get, Hooks}; +use sp_runtime::traits::Zero; + +/// Pallet we're benchmarking here. +pub struct Pallet, I: 'static = ()>(crate::Pallet); + +/// Trait that must be implemented by runtime to be able to benchmark pallet properly. +pub trait Config: crate::Config { + /// Fill up queue so it becomes congested. + fn make_congested(); +} + +benchmarks_instance_pallet! { + on_initialize_when_non_congested { + DeliveryFeeFactor::::put(InitialFactor::get() + InitialFactor::get()); + }: { + crate::Pallet::::on_initialize(Zero::zero()) + } + + on_initialize_when_congested { + DeliveryFeeFactor::::put(InitialFactor::get() + InitialFactor::get()); + T::make_congested(); + }: { + crate::Pallet::::on_initialize(Zero::zero()) + } +} diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index d8fa55efa7..a9177412a2 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -33,6 +33,10 @@ use xcm::prelude::*; use xcm_builder::{ExporterFor, SovereignPaidRemoteExporter}; pub use pallet::*; +pub use weights::WeightInfo; + +pub mod benchmarking; +pub mod weights; mod mock; @@ -59,6 +63,9 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// Benchmarks results from runtime we're plugged into. + type WeightInfo: WeightInfo; + /// Universal location of this runtime. type UniversalLocation: Get; /// Relative location of the sibling bridge hub. @@ -88,7 +95,7 @@ pub mod pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { // if XCM queue is still congested, we don't change anything if T::WithBridgeHubChannel::is_congested() { - return Weight::zero() // TODO: benchmarks + return T::WeightInfo::on_initialize_when_congested() } DeliveryFeeFactor::::mutate(|f| { @@ -101,11 +108,15 @@ pub mod pallet { previous_factor, f, ); - } - *f - }); - Weight::zero() // TODO: benchmarks + T::WeightInfo::on_initialize_when_non_congested() + } else { + // we have not actually updated the `DeliveryFeeFactor`, so we may deduct + // single db write from maximal weight + T::WeightInfo::on_initialize_when_non_congested() + .saturating_sub(T::DbWeight::get().writes(1)) + } + }) } } diff --git a/modules/xcm-bridge-hub-router/src/mock.rs b/modules/xcm-bridge-hub-router/src/mock.rs index 2c64f0984d..320278609f 100644 --- a/modules/xcm-bridge-hub-router/src/mock.rs +++ b/modules/xcm-bridge-hub-router/src/mock.rs @@ -80,6 +80,8 @@ impl frame_system::Config for TestRuntime { } impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { + type WeightInfo = (); + type UniversalLocation = UniversalLocation; type SiblingBridgeHubLocation = SiblingBridgeHubLocation; type BridgedNetworkId = BridgedNetworkId; diff --git a/modules/xcm-bridge-hub-router/src/weights.rs b/modules/xcm-bridge-hub-router/src/weights.rs new file mode 100644 index 0000000000..497ab9e4f1 --- /dev/null +++ b/modules/xcm-bridge-hub-router/src/weights.rs @@ -0,0 +1,128 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! 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-07-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/millau-bridge-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_xcm_bridge_hub_router +// --extrinsic=* +// --execution=wasm +// --wasm-execution=Compiled +// --heap-pages=4096 +// --output=./modules/xcm-bridge-hub-router/src/weights.rs +// --template=./.maintain/bridge-weight-template.hbs + +#![allow(clippy::all)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_xcm_bridge_hub_router. +pub trait WeightInfo { + fn on_initialize_when_non_congested() -> Weight; + fn on_initialize_when_congested() -> Weight; +} + +/// Weights for `pallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets. +/// +/// Those weights are test only and must never be used in production. +pub struct BridgeWeight(PhantomData); +impl WeightInfo for BridgeWeight { + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + /// + /// Storage: `XcmBridgeHubRouter::DeliveryFeeFactor` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), + /// added: 511, mode: `MaxEncodedLen`) + fn on_initialize_when_non_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3517` + // Minimum execution time: 11_141 nanoseconds. + Weight::from_parts(11_339_000, 3517) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn on_initialize_when_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `82` + // Estimated: `3547` + // Minimum execution time: 4_239 nanoseconds. + Weight::from_parts(4_383_000, 3547).saturating_add(T::DbWeight::get().reads(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + /// + /// Storage: `XcmBridgeHubRouter::DeliveryFeeFactor` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), + /// added: 511, mode: `MaxEncodedLen`) + fn on_initialize_when_non_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3517` + // Minimum execution time: 11_141 nanoseconds. + Weight::from_parts(11_339_000, 3517) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn on_initialize_when_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `82` + // Estimated: `3547` + // Minimum execution time: 4_239 nanoseconds. + Weight::from_parts(4_383_000, 3547).saturating_add(RocksDbWeight::get().reads(1_u64)) + } +} diff --git a/scripts/update-weights.sh b/scripts/update-weights.sh index b5808042f4..fbbbf0790a 100755 --- a/scripts/update-weights.sh +++ b/scripts/update-weights.sh @@ -56,6 +56,18 @@ time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- --output=./modules/relayers/src/weights.rs \ --template=./.maintain/bridge-weight-template.hbs +time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet \ + --chain=dev \ + --steps=50 \ + --repeat=20 \ + --pallet=pallet_xcm_bridge_hub_router \ + --extrinsic=* \ + --execution=wasm \ + --wasm-execution=Compiled \ + --heap-pages=4096 \ + --output=./modules/xcm_bridge_hub_router/src/weights.rs \ + --template=./.maintain/bridge-weight-template.hbs + # weights for Millau runtime. We want to provide runtime weight overhead for messages calls, # so we can't use "default" test weights directly - they'll be rejected by our integration tests. From ed72ebe62b0ca20a51535276d69a2dc8df43a6eb Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 28 Jul 2023 11:44:08 +0300 Subject: [PATCH 16/31] get rid of redundant storage value --- bin/millau/runtime/src/lib.rs | 2 - bin/millau/runtime/src/rialto_messages.rs | 13 +- .../runtime/src/rialto_parachain_messages.rs | 13 +- bin/rialto-parachain/runtime/src/lib.rs | 1 - .../runtime/src/millau_messages.rs | 13 +- bin/rialto/runtime/src/lib.rs | 1 - bin/rialto/runtime/src/millau_messages.rs | 13 +- .../src/messages_xcm_extension.rs | 315 +++++++----------- bin/runtime-common/src/mock.rs | 1 - modules/messages/src/lib.rs | 32 +- modules/messages/src/mock.rs | 23 +- modules/messages/src/outbound_lane.rs | 9 +- primitives/messages/src/lib.rs | 8 + primitives/messages/src/source_chain.rs | 12 - 14 files changed, 166 insertions(+), 290 deletions(-) diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index bec6121455..56c2e7fefc 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -464,7 +464,6 @@ impl pallet_bridge_messages::Config for Runtime { WithRialtoMessagesInstance, frame_support::traits::ConstU64<100_000>, >; - type OnMessagesDelivered = (); type SourceHeaderChain = crate::rialto_messages::RialtoAsSourceHeaderChain; type MessageDispatch = crate::rialto_messages::FromRialtoMessageDispatch; @@ -496,7 +495,6 @@ impl pallet_bridge_messages::Config for Run WithRialtoParachainMessagesInstance, frame_support::traits::ConstU64<100_000>, >; - type OnMessagesDelivered = (); type SourceHeaderChain = crate::rialto_parachain_messages::RialtoParachainAsSourceHeaderChain; type MessageDispatch = crate::rialto_parachain_messages::FromRialtoParachainMessageDispatch; diff --git a/bin/millau/runtime/src/rialto_messages.rs b/bin/millau/runtime/src/rialto_messages.rs index b6b64d73f9..aa7a6a9d4b 100644 --- a/bin/millau/runtime/src/rialto_messages.rs +++ b/bin/millau/runtime/src/rialto_messages.rs @@ -23,7 +23,7 @@ use bridge_runtime_common::{ messages::{ self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge, }, - messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, + messages_xcm_extension::{SenderAndLane, XcmBlobHauler, XcmBlobHaulerAdapter}, }; use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; use pallet_bridge_relayers::WeightInfoExt as _; @@ -43,6 +43,8 @@ parameter_types! { /// 2 XCM instructions is for simple `Trap(42)` program, coming through bridge /// (it is prepended with `UniversalOrigin` instruction). pub const WeightCredit: Weight = BASE_XCM_WEIGHT_TWICE; + /// Lane used by the with-Rialto bridge. + pub RialtoSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), XCM_LANE); } /// Message payload for Millau -> Rialto messages. @@ -126,19 +128,12 @@ pub struct ToRialtoXcmBlobHauler; impl XcmBlobHauler for ToRialtoXcmBlobHauler { type MessageSender = pallet_bridge_messages::Pallet; type MessageSenderOrigin = RuntimeOrigin; + type SenderAndLane = RialtoSenderAndLane; fn message_sender_origin() -> RuntimeOrigin { pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get())) .into() } - - fn sending_chain_location() -> MultiLocation { - Here.into() - } - - fn xcm_lane() -> LaneId { - XCM_LANE - } } impl pallet_bridge_messages::WeightInfoExt for crate::weights::RialtoMessagesWeightInfo { diff --git a/bin/millau/runtime/src/rialto_parachain_messages.rs b/bin/millau/runtime/src/rialto_parachain_messages.rs index 57aafe5913..81193bc94e 100644 --- a/bin/millau/runtime/src/rialto_parachain_messages.rs +++ b/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -25,7 +25,7 @@ use bridge_runtime_common::{ messages::{ self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge, }, - messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, + messages_xcm_extension::{SenderAndLane, XcmBlobHauler, XcmBlobHaulerAdapter}, }; use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; use pallet_bridge_relayers::WeightInfoExt as _; @@ -45,6 +45,8 @@ parameter_types! { /// 2 XCM instructions is for simple `Trap(42)` program, coming through bridge /// (it is prepended with `UniversalOrigin` instruction). pub const WeightCredit: Weight = BASE_XCM_WEIGHT_TWICE; + /// Lane used by the with-RialtoParachain bridge. + pub RialtoParachainSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), XCM_LANE); } /// Message payload for Millau -> RialtoParachain messages. @@ -127,19 +129,12 @@ impl XcmBlobHauler for ToRialtoParachainXcmBlobHauler { type MessageSender = pallet_bridge_messages::Pallet; type MessageSenderOrigin = RuntimeOrigin; + type SenderAndLane = RialtoParachainSenderAndLane; fn message_sender_origin() -> RuntimeOrigin { pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get())) .into() } - - fn sending_chain_location() -> MultiLocation { - Here.into() - } - - fn xcm_lane() -> LaneId { - XCM_LANE - } } impl pallet_bridge_messages::WeightInfoExt diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs index 70810cc534..18c915819d 100644 --- a/bin/rialto-parachain/runtime/src/lib.rs +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -584,7 +584,6 @@ impl pallet_bridge_messages::Config for Runtime { WithMillauMessagesInstance, frame_support::traits::ConstU128<100_000>, >; - type OnMessagesDelivered = (); type SourceHeaderChain = crate::millau_messages::MillauAsSourceHeaderChain; type MessageDispatch = crate::millau_messages::FromMillauMessageDispatch; diff --git a/bin/rialto-parachain/runtime/src/millau_messages.rs b/bin/rialto-parachain/runtime/src/millau_messages.rs index 2e29e1a8e2..64b6e67e83 100644 --- a/bin/rialto-parachain/runtime/src/millau_messages.rs +++ b/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -26,7 +26,7 @@ use bridge_runtime_common::{ messages::{ self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge, }, - messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, + messages_xcm_extension::{SenderAndLane, XcmBlobHauler, XcmBlobHaulerAdapter}, }; use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; use xcm::latest::prelude::*; @@ -45,6 +45,8 @@ parameter_types! { /// 2 XCM instructions is for simple `Trap(42)` program, coming through bridge /// (it is prepended with `UniversalOrigin` instruction). pub const WeightCredit: Weight = BASE_XCM_WEIGHT_TWICE; + /// Lane used by the with-Millau bridge. + pub MullauSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), XCM_LANE); } /// Message payload for RialtoParachain -> Millau messages. @@ -126,18 +128,11 @@ pub struct ToMillauXcmBlobHauler; impl XcmBlobHauler for ToMillauXcmBlobHauler { type MessageSender = pallet_bridge_messages::Pallet; type MessageSenderOrigin = RuntimeOrigin; + type SenderAndLane = MullauSenderAndLane; fn message_sender_origin() -> RuntimeOrigin { pallet_xcm::Origin::from(MultiLocation::new(1, crate::UniversalLocation::get())).into() } - - fn sending_chain_location() -> MultiLocation { - Here.into() - } - - fn xcm_lane() -> LaneId { - XCM_LANE - } } #[cfg(test)] diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs index e0cb6e9b30..1d93d24951 100644 --- a/bin/rialto/runtime/src/lib.rs +++ b/bin/rialto/runtime/src/lib.rs @@ -442,7 +442,6 @@ impl pallet_bridge_messages::Config for Runtime { WithMillauMessagesInstance, frame_support::traits::ConstU128<100_000>, >; - type OnMessagesDelivered = (); type SourceHeaderChain = crate::millau_messages::MillauAsSourceHeaderChain; type MessageDispatch = crate::millau_messages::FromMillauMessageDispatch; diff --git a/bin/rialto/runtime/src/millau_messages.rs b/bin/rialto/runtime/src/millau_messages.rs index 82a4dc7850..aeec2c02f3 100644 --- a/bin/rialto/runtime/src/millau_messages.rs +++ b/bin/rialto/runtime/src/millau_messages.rs @@ -23,7 +23,7 @@ use bridge_runtime_common::{ messages::{ self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge, }, - messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, + messages_xcm_extension::{SenderAndLane, XcmBlobHauler, XcmBlobHaulerAdapter}, }; use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; use xcm::latest::prelude::*; @@ -42,6 +42,8 @@ parameter_types! { /// 2 XCM instructions is for simple `Trap(42)` program, coming through bridge /// (it is prepended with `UniversalOrigin` instruction). pub const WeightCredit: Weight = BASE_XCM_WEIGHT_TWICE; + /// Lane used by the with-Millau bridge. + pub MullauSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), XCM_LANE); } /// Message payload for Rialto -> Millau messages. @@ -125,19 +127,12 @@ pub struct ToMillauXcmBlobHauler; impl XcmBlobHauler for ToMillauXcmBlobHauler { type MessageSender = pallet_bridge_messages::Pallet; type MessageSenderOrigin = RuntimeOrigin; + type SenderAndLane = MullauSenderAndLane; fn message_sender_origin() -> RuntimeOrigin { pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get())) .into() } - - fn sending_chain_location() -> MultiLocation { - Here.into() - } - - fn xcm_lane() -> LaneId { - XCM_LANE - } } #[cfg(test)] diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index e68a07bbb2..8f2ee3ad35 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -22,24 +22,25 @@ //! `XcmRouter` <- `MessageDispatch` <- `InboundMessageQueue` use bp_messages::{ - source_chain::{MessagesBridge, OnMessagesDelivered}, + source_chain::MessagesBridge, target_chain::{DispatchMessage, MessageDispatch}, LaneId, MessageNonce, }; -use bp_runtime::messages::MessageDispatchResult; +use bp_runtime::{messages::MessageDispatchResult, RangeInclusiveExt}; use bp_xcm_bridge_hub_router::LocalXcmChannel; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ dispatch::Weight, - traits::{ProcessMessage, ProcessMessageError, QueuePausedQuery}, + traits::{Get, ProcessMessage, ProcessMessageError, QueuePausedQuery}, weights::WeightMeter, CloneNoBound, EqNoBound, PartialEqNoBound, }; -use pallet_bridge_messages::WeightInfoExt as MessagesPalletWeights; +use pallet_bridge_messages::{ + Config as MessagesConfig, Pallet as MessagesPallet, WeightInfoExt as MessagesPalletWeights, +}; use scale_info::TypeInfo; -use sp_io::hashing::blake2_256; use sp_runtime::SaturatedConversion; -use sp_std::{boxed::Box, fmt::Debug, marker::PhantomData, vec::Vec}; +use sp_std::{fmt::Debug, marker::PhantomData}; use xcm::prelude::*; use xcm_builder::{DispatchBlob, DispatchBlobError, HaulBlob, HaulBlobError}; @@ -121,20 +122,34 @@ impl Self { + SenderAndLane { location, lane } + } +} + /// [`XcmBlobHauler`] is responsible for sending messages to the bridge "point-to-point link" from /// one side, where on the other it can be dispatched by [`XcmBlobMessageDispatch`]. pub trait XcmBlobHauler { /// Runtime message sender adapter. type MessageSender: MessagesBridge; + /// Returns lane used by this hauler. + type SenderAndLane: Get; /// Runtime message sender origin, which is used by [`Self::MessageSender`]. type MessageSenderOrigin; /// Runtime origin for our (i.e. this bridge hub) location within the Consensus Universe. fn message_sender_origin() -> Self::MessageSenderOrigin; - /// Location of the sending chain (i.e. sibling asset hub) within the Consensus universe. - fn sending_chain_location() -> MultiLocation; - /// Return message lane (as "point-to-point link") used to deliver XCM messages. - fn xcm_lane() -> LaneId; } /// XCM bridge adapter which connects [`XcmBlobHauler`] with [`XcmBlobHauler::MessageSender`] and @@ -147,19 +162,13 @@ impl> HaulBlo for XcmBlobHaulerAdapter { fn haul_blob(blob: sp_std::prelude::Vec) -> Result<(), HaulBlobError> { - let lane = H::xcm_lane(); + let lane = H::SenderAndLane::get().lane; H::MessageSender::send_message(H::message_sender_origin(), lane, blob) .map(|artifacts| { log::info!( target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "haul_blob result - ok: {:?} on lane: {:?}", + "haul_blob result - ok: {:?} on lane: {:?}. Enqueued messages: {}", artifacts.nonce, - lane - ); - - // notify XCM queue manager about updated lane state - LocalXcmQueueManager::on_bridge_message_enqueued( - Box::new(H::sending_chain_location()), lane, artifacts.enqueued_messages, ); @@ -176,35 +185,12 @@ impl> HaulBlo } } -impl> OnMessagesDelivered - for XcmBlobHaulerAdapter -{ - fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { - // notify XCM queue manager about updated lane state - LocalXcmQueueManager::on_bridge_messages_delivered( - Box::new(H::sending_chain_location()), - lane, - enqueued_messages, - ); - } -} - /// Manager of local XCM queues (and indirectly - underlying transport channels) that /// controls the queue state. /// /// It needs to be used at the source bridge hub. pub struct LocalXcmQueueManager; -/// Prefix for storage keys, written by the `LocalXcmQueueManager`. -/// -/// We don't have a separate pallet with available storage entries for managing XCM queues -/// in this (internediate) version of dynamic fees implementation. So we write to the runtime -/// storage directly with this prefix. -const LOCAL_XCM_QUEUE_MANAGER_STORAGE_PREFIX: &[u8] = b"LocalXcmQueueManager"; - -/// Name of "virtual" storage map that holds entries for every suspended queue. -const SUSPENDED_QUEUE_MAP_STORAGE_PREFIX: &[u8] = b"SuspendedQueues"; - /// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we /// stop processing XCM messages from the sending chain (asset hub) that "owns" the lane. /// @@ -213,96 +199,11 @@ const SUSPENDED_QUEUE_MAP_STORAGE_PREFIX: &[u8] = b"SuspendedQueues"; const MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE: MessageNonce = 4096; impl LocalXcmQueueManager { - /// Must be called whenever we push a message to the bridge lane. - pub fn on_bridge_message_enqueued( - sending_chain_location: Box, - lane: LaneId, - enqueued_messages: MessageNonce, - ) { - // suspend the inbound XCM queue with the sender to avoid queueing more messages - // at the outbound bridge queue AND turn on internal backpressure mechanism of the - // XCM queue - let is_congested = enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE; - if !is_congested { - return - } - - log::info!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "Suspending inbound XCM queue with {:?} to avoid congesting lane {:?}: there are\ - {} messages queued at the bridge queue", - sending_chain_location, - lane, - enqueued_messages, - ); - - Self::suspend_inbound_queue(sending_chain_location); - } - - /// Must be called whenever we receive a message delivery confirmation. - pub fn on_bridge_messages_delivered( - sending_chain_location: Box, - lane: LaneId, - enqueued_messages: MessageNonce, - ) { - // the queue before this call may be either suspended or not. If the lane is still - // congested, we win't need to do anything - let is_congested = enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE; - if is_congested { - return - } - - // else - resume the inbound queue - if !Self::is_inbound_queue_suspended(sending_chain_location.clone()) { - return - } - - log::info!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "Resuming inbound XCM queue with {:?} using lane {:?}: there are\ - {} messages queued at the bridge queue", - sending_chain_location, - lane, - enqueued_messages, - ); - - Self::resume_inbound_queue(sending_chain_location); - } - /// Returns true if XCM message queue with given location is currently suspended. - pub fn is_inbound_queue_suspended(with: Box) -> bool { - frame_support::storage::unhashed::get_or_default(&Self::suspended_queue_map_storage_key( - with, - )) - } - - // since dynamic fees for v1 is a temporary solution, we're using direct storage writes here. - // In v2, the separate pallet (`palle-xcm-bridge-hub`) will store this data. - - fn suspend_inbound_queue(with: Box) { - frame_support::storage::unhashed::put(&Self::suspended_queue_map_storage_key(with), &true); - } - - fn resume_inbound_queue(with: Box) { - frame_support::storage::unhashed::kill(&Self::suspended_queue_map_storage_key(with)); - } - - fn suspended_queue_map_storage_key(with: Box) -> Vec { - let with: VersionedMultiLocation = (*with).into(); - let with_hashed = with.using_encoded(blake2_256); - - // let's emulate real map here - it'd be easier to kill all entries later - let mut final_key = Vec::with_capacity( - LOCAL_XCM_QUEUE_MANAGER_STORAGE_PREFIX.len() + - SUSPENDED_QUEUE_MAP_STORAGE_PREFIX.len() + - with_hashed.len(), - ); - - final_key.extend_from_slice(LOCAL_XCM_QUEUE_MANAGER_STORAGE_PREFIX); - final_key.extend_from_slice(SUSPENDED_QUEUE_MAP_STORAGE_PREFIX); - final_key.extend_from_slice(&with_hashed); - - final_key + pub fn is_inbound_queue_suspended, MI: 'static>(lane: LaneId) -> bool { + let outbound_lane = MessagesPallet::::outbound_lane_data(lane); + let enqueued_messages = outbound_lane.queued_messages().checked_len().unwrap_or(0); + enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE } } @@ -311,9 +212,12 @@ impl LocalXcmQueueManager { /// bridge queue is congested. /// /// It needs to be used at the source bridge hub. -pub struct LocalXcmQueueMessageProcessor(PhantomData<(Origin, Inner)>); +pub struct LocalXcmQueueMessageProcessor( + PhantomData<(Origin, Inner, R, MI, SL)>, +); -impl ProcessMessage for LocalXcmQueueMessageProcessor +impl ProcessMessage + for LocalXcmQueueMessageProcessor where Origin: Clone + Into @@ -325,6 +229,9 @@ where + TypeInfo + Debug, Inner: ProcessMessage, + R: MessagesConfig, + MI: 'static, + SL: Get, { type Origin = Origin; @@ -335,8 +242,11 @@ where id: &mut [u8; 32], ) -> Result { // if the queue is suspended, yield immediately - if LocalXcmQueueManager::is_inbound_queue_suspended(Box::new(origin.clone().into())) { - return Err(ProcessMessageError::Yield) + let sender_and_lane = SL::get(); + if origin.clone().into() == sender_and_lane.location { + if LocalXcmQueueManager::is_inbound_queue_suspended::(sender_and_lane.lane) { + return Err(ProcessMessageError::Yield) + } } // else pass message to backed processor @@ -349,12 +259,18 @@ where /// bridge queue is congested. /// /// It needs to be used at the source bridge hub. -pub struct LocalXcmQueueSuspender(PhantomData<(Origin, Inner)>); +pub struct LocalXcmQueueSuspender( + PhantomData<(Origin, Inner, R, MI, SL)>, +); -impl QueuePausedQuery for LocalXcmQueueSuspender +impl QueuePausedQuery + for LocalXcmQueueSuspender where Origin: Clone + Into, Inner: QueuePausedQuery, + R: MessagesConfig, + MI: 'static, + SL: Get, { fn is_paused(origin: &Origin) -> bool { // give priority to inner status @@ -363,8 +279,11 @@ where } // if we have suspended the queue before, do not even start processing its messages - if LocalXcmQueueManager::is_inbound_queue_suspended(Box::new(origin.clone().into())) { - return true + let sender_and_lane = SL::get(); + if origin.clone().into() == sender_and_lane.location { + if LocalXcmQueueManager::is_inbound_queue_suspended::(sender_and_lane.lane) { + return true + } } // else process message @@ -377,24 +296,30 @@ mod tests { use super::*; use crate::mock::*; + use frame_support::parameter_types; use sp_runtime::traits::{ConstBool, Get}; + parameter_types! { + pub TestSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), TEST_LANE_ID); + } + + fn test_origin_location() -> MultiLocation { + TestSenderAndLane::get().location + } + + fn test_origin() -> MultiLocation { + test_origin_location() + } + struct TestXcmBlobHauler; impl XcmBlobHauler for TestXcmBlobHauler { type MessageSender = BridgeMessages; type MessageSenderOrigin = RuntimeOrigin; + type SenderAndLane = TestSenderAndLane; fn message_sender_origin() -> Self::MessageSenderOrigin { RuntimeOrigin::root() } - - fn sending_chain_location() -> MultiLocation { - ParentThen(X1(Parachain(1000))).into() - } - - fn xcm_lane() -> LaneId { - TEST_LANE_ID - } } struct TestInnerXcmQueueMessageProcessor; @@ -421,18 +346,20 @@ mod tests { } type TestXcmBlobHaulerAdapter = XcmBlobHaulerAdapter; - type TestLocalXcmQueueMessageProcessor = - LocalXcmQueueMessageProcessor; - type TestLocalXcmQueueSuspender = - LocalXcmQueueSuspender>>; - - fn test_origin_location() -> MultiLocation { - Here.into() - } - - fn test_origin() -> MultiLocation { - test_origin_location() - } + type TestLocalXcmQueueMessageProcessor = LocalXcmQueueMessageProcessor< + MultiLocation, + TestInnerXcmQueueMessageProcessor, + TestRuntime, + (), + TestSenderAndLane, + >; + type TestLocalXcmQueueSuspender = LocalXcmQueueSuspender< + MultiLocation, + TestInnerXcmQueueSuspender>, + TestRuntime, + (), + TestSenderAndLane, + >; #[test] fn inbound_xcm_queue_with_sending_chain_is_managed_by_blob_hauler() { @@ -441,45 +368,27 @@ mod tests { // queue, the inbound channel with the sending chain stays opened for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE { TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); - assert!(!LocalXcmQueueManager::is_inbound_queue_suspended(Box::new( - TestXcmBlobHauler::sending_chain_location() - ))); + assert!(!LocalXcmQueueManager::is_inbound_queue_suspended::( + TEST_LANE_ID + )); } // then when we enqueue more messages, we suspend inbound queue. Note that messages // are not dropped - they're enqueued at the bridge queue TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); - assert!(LocalXcmQueueManager::is_inbound_queue_suspended(Box::new( - TestXcmBlobHauler::sending_chain_location() - ))); - - // okay - let's now emulate delivery confirmation. If we still have more than - // `MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE` in the bridge queue, the inbound - // channel stays suspended - TestXcmBlobHaulerAdapter::on_messages_delivered( - TEST_LANE_ID, - MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + 1, - ); - assert!(LocalXcmQueueManager::is_inbound_queue_suspended(Box::new( - TestXcmBlobHauler::sending_chain_location() - ))); - - // and when there's lte than `MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE` messages in the - // bridge queue, we resume the inbound channel - TestXcmBlobHaulerAdapter::on_messages_delivered( - TEST_LANE_ID, - MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE, - ); - assert!(!LocalXcmQueueManager::is_inbound_queue_suspended(Box::new( - TestXcmBlobHauler::sending_chain_location() - ))); + assert!(LocalXcmQueueManager::is_inbound_queue_suspended::( + TEST_LANE_ID + )); }); } #[test] fn inbound_xcm_message_from_sibling_is_not_processed_when_bridge_queue_is_congested() { run_test(|| { - LocalXcmQueueManager::suspend_inbound_queue(Box::new(test_origin_location())); + for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + 1 { + TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + } + assert_eq!( TestLocalXcmQueueMessageProcessor::process_message( &[42], @@ -492,6 +401,25 @@ mod tests { }) } + #[test] + fn inbound_xcm_message_from_other_origin_is_processed_normally() { + run_test(|| { + for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + 1 { + TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + } + + assert_eq!( + TestLocalXcmQueueMessageProcessor::process_message( + &[42], + ParentThen(X1(Parachain(1000))).into(), + &mut WeightMeter::max_limit(), + &mut [0u8; 32], + ), + Ok(true), + ); + }) + } + #[test] fn inbound_xcm_message_from_sibling_is_processed_normally() { run_test(|| { @@ -513,6 +441,9 @@ mod tests { assert!(LocalXcmQueueSuspender::< MultiLocation, TestInnerXcmQueueSuspender>, + TestRuntime, + (), + TestSenderAndLane, >::is_paused(&test_origin())) }) } @@ -520,11 +451,21 @@ mod tests { #[test] fn local_xcm_queue_is_paused_when_bridge_queue_is_congested() { run_test(|| { - LocalXcmQueueManager::suspend_inbound_queue(Box::new(test_origin_location())); + for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + 1 { + TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + } + assert!(TestLocalXcmQueueSuspender::is_paused(&test_origin())) }); } + #[test] + fn local_xcm_queue_with_other_origin_is_not_paused() { + run_test(|| { + assert!(!TestLocalXcmQueueSuspender::is_paused(&ParentThen(X1(Parachain(1000))).into())) + }); + } + #[test] fn local_xcm_queue_is_not_paused_normally() { run_test(|| assert!(!TestLocalXcmQueueSuspender::is_paused(&test_origin()))); diff --git a/bin/runtime-common/src/mock.rs b/bin/runtime-common/src/mock.rs index 9c41d17fa9..8a2aae1e06 100644 --- a/bin/runtime-common/src/mock.rs +++ b/bin/runtime-common/src/mock.rs @@ -250,7 +250,6 @@ impl pallet_bridge_messages::Config for TestRuntime { (), ConstU64<100_000>, >; - type OnMessagesDelivered = (); type SourceHeaderChain = SourceHeaderChainAdapter; type MessageDispatch = DummyMessageDispatch; diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 932d16c205..554b5975a4 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -53,8 +53,7 @@ use crate::{ use bp_messages::{ source_chain::{ - DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, - SendMessageArtifacts, TargetHeaderChain, + DeliveryConfirmationPayments, LaneMessageVerifier, SendMessageArtifacts, TargetHeaderChain, }, target_chain::{ DeliveryPayments, DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, @@ -159,8 +158,6 @@ pub mod pallet { type LaneMessageVerifier: LaneMessageVerifier; /// Delivery confirmation payments. type DeliveryConfirmationPayments: DeliveryConfirmationPayments; - /// Delivery confirmation callback. - type OnMessagesDelivered: OnMessagesDelivered; // Types that are used by inbound_lane (on target chain). @@ -495,12 +492,6 @@ pub mod pallet { lane_id, ); - // notify others about messages delivery - T::OnMessagesDelivered::on_messages_delivered( - lane_id, - lane.queued_messages().checked_len().unwrap_or(0), - ); - // because of lags, the inbound lane state (`lane_data`) may have entries for // already rewarded relayers and messages (if all entries are duplicated, then // this transaction must be filtered out by our signed extension) @@ -643,6 +634,11 @@ pub mod pallet { } } + /// Return outbound lane data. + pub fn outbound_lane_data(lane: LaneId) -> OutboundLaneData { + OutboundLanes::::get(lane) + } + /// Return inbound lane data. pub fn inbound_lane_data(lane: LaneId) -> InboundLaneData { InboundLanes::::get(lane).0 @@ -725,7 +721,7 @@ fn send_message, I: 'static>( .map_err(Error::::MessageRejectedByPallet)?; // return number of messages in the queue to let sender know about its state - let enqueued_messages = lane.queued_messages().checked_len().unwrap_or(0); + let enqueued_messages = lane.data().queued_messages().checked_len().unwrap_or(0); log::trace!( target: LOG_TARGET, @@ -906,10 +902,9 @@ mod tests { inbound_unrewarded_relayers_state, message, message_payload, run_test, unrewarded_relayer, AccountId, DbWeight, RuntimeEvent as TestEvent, RuntimeOrigin, TestDeliveryConfirmationPayments, TestDeliveryPayments, TestMessageDispatch, - TestMessagesDeliveryProof, TestMessagesProof, TestOnMessagesDelivered, TestRelayer, - TestRuntime, TestWeightInfo, MAX_OUTBOUND_PAYLOAD_SIZE, - PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_LANE_ID_2, - TEST_LANE_ID_3, TEST_RELAYER_A, TEST_RELAYER_B, + TestMessagesDeliveryProof, TestMessagesProof, TestRelayer, TestRuntime, TestWeightInfo, + MAX_OUTBOUND_PAYLOAD_SIZE, PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, + TEST_LANE_ID, TEST_LANE_ID_2, TEST_LANE_ID_3, TEST_RELAYER_A, TEST_RELAYER_B, }, outbound_lane::ReceivalConfirmationError, }; @@ -935,7 +930,8 @@ mod tests { let outbound_lane = outbound_lane::(TEST_LANE_ID); let message_nonce = outbound_lane.data().latest_generated_nonce + 1; - let prev_enqueud_messages = outbound_lane.queued_messages().checked_len().unwrap_or(0); + let prev_enqueud_messages = + outbound_lane.data().queued_messages().checked_len().unwrap_or(0); let artifacts = send_message::( RuntimeOrigin::signed(1), TEST_LANE_ID, @@ -984,8 +980,6 @@ mod tests { }, )); - assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0))); - assert_eq!( System::::events(), vec![EventRecord { @@ -1382,7 +1376,6 @@ mod tests { ); assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); - assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 1))); // this reports delivery of both message 1 and message 2 => reward is paid only to // TEST_RELAYER_B @@ -1425,7 +1418,6 @@ mod tests { ); assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); - assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0))); }); } diff --git a/modules/messages/src/mock.rs b/modules/messages/src/mock.rs index aac83998bb..8d93c66b4b 100644 --- a/modules/messages/src/mock.rs +++ b/modules/messages/src/mock.rs @@ -21,9 +21,7 @@ use crate::Config; use bp_messages::{ calc_relayers_rewards, - source_chain::{ - DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, TargetHeaderChain, - }, + source_chain::{DeliveryConfirmationPayments, LaneMessageVerifier, TargetHeaderChain}, target_chain::{ DeliveryPayments, DispatchMessage, DispatchMessageData, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, @@ -163,7 +161,6 @@ impl Config for TestRuntime { type TargetHeaderChain = TestTargetHeaderChain; type LaneMessageVerifier = TestLaneMessageVerifier; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; - type OnMessagesDelivered = TestOnMessagesDelivered; type SourceHeaderChain = TestSourceHeaderChain; type MessageDispatch = TestMessageDispatch; @@ -443,24 +440,6 @@ impl MessageDispatch for TestMessageDispatch { } } -/// Test callback, called during message delivery confirmation transaction. -pub struct TestOnMessagesDelivered; - -impl TestOnMessagesDelivered { - pub fn call_arguments() -> Option<(LaneId, MessageNonce)> { - frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered") - } -} - -impl OnMessagesDelivered for TestOnMessagesDelivered { - fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { - frame_support::storage::unhashed::put( - b"TestOnMessagesDelivered.OnMessagesDelivered", - &(lane, enqueued_messages), - ); - } -} - /// Return test lane message with given nonce and payload. pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { Message { key: MessageKey { lane_id: TEST_LANE_ID, nonce }, payload: payload.encode() } diff --git a/modules/messages/src/outbound_lane.rs b/modules/messages/src/outbound_lane.rs index 1cf4aae6ef..0cf0c4ccb4 100644 --- a/modules/messages/src/outbound_lane.rs +++ b/modules/messages/src/outbound_lane.rs @@ -29,7 +29,7 @@ use frame_support::{ }; use num_traits::Zero; use scale_info::TypeInfo; -use sp_std::{collections::vec_deque::VecDeque, ops::RangeInclusive}; +use sp_std::collections::vec_deque::VecDeque; /// Outbound lane storage. pub trait OutboundLaneStorage { @@ -87,13 +87,6 @@ impl OutboundLane { self.storage.data() } - /// Return nonces of all currently queued messages (i.e. messages that we believe - /// are not delivered yet). - pub fn queued_messages(&self) -> RangeInclusive { - let data = self.storage.data(); - (data.latest_received_nonce + 1)..=data.latest_generated_nonce - } - /// Send message over lane. /// /// Returns new message nonce. diff --git a/primitives/messages/src/lib.rs b/primitives/messages/src/lib.rs index cb3a14572d..84c41de3b3 100644 --- a/primitives/messages/src/lib.rs +++ b/primitives/messages/src/lib.rs @@ -387,6 +387,14 @@ impl Default for OutboundLaneData { } } +impl OutboundLaneData { + /// Return nonces of all currently queued messages (i.e. messages that we believe + /// are not delivered yet). + pub fn queued_messages(&self) -> RangeInclusive { + (self.latest_received_nonce + 1)..=self.latest_generated_nonce + } +} + /// Calculate the number of messages that the relayers have delivered. pub fn calc_relayers_rewards( messages_relayers: VecDeque>, diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs index bf0964d89a..cf2ac3a3df 100644 --- a/primitives/messages/src/source_chain.rs +++ b/primitives/messages/src/source_chain.rs @@ -116,18 +116,6 @@ impl DeliveryConfirmationPayments for () { } } -/// Callback that is called at the source chain (bridge hub) when we get new delivery confrimation. -pub trait OnMessagesDelivered { - /// Messages delivery has been confimed. - /// - /// The only argument of the function is the number of yet undelivered messages. - fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce); -} - -impl OnMessagesDelivered for () { - fn on_messages_delivered(_lane: LaneId, _enqueued_messages: MessageNonce) {} -} - /// Send message artifacts. #[derive(Eq, RuntimeDebug, PartialEq)] pub struct SendMessageArtifacts { From c467911a37ac2f8aa47b3e4f11bff2d843cfece5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 28 Jul 2023 12:54:09 +0300 Subject: [PATCH 17/31] add new pallet to verify-pallets-build.sh --- scripts/verify-pallets-build.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/verify-pallets-build.sh b/scripts/verify-pallets-build.sh index 2230d68a9c..1ea3dbe862 100755 --- a/scripts/verify-pallets-build.sh +++ b/scripts/verify-pallets-build.sh @@ -125,6 +125,9 @@ cargo check -p pallet-bridge-parachains --features try-runtime cargo check -p pallet-bridge-relayers cargo check -p pallet-bridge-relayers --features runtime-benchmarks cargo check -p pallet-bridge-relayers --features try-runtime +cargo check -p pallet-xcm-bridge-hub-router +cargo check -p pallet-xcm-bridge-hub-router --features runtime-benchmarks +cargo check -p pallet-xcm-bridge-hub-router --features try-runtime cargo check -p bridge-runtime-common cargo check -p bridge-runtime-common --features runtime-benchmarks cargo check -p bridge-runtime-common --features integrity-test From b26aa98d1ea64583a3498a27a1e29efae237f38f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 28 Jul 2023 12:57:34 +0300 Subject: [PATCH 18/31] fixing spellcheck, clippy and rustdoc --- .../src/messages_xcm_extension.rs | 22 ++++++++++--------- modules/xcm-bridge-hub-router/src/lib.rs | 7 ++---- modules/xcm-bridge-hub-router/src/mock.rs | 2 +- primitives/messages/src/target_chain.rs | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 8f2ee3ad35..eb2e206f32 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -207,7 +207,7 @@ impl LocalXcmQueueManager { } } -/// A structure that implements [`frame_support:traits::messages::ProcessMessage`] and may +/// A structure that implements [`frame_support::traits::ProcessMessage`] and may /// be used in the `pallet-message-queue` configuration to stop processing messages when the /// bridge queue is congested. /// @@ -243,10 +243,11 @@ where ) -> Result { // if the queue is suspended, yield immediately let sender_and_lane = SL::get(); - if origin.clone().into() == sender_and_lane.location { - if LocalXcmQueueManager::is_inbound_queue_suspended::(sender_and_lane.lane) { - return Err(ProcessMessageError::Yield) - } + let is_expected_origin = origin.clone().into() == sender_and_lane.location; + if is_expected_origin && + LocalXcmQueueManager::is_inbound_queue_suspended::(sender_and_lane.lane) + { + return Err(ProcessMessageError::Yield) } // else pass message to backed processor @@ -254,7 +255,7 @@ where } } -/// A structure that implements [`frame_support:traits::messages::QueuePausedQuery`] and may +/// A structure that implements [`frame_support::traits::QueuePausedQuery`] and may /// be used in the `pallet-message-queue` configuration to stop processing messages when the /// bridge queue is congested. /// @@ -280,10 +281,11 @@ where // if we have suspended the queue before, do not even start processing its messages let sender_and_lane = SL::get(); - if origin.clone().into() == sender_and_lane.location { - if LocalXcmQueueManager::is_inbound_queue_suspended::(sender_and_lane.lane) { - return true - } + let is_expected_origin = origin.clone().into() == sender_and_lane.location; + if is_expected_origin && + LocalXcmQueueManager::is_inbound_queue_suspended::(sender_and_lane.lane) + { + return true } // else process message diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index a9177412a2..82c9c21071 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -73,7 +73,7 @@ pub mod pallet { /// The bridged network that this config is for. type BridgedNetworkId: Get; - /// Actual message sender (XCMP/DMP) to the sibling bridge hub location. + /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. type ToBridgeHubSender: SendXcm; /// Underlying channel with the sibling bridge hub. It must match the channel, used /// by the `Self::ToBridgeHubSender`. @@ -343,10 +343,7 @@ mod tests { .into_inner() / FixedU128::DIV + HRMP_FEE; assert_eq!( - XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm.clone())) - .unwrap() - .1 - .get(0), + XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm)).unwrap().1.get(0), Some(&(BridgeFeeAsset::get(), expected_fee).into()), ); }); diff --git a/modules/xcm-bridge-hub-router/src/mock.rs b/modules/xcm-bridge-hub-router/src/mock.rs index 320278609f..b585ee76ba 100644 --- a/modules/xcm-bridge-hub-router/src/mock.rs +++ b/modules/xcm-bridge-hub-router/src/mock.rs @@ -114,7 +114,7 @@ impl SendXcm for TestToBridgeHubSender { fn deliver(_ticket: Self::Ticket) -> Result { frame_support::storage::unhashed::put(b"TestToBridgeHubSender.Sent", &true); - Ok([0u8; 32].into()) + Ok([0u8; 32]) } } diff --git a/primitives/messages/src/target_chain.rs b/primitives/messages/src/target_chain.rs index 0c824664a0..60f5c1b3c1 100644 --- a/primitives/messages/src/target_chain.rs +++ b/primitives/messages/src/target_chain.rs @@ -96,7 +96,7 @@ pub trait MessageDispatch { /// simply drop messages if it returns `false`. The consumer may still call the `dispatch` /// if dispatcher has returned `false`. /// - /// We check it in the messages delivery transaction prolgoue. So if it becomes `false` + /// We check it in the messages delivery transaction prologue. So if it becomes `false` /// after some portion of messages is already dispatched, it doesn't fail the whole transaction. fn is_active() -> bool; From 48f1ba032334e3c6d8470436483736988aa060ac Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 28 Jul 2023 13:00:44 +0300 Subject: [PATCH 19/31] trigger CI --- modules/xcm-bridge-hub-router/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 82c9c21071..82bcb5aa29 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -72,7 +72,7 @@ pub mod pallet { type SiblingBridgeHubLocation: Get; /// The bridged network that this config is for. type BridgedNetworkId: Get; - +trigger CI /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. type ToBridgeHubSender: SendXcm; /// Underlying channel with the sibling bridge hub. It must match the channel, used From 773f93209f884bd1f361f716b55647c23c46be7e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 28 Jul 2023 13:00:52 +0300 Subject: [PATCH 20/31] Revert "trigger CI" This reverts commit 48f1ba032334e3c6d8470436483736988aa060ac. --- modules/xcm-bridge-hub-router/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 82bcb5aa29..82c9c21071 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -72,7 +72,7 @@ pub mod pallet { type SiblingBridgeHubLocation: Get; /// The bridged network that this config is for. type BridgedNetworkId: Get; -trigger CI + /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. type ToBridgeHubSender: SendXcm; /// Underlying channel with the sibling bridge hub. It must match the channel, used From 8d7a38a40953b61d9557aeffcae6048cfe032ae0 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 31 Jul 2023 13:27:29 +0300 Subject: [PATCH 21/31] change log target for xcm bridge router pallet --- modules/xcm-bridge-hub-router/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 82c9c21071..960d7eb8f0 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -53,7 +53,12 @@ const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0 pub const HARD_MESSAGE_SIZE_LIMIT: u32 = 32 * 1024; /// The target that will be used when publishing logs related to this pallet. -pub const LOG_TARGET: &str = "runtime::bridge-hub-router"; +/// +/// This doesn't match the pattern used by other bridge pallets (`runtime::bridge-*`). But this +/// pallet has significant differences with those pallets. The main one is that is intended to +/// be deployed at sending chains. Other bridge pallets are likely to be deployed at the separate +/// bridge hub parachain. +pub const LOG_TARGET: &str = "xcm::bridge-hub-router"; #[frame_support::pallet] pub mod pallet { From 7cc147052856de1b474284e8d9a7a749cbd62186 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 2 Aug 2023 09:19:47 +0300 Subject: [PATCH 22/31] Update modules/xcm-bridge-hub-router/src/lib.rs Co-authored-by: Branislav Kontur --- modules/xcm-bridge-hub-router/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 960d7eb8f0..eae89c6be9 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -243,7 +243,7 @@ impl, I: 'static> SendXcm for Pallet { // use router to enqueue message to the sibling/child bridge hub. This also should handle // payment for passing through this queue. let (message_size, ticket) = ticket; - let xcm_hash = T::ToBridgeHubSender::deliver(ticket)?; + let xcm_hash = ViaBridgeHubExporter::::deliver(ticket)?; // increase delivery fee factor if required Self::on_message_sent_to_bridge(message_size); From 5d76f25311b0d9da0aa5a9b55d8f4518e9eb8d64 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 2 Aug 2023 10:48:23 +0300 Subject: [PATCH 23/31] use saturated_len where possible --- bin/runtime-common/src/messages_xcm_extension.rs | 2 +- bin/runtime-common/src/refund_relayer_extension.rs | 2 +- modules/messages/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index eb2e206f32..f8ade35e85 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -202,7 +202,7 @@ impl LocalXcmQueueManager { /// Returns true if XCM message queue with given location is currently suspended. pub fn is_inbound_queue_suspended, MI: 'static>(lane: LaneId) -> bool { let outbound_lane = MessagesPallet::::outbound_lane_data(lane); - let enqueued_messages = outbound_lane.queued_messages().checked_len().unwrap_or(0); + let enqueued_messages = outbound_lane.queued_messages().saturating_len(); enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE } } diff --git a/bin/runtime-common/src/refund_relayer_extension.rs b/bin/runtime-common/src/refund_relayer_extension.rs index c541983731..f177bb2907 100644 --- a/bin/runtime-common/src/refund_relayer_extension.rs +++ b/bin/runtime-common/src/refund_relayer_extension.rs @@ -495,7 +495,7 @@ where // compute total number of messages in transaction let bundled_messages = - parsed_call.messages_call_info().bundled_messages().checked_len().unwrap_or(0); + parsed_call.messages_call_info().bundled_messages().saturating_len(); // a quick check to avoid invalid high-priority transactions if bundled_messages > Runtime::MaxUnconfirmedMessagesAtInboundLane::get() { diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 554b5975a4..b5e7e78b19 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -721,7 +721,7 @@ fn send_message, I: 'static>( .map_err(Error::::MessageRejectedByPallet)?; // return number of messages in the queue to let sender know about its state - let enqueued_messages = lane.data().queued_messages().checked_len().unwrap_or(0); + let enqueued_messages = lane.data().queued_messages().saturating_len(); log::trace!( target: LOG_TARGET, @@ -931,7 +931,7 @@ mod tests { let outbound_lane = outbound_lane::(TEST_LANE_ID); let message_nonce = outbound_lane.data().latest_generated_nonce + 1; let prev_enqueud_messages = - outbound_lane.data().queued_messages().checked_len().unwrap_or(0); + outbound_lane.data().queued_messages().saturating_len(); let artifacts = send_message::( RuntimeOrigin::signed(1), TEST_LANE_ID, From c68467beff115033c88113a9d953673e57b07295 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 2 Aug 2023 10:52:43 +0300 Subject: [PATCH 24/31] fmt --- bin/runtime-common/src/refund_relayer_extension.rs | 3 +-- modules/messages/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bin/runtime-common/src/refund_relayer_extension.rs b/bin/runtime-common/src/refund_relayer_extension.rs index f177bb2907..f611686420 100644 --- a/bin/runtime-common/src/refund_relayer_extension.rs +++ b/bin/runtime-common/src/refund_relayer_extension.rs @@ -494,8 +494,7 @@ where }; // compute total number of messages in transaction - let bundled_messages = - parsed_call.messages_call_info().bundled_messages().saturating_len(); + let bundled_messages = parsed_call.messages_call_info().bundled_messages().saturating_len(); // a quick check to avoid invalid high-priority transactions if bundled_messages > Runtime::MaxUnconfirmedMessagesAtInboundLane::get() { diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index b5e7e78b19..2ffc253563 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -930,8 +930,7 @@ mod tests { let outbound_lane = outbound_lane::(TEST_LANE_ID); let message_nonce = outbound_lane.data().latest_generated_nonce + 1; - let prev_enqueud_messages = - outbound_lane.data().queued_messages().saturating_len(); + let prev_enqueud_messages = outbound_lane.data().queued_messages().saturating_len(); let artifacts = send_message::( RuntimeOrigin::signed(1), TEST_LANE_ID, From e7cab6ab49cfe7da4e22d669b1ab82b2c7682c46 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 3 Aug 2023 06:45:45 +0200 Subject: [PATCH 25/31] (Suggestion) Ability to externalize configuration for `ExporterFor` (#2313) * Ability to externalize configuration for `ExporterFor` (Replaced `BridgedNetworkId/SiblingBridgeHubLocation` with `Bridges: ExporterFor`) * Fix millau * Compile fix * Return back `BridgedNetworkId` but as optional filter * Replaced `BaseFee` with fees from inner `Bridges: ExporterFor` * typo --- bin/millau/runtime/src/lib.rs | 8 ++- modules/xcm-bridge-hub-router/src/lib.rs | 75 ++++++++++++++++++----- modules/xcm-bridge-hub-router/src/mock.rs | 6 +- 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index 56c2e7fefc..9fe25bf15f 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -55,6 +55,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; +use xcm_builder::NetworkExportTable; // to be able to use Millau runtime in `bridge-runtime-common` tests pub use bridge_runtime_common; @@ -546,17 +547,20 @@ impl pallet_utility::Config for Runtime { // this config is totally incorrect - the pallet is not actually used at this runtime. We need // it only to be able to run benchmarks and make required traits (and default weights for tests). +parameter_types! { + pub BridgeTable: Vec<(xcm::prelude::NetworkId, xcm::prelude::MultiLocation, Option)> + = vec![(xcm_config::RialtoNetwork::get(), xcm_config::TokenLocation::get(), Some((xcm_config::TokenAssetId::get(), 1_000_000_000_u128).into()))]; +} impl pallet_xcm_bridge_hub_router::Config for Runtime { type WeightInfo = (); type UniversalLocation = xcm_config::UniversalLocation; - type SiblingBridgeHubLocation = xcm_config::TokenLocation; type BridgedNetworkId = xcm_config::RialtoNetwork; + type Bridges = NetworkExportTable; type ToBridgeHubSender = xcm_config::XcmRouter; type WithBridgeHubChannel = xcm_config::EmulatedSiblingXcmpChannel; - type BaseFee = ConstU128<1_000_000_000>; type ByteFee = ConstU128<1_000>; type FeeAsset = xcm_config::TokenAssetId; } diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index eae89c6be9..8fee167296 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -73,10 +73,14 @@ pub mod pallet { /// Universal location of this runtime. type UniversalLocation: Get; - /// Relative location of the sibling bridge hub. - type SiblingBridgeHubLocation: Get; - /// The bridged network that this config is for. - type BridgedNetworkId: Get; + /// The bridged network that this config is for if specified. + /// Also used for filtering `Bridges` by `BridgedNetworkId`. + /// If not specified, allows all networks pass through. + type BridgedNetworkId: Get>; + /// Configuration for supported **bridged networks/locations** with **bridge location** and + /// **possible fee**. Allows to externalize better control over allowed **bridged + /// networks/locations**. + type Bridges: ExporterFor; /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. type ToBridgeHubSender: SendXcm; @@ -84,8 +88,6 @@ pub mod pallet { /// by the `Self::ToBridgeHubSender`. type WithBridgeHubChannel: LocalXcmChannel; - /// Base bridge fee that is paid for every outbound message. - type BaseFee: Get; /// Additional fee that is paid for every byte of the outbound message. type ByteFee: Get; /// Asset that is used to paid bridge fee. @@ -177,32 +179,75 @@ type ViaBridgeHubExporter = SovereignPaidRemoteExporter< impl, I: 'static> ExporterFor for Pallet { fn exporter_for( network: &NetworkId, - _remote_location: &InteriorMultiLocation, + remote_location: &InteriorMultiLocation, message: &Xcm<()>, ) -> Option<(MultiLocation, Option)> { - // ensure that the message is sent to the expected bridged network - if *network != T::BridgedNetworkId::get() { - return None + // ensure that the message is sent to the expected bridged network (if specified). + if let Some(bridged_network) = T::BridgedNetworkId::get() { + if *network != bridged_network { + log::trace!( + target: LOG_TARGET, + "Router with bridged_network_id {:?} does not support bridging to network {:?}!", + bridged_network, + network, + ); + return None + } } + // ensure that the message is sent to the expected bridged network and location. + let Some((bridge_hub_location, maybe_payment)) = T::Bridges::exporter_for(network, remote_location, message) else { + log::trace!( + target: LOG_TARGET, + "Router with bridged_network_id {:?} does not support bridging to network {:?} and remote_location {:?}!", + T::BridgedNetworkId::get(), + network, + remote_location, + ); + return None + }; + + // take `base_fee` from `T::Brides`, but it has to be the same `T::FeeAsset` + let base_fee = match maybe_payment { + Some(payment) => match payment { + MultiAsset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount, + invalid_asset @ _ => { + log::error!( + target: LOG_TARGET, + "Router with bridged_network_id {:?} is configured for `T::FeeAsset` {:?} which is not compatible with {:?} for bridge_hub_location: {:?} for bridging to {:?}/{:?}!", + T::BridgedNetworkId::get(), + T::FeeAsset::get(), + invalid_asset, + bridge_hub_location, + network, + remote_location, + ); + return None + }, + }, + None => 0, + }; + // compute fee amount. Keep in mind that this is only the bridge fee. The fee for sending // message from this chain to child/sibling bridge hub is determined by the // `Config::ToBridgeHubSender` let message_size = message.encoded_size(); let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get()); - let fee_sum = T::BaseFee::get().saturating_add(message_fee); + let fee_sum = base_fee.saturating_add(message_fee); let fee_factor = Self::delivery_fee_factor(); let fee = fee_factor.saturating_mul_int(fee_sum); + let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None }; + log::info!( target: LOG_TARGET, - "Going to send message ({} bytes) over bridge. Computed bridge fee {} using fee factor {}", - fee, - fee_factor, + "Going to send message ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}", message_size, + fee, + fee_factor ); - Some((T::SiblingBridgeHubLocation::get(), Some((T::FeeAsset::get(), fee).into()))) + Some((bridge_hub_location, fee)) } } diff --git a/modules/xcm-bridge-hub-router/src/mock.rs b/modules/xcm-bridge-hub-router/src/mock.rs index b585ee76ba..9be1dd1f52 100644 --- a/modules/xcm-bridge-hub-router/src/mock.rs +++ b/modules/xcm-bridge-hub-router/src/mock.rs @@ -26,6 +26,7 @@ use sp_runtime::{ BuildStorage, }; use xcm::prelude::*; +use xcm_builder::NetworkExportTable; pub type AccountId = u64; type Block = frame_system::mocking::MockBlock; @@ -51,6 +52,8 @@ parameter_types! { pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(ThisNetworkId::get()), Parachain(1000)); pub SiblingBridgeHubLocation: MultiLocation = ParentThen(X1(Parachain(1002))).into(); pub BridgeFeeAsset: AssetId = MultiLocation::parent().into(); + pub BridgeTable: Vec<(NetworkId, MultiLocation, Option)> + = vec![(BridgedNetworkId::get(), SiblingBridgeHubLocation::get(), Some((BridgeFeeAsset::get(), BASE_FEE).into()))]; } impl frame_system::Config for TestRuntime { @@ -83,13 +86,12 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { type WeightInfo = (); type UniversalLocation = UniversalLocation; - type SiblingBridgeHubLocation = SiblingBridgeHubLocation; type BridgedNetworkId = BridgedNetworkId; + type Bridges = NetworkExportTable; type ToBridgeHubSender = TestToBridgeHubSender; type WithBridgeHubChannel = TestWithBridgeHubChannel; - type BaseFee = ConstU128; type ByteFee = ConstU128; type FeeAsset = BridgeFeeAsset; } From dc3618a4a5bff2b1672c5a457a8980105d65a442 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 3 Aug 2023 11:18:09 +0200 Subject: [PATCH 26/31] Clippy --- modules/xcm-bridge-hub-router/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 8fee167296..485547cc65 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -211,7 +211,7 @@ impl, I: 'static> ExporterFor for Pallet { let base_fee = match maybe_payment { Some(payment) => match payment { MultiAsset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount, - invalid_asset @ _ => { + invalid_asset => { log::error!( target: LOG_TARGET, "Router with bridged_network_id {:?} is configured for `T::FeeAsset` {:?} which is not compatible with {:?} for bridge_hub_location: {:?} for bridging to {:?}/{:?}!", From 569a80f23395766545691c96a144582b20f22db6 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 3 Aug 2023 12:47:46 +0200 Subject: [PATCH 27/31] Rename LocalXcmChannel to XcmChannelStatusProvider (#2319) * Rename LocalXcmChannel to XcmChannelStatusProvider * fmt --- bin/millau/runtime/src/xcm_config.rs | 2 +- bin/runtime-common/src/messages_xcm_extension.rs | 9 ++++++--- modules/xcm-bridge-hub-router/src/lib.rs | 4 ++-- modules/xcm-bridge-hub-router/src/mock.rs | 4 ++-- primitives/xcm-bridge-hub-router/src/lib.rs | 6 +++--- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bin/millau/runtime/src/xcm_config.rs b/bin/millau/runtime/src/xcm_config.rs index a7abbdf255..25a7b0d093 100644 --- a/bin/millau/runtime/src/xcm_config.rs +++ b/bin/millau/runtime/src/xcm_config.rs @@ -248,7 +248,7 @@ impl EmulatedSiblingXcmpChannel { } } -impl bp_xcm_bridge_hub_router::LocalXcmChannel for EmulatedSiblingXcmpChannel { +impl bp_xcm_bridge_hub_router::XcmChannelStatusProvider for EmulatedSiblingXcmpChannel { fn is_congested() -> bool { frame_support::storage::unhashed::get_or_default(b"EmulatedSiblingXcmpChannel.Congested") } diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index f8ade35e85..f85bf07fa5 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -27,7 +27,7 @@ use bp_messages::{ LaneId, MessageNonce, }; use bp_runtime::{messages::MessageDispatchResult, RangeInclusiveExt}; -use bp_xcm_bridge_hub_router::LocalXcmChannel; +use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ dispatch::Weight, @@ -62,8 +62,11 @@ pub struct XcmBlobMessageDispatch { _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, Channel)>, } -impl - MessageDispatch for XcmBlobMessageDispatch +impl< + BlobDispatcher: DispatchBlob, + Weights: MessagesPalletWeights, + Channel: XcmChannelStatusProvider, + > MessageDispatch for XcmBlobMessageDispatch { type DispatchPayload = XcmAsPlainPayload; type DispatchLevelResult = XcmBlobMessageDispatchResult; diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 485547cc65..3d312748b3 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -25,7 +25,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use bp_xcm_bridge_hub_router::LocalXcmChannel; +use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; use codec::Encode; use frame_support::traits::Get; use sp_runtime::{traits::One, FixedPointNumber, FixedU128, Saturating}; @@ -86,7 +86,7 @@ pub mod pallet { type ToBridgeHubSender: SendXcm; /// Underlying channel with the sibling bridge hub. It must match the channel, used /// by the `Self::ToBridgeHubSender`. - type WithBridgeHubChannel: LocalXcmChannel; + type WithBridgeHubChannel: XcmChannelStatusProvider; /// Additional fee that is paid for every byte of the outbound message. type ByteFee: Get; diff --git a/modules/xcm-bridge-hub-router/src/mock.rs b/modules/xcm-bridge-hub-router/src/mock.rs index 9be1dd1f52..1392958b21 100644 --- a/modules/xcm-bridge-hub-router/src/mock.rs +++ b/modules/xcm-bridge-hub-router/src/mock.rs @@ -18,7 +18,7 @@ use crate as pallet_xcm_bridge_hub_router; -use bp_xcm_bridge_hub_router::LocalXcmChannel; +use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; use frame_support::{construct_runtime, parameter_types}; use sp_core::H256; use sp_runtime::{ @@ -128,7 +128,7 @@ impl TestWithBridgeHubChannel { } } -impl LocalXcmChannel for TestWithBridgeHubChannel { +impl XcmChannelStatusProvider for TestWithBridgeHubChannel { fn is_congested() -> bool { frame_support::storage::unhashed::get_or_default(b"TestWithBridgeHubChannel.Congested") } diff --git a/primitives/xcm-bridge-hub-router/src/lib.rs b/primitives/xcm-bridge-hub-router/src/lib.rs index e3d627660a..81798d4181 100644 --- a/primitives/xcm-bridge-hub-router/src/lib.rs +++ b/primitives/xcm-bridge-hub-router/src/lib.rs @@ -18,13 +18,13 @@ #![cfg_attr(not(feature = "std"), no_std)] -/// Local XCM channel that may report whether it is congested or not. -pub trait LocalXcmChannel { +/// XCM channel status provider that may report whether it is congested or not. +pub trait XcmChannelStatusProvider { /// Returns true if the queue is currently congested. fn is_congested() -> bool; } -impl LocalXcmChannel for () { +impl XcmChannelStatusProvider for () { fn is_congested() -> bool { false } From add9fb1d53e18b1128eab5d56df022f2870ef9f4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 4 Aug 2023 09:08:42 +0300 Subject: [PATCH 28/31] added/fixed some docs --- modules/xcm-bridge-hub-router/src/lib.rs | 20 ++++++++++++++------ primitives/xcm-bridge-hub-router/src/lib.rs | 5 ++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 3d312748b3..33645dee8a 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -22,6 +22,11 @@ //! All other bridge hub queues offer some backpressure mechanisms. So if at least one //! of all queues is congested, it will eventually lead to the growth of the queue at //! this chain. +//! +//! **A note on terminology**: when we mention the bridge hub here, we mean the chain that +//! has the messages pallet deployed (`pallet-bridge-grandpa`, `pallet-bridge-messages`, +//! `pallet-xcm-bridge-hub`, ...). It may be the system bridge hub parachain or any other +//! chain. #![cfg_attr(not(feature = "std"), no_std)] @@ -100,7 +105,7 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - // if XCM queue is still congested, we don't change anything + // if XCM channel is still congested, we don't change anything if T::WithBridgeHubChannel::is_congested() { return T::WeightInfo::on_initialize_when_congested() } @@ -111,7 +116,7 @@ pub mod pallet { if previous_factor != *f { log::info!( target: LOG_TARGET, - "Bridge queue is uncongested. Decreased fee factor from {} to {}", + "Bridge channel is uncongested. Decreased fee factor from {} to {}", previous_factor, f, ); @@ -142,7 +147,7 @@ pub mod pallet { impl, I: 'static> Pallet { /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. pub(crate) fn on_message_sent_to_bridge(message_size: u32) { - // if outbound queue is not congested, do nothing + // if outbound channel is not congested, do nothing if !T::WithBridgeHubChannel::is_congested() { return } @@ -156,7 +161,7 @@ pub mod pallet { *f = f.saturating_mul(total_factor); log::info!( target: LOG_TARGET, - "Bridge queue is congested. Increased fee factor from {} to {}", + "Bridge channel is congested. Increased fee factor from {} to {}", previous_factor, f, ); @@ -196,7 +201,9 @@ impl, I: 'static> ExporterFor for Pallet { } // ensure that the message is sent to the expected bridged network and location. - let Some((bridge_hub_location, maybe_payment)) = T::Bridges::exporter_for(network, remote_location, message) else { + let Some((bridge_hub_location, maybe_payment)) = + T::Bridges::exporter_for(network, remote_location, message) + else { log::trace!( target: LOG_TARGET, "Router with bridged_network_id {:?} does not support bridging to network {:?} and remote_location {:?}!", @@ -214,7 +221,8 @@ impl, I: 'static> ExporterFor for Pallet { invalid_asset => { log::error!( target: LOG_TARGET, - "Router with bridged_network_id {:?} is configured for `T::FeeAsset` {:?} which is not compatible with {:?} for bridge_hub_location: {:?} for bridging to {:?}/{:?}!", + "Router with bridged_network_id {:?} is configured for `T::FeeAsset` {:?} which is not \ + compatible with {:?} for bridge_hub_location: {:?} for bridging to {:?}/{:?}!", T::BridgedNetworkId::get(), T::FeeAsset::get(), invalid_asset, diff --git a/primitives/xcm-bridge-hub-router/src/lib.rs b/primitives/xcm-bridge-hub-router/src/lib.rs index 81798d4181..88db2cad77 100644 --- a/primitives/xcm-bridge-hub-router/src/lib.rs +++ b/primitives/xcm-bridge-hub-router/src/lib.rs @@ -19,8 +19,11 @@ #![cfg_attr(not(feature = "std"), no_std)] /// XCM channel status provider that may report whether it is congested or not. +/// +/// By channel we mean the physical channel that is used to deliver messages of one +/// of the bridge queues. pub trait XcmChannelStatusProvider { - /// Returns true if the queue is currently congested. + /// Returns true if the channel is currently congested. fn is_congested() -> bool; } From f822ebc45081e67217e7395cae7d42c661dc8464 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 4 Aug 2023 16:14:45 +0300 Subject: [PATCH 29/31] Dynamic fees v1: report congestion status to sending chain (#2318) * report congestion status: changes at the sending chain * OnMessagesDelivered is back * report congestion status: changes at the bridge hub * moer logging * fix? benchmarks * spelling * tests for XcmBlobHaulerAdapter and LocalXcmQueueManager * tests for messages pallet * fix typo * rustdoc * Update modules/messages/src/lib.rs * apply review suggestions --- Cargo.lock | 5 + bin/millau/runtime/src/lib.rs | 3 + bin/millau/runtime/src/rialto_messages.rs | 13 +- .../runtime/src/rialto_parachain_messages.rs | 14 +- bin/millau/runtime/src/xcm_config.rs | 21 +- bin/rialto-parachain/runtime/src/lib.rs | 1 + .../runtime/src/millau_messages.rs | 13 +- bin/rialto/runtime/src/lib.rs | 1 + bin/rialto/runtime/src/millau_messages.rs | 13 +- .../src/messages_xcm_extension.rs | 513 +++++++++--------- bin/runtime-common/src/mock.rs | 1 + modules/messages/src/lib.rs | 39 +- modules/messages/src/mock.rs | 23 +- modules/xcm-bridge-hub-router/Cargo.toml | 3 +- .../xcm-bridge-hub-router/src/benchmarking.rs | 48 +- modules/xcm-bridge-hub-router/src/lib.rs | 226 +++++--- modules/xcm-bridge-hub-router/src/mock.rs | 2 + modules/xcm-bridge-hub-router/src/weights.rs | 134 ++++- primitives/messages/src/source_chain.rs | 13 + primitives/xcm-bridge-hub-router/Cargo.toml | 14 +- primitives/xcm-bridge-hub-router/src/lib.rs | 22 + scripts/update-weights.sh | 2 +- 22 files changed, 764 insertions(+), 360 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd59bf9f98..4e609a3050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1161,6 +1161,11 @@ dependencies = [ [[package]] name = "bp-xcm-bridge-hub-router" version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", +] [[package]] name = "bridge-runtime-common" diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index 9fe25bf15f..82d7c8366d 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -465,6 +465,7 @@ impl pallet_bridge_messages::Config for Runtime { WithRialtoMessagesInstance, frame_support::traits::ConstU64<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = crate::rialto_messages::RialtoAsSourceHeaderChain; type MessageDispatch = crate::rialto_messages::FromRialtoMessageDispatch; @@ -496,6 +497,7 @@ impl pallet_bridge_messages::Config for Run WithRialtoParachainMessagesInstance, frame_support::traits::ConstU64<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = crate::rialto_parachain_messages::RialtoParachainAsSourceHeaderChain; type MessageDispatch = crate::rialto_parachain_messages::FromRialtoParachainMessageDispatch; @@ -558,6 +560,7 @@ impl pallet_xcm_bridge_hub_router::Config for Runtime { type BridgedNetworkId = xcm_config::RialtoNetwork; type Bridges = NetworkExportTable; + type BridgeHubOrigin = frame_system::EnsureRoot; type ToBridgeHubSender = xcm_config::XcmRouter; type WithBridgeHubChannel = xcm_config::EmulatedSiblingXcmpChannel; diff --git a/bin/millau/runtime/src/rialto_messages.rs b/bin/millau/runtime/src/rialto_messages.rs index aa7a6a9d4b..47a765c59f 100644 --- a/bin/millau/runtime/src/rialto_messages.rs +++ b/bin/millau/runtime/src/rialto_messages.rs @@ -45,6 +45,9 @@ parameter_types! { pub const WeightCredit: Weight = BASE_XCM_WEIGHT_TWICE; /// Lane used by the with-Rialto bridge. pub RialtoSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), XCM_LANE); + + /// Dummy message used in configuration. + pub DummyXcmMessage: Xcm<()> = Xcm::new(); } /// Message payload for Millau -> Rialto messages. @@ -126,10 +129,16 @@ pub type ToRialtoBlobExporter = HaulBlobExporter< pub struct ToRialtoXcmBlobHauler; impl XcmBlobHauler for ToRialtoXcmBlobHauler { - type MessageSender = pallet_bridge_messages::Pallet; - type MessageSenderOrigin = RuntimeOrigin; + type Runtime = Runtime; + type MessagesInstance = WithRialtoMessagesInstance; type SenderAndLane = RialtoSenderAndLane; + type ToSourceChainSender = crate::xcm_config::XcmRouter; + type CongestedMessage = DummyXcmMessage; + type UncongestedMessage = DummyXcmMessage; + + type MessageSenderOrigin = RuntimeOrigin; + fn message_sender_origin() -> RuntimeOrigin { pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get())) .into() diff --git a/bin/millau/runtime/src/rialto_parachain_messages.rs b/bin/millau/runtime/src/rialto_parachain_messages.rs index 81193bc94e..fb1d79d006 100644 --- a/bin/millau/runtime/src/rialto_parachain_messages.rs +++ b/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -47,6 +47,9 @@ parameter_types! { pub const WeightCredit: Weight = BASE_XCM_WEIGHT_TWICE; /// Lane used by the with-RialtoParachain bridge. pub RialtoParachainSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), XCM_LANE); + + /// Dummy message used in configuration. + pub DummyXcmMessage: Xcm<()> = Xcm::new(); } /// Message payload for Millau -> RialtoParachain messages. @@ -126,11 +129,16 @@ pub type ToRialtoParachainBlobExporter = HaulBlobExporter< pub struct ToRialtoParachainXcmBlobHauler; impl XcmBlobHauler for ToRialtoParachainXcmBlobHauler { - type MessageSender = - pallet_bridge_messages::Pallet; - type MessageSenderOrigin = RuntimeOrigin; + type Runtime = Runtime; + type MessagesInstance = WithRialtoParachainMessagesInstance; type SenderAndLane = RialtoParachainSenderAndLane; + type ToSourceChainSender = crate::xcm_config::XcmRouter; + type CongestedMessage = DummyXcmMessage; + type UncongestedMessage = DummyXcmMessage; + + type MessageSenderOrigin = RuntimeOrigin; + fn message_sender_origin() -> RuntimeOrigin { pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get())) .into() diff --git a/bin/millau/runtime/src/xcm_config.rs b/bin/millau/runtime/src/xcm_config.rs index 25a7b0d093..45236360dd 100644 --- a/bin/millau/runtime/src/xcm_config.rs +++ b/bin/millau/runtime/src/xcm_config.rs @@ -100,7 +100,7 @@ parameter_types! { } /// The XCM router. We are not sending messages to sibling/parent/child chains here. -pub type XcmRouter = (); +pub type XcmRouter = EmulatedSiblingXcmpChannel; /// The barriers one of which must be passed for an XCM message to be executed. pub type Barrier = ( @@ -241,6 +241,21 @@ impl ExportXcm for ToRialtoOrRialtoParachainSwitchExporter { /// so we have to provide at least something to be able to run benchmarks. pub struct EmulatedSiblingXcmpChannel; +impl SendXcm for EmulatedSiblingXcmpChannel { + type Ticket = (); + + fn validate( + _destination: &mut Option, + _message: &mut Option>, + ) -> SendResult { + Ok(((), Default::default())) + } + + fn deliver(_ticket: Self::Ticket) -> Result { + Ok(XcmHash::default()) + } +} + impl EmulatedSiblingXcmpChannel { /// Start emulating congested channel. pub fn make_congested() { @@ -376,7 +391,7 @@ mod tests { let dispatch_result = FromRialtoMessageDispatch::dispatch(incoming_message); assert!(matches!( dispatch_result.dispatch_level_result, - XcmBlobMessageDispatchResult::NotDispatched(_), + XcmBlobMessageDispatchResult::Dispatched, )); } @@ -389,7 +404,7 @@ mod tests { let dispatch_result = FromRialtoMessageDispatch::dispatch(incoming_message); assert!(matches!( dispatch_result.dispatch_level_result, - XcmBlobMessageDispatchResult::NotDispatched(_), + XcmBlobMessageDispatchResult::Dispatched, )); } } diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs index 18c915819d..70810cc534 100644 --- a/bin/rialto-parachain/runtime/src/lib.rs +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -584,6 +584,7 @@ impl pallet_bridge_messages::Config for Runtime { WithMillauMessagesInstance, frame_support::traits::ConstU128<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = crate::millau_messages::MillauAsSourceHeaderChain; type MessageDispatch = crate::millau_messages::FromMillauMessageDispatch; diff --git a/bin/rialto-parachain/runtime/src/millau_messages.rs b/bin/rialto-parachain/runtime/src/millau_messages.rs index 64b6e67e83..fb6659b7f0 100644 --- a/bin/rialto-parachain/runtime/src/millau_messages.rs +++ b/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -47,6 +47,9 @@ parameter_types! { pub const WeightCredit: Weight = BASE_XCM_WEIGHT_TWICE; /// Lane used by the with-Millau bridge. pub MullauSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), XCM_LANE); + + /// Dummy message used in configuration. + pub DummyXcmMessage: Xcm<()> = Xcm::new(); } /// Message payload for RialtoParachain -> Millau messages. @@ -126,10 +129,16 @@ pub type ToMillauBlobExporter = pub struct ToMillauXcmBlobHauler; impl XcmBlobHauler for ToMillauXcmBlobHauler { - type MessageSender = pallet_bridge_messages::Pallet; - type MessageSenderOrigin = RuntimeOrigin; + type Runtime = Runtime; + type MessagesInstance = WithMillauMessagesInstance; type SenderAndLane = MullauSenderAndLane; + type ToSourceChainSender = crate::XcmRouter; + type CongestedMessage = DummyXcmMessage; + type UncongestedMessage = DummyXcmMessage; + + type MessageSenderOrigin = RuntimeOrigin; + fn message_sender_origin() -> RuntimeOrigin { pallet_xcm::Origin::from(MultiLocation::new(1, crate::UniversalLocation::get())).into() } diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs index 1d93d24951..e0cb6e9b30 100644 --- a/bin/rialto/runtime/src/lib.rs +++ b/bin/rialto/runtime/src/lib.rs @@ -442,6 +442,7 @@ impl pallet_bridge_messages::Config for Runtime { WithMillauMessagesInstance, frame_support::traits::ConstU128<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = crate::millau_messages::MillauAsSourceHeaderChain; type MessageDispatch = crate::millau_messages::FromMillauMessageDispatch; diff --git a/bin/rialto/runtime/src/millau_messages.rs b/bin/rialto/runtime/src/millau_messages.rs index aeec2c02f3..0927d9e405 100644 --- a/bin/rialto/runtime/src/millau_messages.rs +++ b/bin/rialto/runtime/src/millau_messages.rs @@ -44,6 +44,9 @@ parameter_types! { pub const WeightCredit: Weight = BASE_XCM_WEIGHT_TWICE; /// Lane used by the with-Millau bridge. pub MullauSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), XCM_LANE); + + /// Dummy message used in configuration. + pub DummyXcmMessage: Xcm<()> = Xcm::new(); } /// Message payload for Rialto -> Millau messages. @@ -125,10 +128,16 @@ pub type ToMillauBlobExporter = HaulBlobExporter< pub struct ToMillauXcmBlobHauler; impl XcmBlobHauler for ToMillauXcmBlobHauler { - type MessageSender = pallet_bridge_messages::Pallet; - type MessageSenderOrigin = RuntimeOrigin; + type Runtime = Runtime; + type MessagesInstance = WithMillauMessagesInstance; type SenderAndLane = MullauSenderAndLane; + type ToSourceChainSender = crate::xcm_config::XcmRouter; + type CongestedMessage = DummyXcmMessage; + type UncongestedMessage = DummyXcmMessage; + + type MessageSenderOrigin = RuntimeOrigin; + fn message_sender_origin() -> RuntimeOrigin { pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get())) .into() diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index f85bf07fa5..a2e4d82e26 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -22,21 +22,18 @@ //! `XcmRouter` <- `MessageDispatch` <- `InboundMessageQueue` use bp_messages::{ - source_chain::MessagesBridge, + source_chain::{MessagesBridge, OnMessagesDelivered}, target_chain::{DispatchMessage, MessageDispatch}, LaneId, MessageNonce, }; -use bp_runtime::{messages::MessageDispatchResult, RangeInclusiveExt}; +use bp_runtime::messages::MessageDispatchResult; use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; -use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; -use frame_support::{ - dispatch::Weight, - traits::{Get, ProcessMessage, ProcessMessageError, QueuePausedQuery}, - weights::WeightMeter, - CloneNoBound, EqNoBound, PartialEqNoBound, -}; +use codec::{Decode, Encode}; +use frame_support::{dispatch::Weight, traits::Get, CloneNoBound, EqNoBound, PartialEqNoBound}; +use frame_system::Config as SystemConfig; use pallet_bridge_messages::{ - Config as MessagesConfig, Pallet as MessagesPallet, WeightInfoExt as MessagesPalletWeights, + Config as MessagesConfig, OutboundLanesCongestedSignals, Pallet as MessagesPallet, + WeightInfoExt as MessagesPalletWeights, }; use scale_info::TypeInfo; use sp_runtime::SaturatedConversion; @@ -144,155 +141,199 @@ impl SenderAndLane { /// [`XcmBlobHauler`] is responsible for sending messages to the bridge "point-to-point link" from /// one side, where on the other it can be dispatched by [`XcmBlobMessageDispatch`]. pub trait XcmBlobHauler { - /// Runtime message sender adapter. - type MessageSender: MessagesBridge; + /// Runtime that has messages pallet deployed. + type Runtime: MessagesConfig; + /// Instance of the messages pallet that is used to send messages. + type MessagesInstance: 'static; /// Returns lane used by this hauler. type SenderAndLane: Get; - /// Runtime message sender origin, which is used by [`Self::MessageSender`]. + /// Actual XCM message sender (`HRMP` or `UMP`) to the source chain + /// location (`Self::SenderAndLane::get().location`). + type ToSourceChainSender: SendXcm; + /// An XCM message that is sent to the sending chain when the bridge queue becomes congested. + type CongestedMessage: Get>; + /// An XCM message that is sent to the sending chain when the bridge queue becomes not + /// congested. + type UncongestedMessage: Get>; + + /// Runtime message sender origin, which is used by the associated messages pallet. type MessageSenderOrigin; /// Runtime origin for our (i.e. this bridge hub) location within the Consensus Universe. fn message_sender_origin() -> Self::MessageSenderOrigin; } -/// XCM bridge adapter which connects [`XcmBlobHauler`] with [`XcmBlobHauler::MessageSender`] and -/// makes sure that XCM blob is sent to the [`pallet_bridge_messages`] queue to be relayed. +/// XCM bridge adapter which connects [`XcmBlobHauler`] with [`pallet_bridge_messages`] and +/// makes sure that XCM blob is sent to the outbound lane to be relayed. /// /// It needs to be used at the source bridge hub. pub struct XcmBlobHaulerAdapter(sp_std::marker::PhantomData); impl> HaulBlob for XcmBlobHaulerAdapter +where + H::Runtime: SystemConfig, + H::Runtime: MessagesConfig, { fn haul_blob(blob: sp_std::prelude::Vec) -> Result<(), HaulBlobError> { - let lane = H::SenderAndLane::get().lane; - H::MessageSender::send_message(H::message_sender_origin(), lane, blob) - .map(|artifacts| { - log::info!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "haul_blob result - ok: {:?} on lane: {:?}. Enqueued messages: {}", - artifacts.nonce, - lane, - artifacts.enqueued_messages, - ); - }) - .map_err(|error| { - log::error!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "haul_blob result - error: {:?} on lane: {:?}", - error, - lane - ); - HaulBlobError::Transport("MessageSenderError") - }) + let sender_and_lane = H::SenderAndLane::get(); + MessagesPallet::::send_message( + H::message_sender_origin(), + sender_and_lane.lane, + blob, + ) + .map(|artifacts| { + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "haul_blob result - ok: {:?} on lane: {:?}. Enqueued messages: {}", + artifacts.nonce, + sender_and_lane.lane, + artifacts.enqueued_messages, + ); + + // notify XCM queue manager about updated lane state + LocalXcmQueueManager::::on_bridge_message_enqueued( + &sender_and_lane, + artifacts.enqueued_messages, + ); + }) + .map_err(|error| { + log::error!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "haul_blob result - error: {:?} on lane: {:?}", + error, + sender_and_lane.lane, + ); + HaulBlobError::Transport("MessageSenderError") + }) } } -/// Manager of local XCM queues (and indirectly - underlying transport channels) that -/// controls the queue state. -/// -/// It needs to be used at the source bridge hub. -pub struct LocalXcmQueueManager; +impl> OnMessagesDelivered + for XcmBlobHaulerAdapter +{ + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { + let sender_and_lane = H::SenderAndLane::get(); + if sender_and_lane.lane != lane { + return + } -/// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we -/// stop processing XCM messages from the sending chain (asset hub) that "owns" the lane. -/// -/// The value is a maximal number of messages that can be delivered in a single message -/// delivery transaction, used on initial bridge hubs. -const MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE: MessageNonce = 4096; - -impl LocalXcmQueueManager { - /// Returns true if XCM message queue with given location is currently suspended. - pub fn is_inbound_queue_suspended, MI: 'static>(lane: LaneId) -> bool { - let outbound_lane = MessagesPallet::::outbound_lane_data(lane); - let enqueued_messages = outbound_lane.queued_messages().saturating_len(); - enqueued_messages > MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + // notify XCM queue manager about updated lane state + LocalXcmQueueManager::::on_bridge_messages_delivered( + &sender_and_lane, + enqueued_messages, + ); } } -/// A structure that implements [`frame_support::traits::ProcessMessage`] and may -/// be used in the `pallet-message-queue` configuration to stop processing messages when the -/// bridge queue is congested. +/// Manager of local XCM queues (and indirectly - underlying transport channels) that +/// controls the queue state. /// /// It needs to be used at the source bridge hub. -pub struct LocalXcmQueueMessageProcessor( - PhantomData<(Origin, Inner, R, MI, SL)>, -); +pub struct LocalXcmQueueManager(PhantomData); -impl ProcessMessage - for LocalXcmQueueMessageProcessor -where - Origin: Clone - + Into - + FullCodec - + MaxEncodedLen - + Clone - + Eq - + PartialEq - + TypeInfo - + Debug, - Inner: ProcessMessage, - R: MessagesConfig, - MI: 'static, - SL: Get, -{ - type Origin = Origin; - - fn process_message( - message: &[u8], - origin: Self::Origin, - meter: &mut WeightMeter, - id: &mut [u8; 32], - ) -> Result { - // if the queue is suspended, yield immediately - let sender_and_lane = SL::get(); - let is_expected_origin = origin.clone().into() == sender_and_lane.location; - if is_expected_origin && - LocalXcmQueueManager::is_inbound_queue_suspended::(sender_and_lane.lane) - { - return Err(ProcessMessageError::Yield) +/// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we +/// send a "congestion" XCM message to the sending chain. +const OUTBOUND_LANE_CONGESTED_THRESHOLD: MessageNonce = 8_192; + +/// After we have sent "congestion" XCM message to the sending chain, we wait until number +/// of messages in the outbound bridge queue drops to this count, before sending `uncongestion` +/// XCM message. +const OUTBOUND_LANE_UNCONGESTED_THRESHOLD: MessageNonce = 1_024; + +impl LocalXcmQueueManager { + /// Must be called whenever we push a message to the bridge lane. + pub fn on_bridge_message_enqueued( + sender_and_lane: &SenderAndLane, + enqueued_messages: MessageNonce, + ) { + // if we have already sent the congestion signal, we don't want to do anything + if Self::is_congested_signal_sent(sender_and_lane.lane) { + return } - // else pass message to backed processor - Inner::process_message(message, origin, meter, id) + // if the bridge queue is not congested, we don't want to do anything + let is_congested = enqueued_messages > OUTBOUND_LANE_CONGESTED_THRESHOLD; + if !is_congested { + return + } + + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Sending 'congested' XCM message to {:?} to avoid overloading lane {:?}: there are\ + {} messages queued at the bridge queue", + sender_and_lane.location, + sender_and_lane.lane, + enqueued_messages, + ); + + if let Err(e) = Self::send_congested_signal(sender_and_lane) { + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Failed to send the 'congested' XCM message to {:?}: {:?}", + sender_and_lane.location, + e, + ); + } } -} -/// A structure that implements [`frame_support::traits::QueuePausedQuery`] and may -/// be used in the `pallet-message-queue` configuration to stop processing messages when the -/// bridge queue is congested. -/// -/// It needs to be used at the source bridge hub. -pub struct LocalXcmQueueSuspender( - PhantomData<(Origin, Inner, R, MI, SL)>, -); + /// Must be called whenever we receive a message delivery confirmation. + pub fn on_bridge_messages_delivered( + sender_and_lane: &SenderAndLane, + enqueued_messages: MessageNonce, + ) { + // if we have not sent the congestion signal before, we don't want to do anything + if !Self::is_congested_signal_sent(sender_and_lane.lane) { + return + } -impl QueuePausedQuery - for LocalXcmQueueSuspender -where - Origin: Clone + Into, - Inner: QueuePausedQuery, - R: MessagesConfig, - MI: 'static, - SL: Get, -{ - fn is_paused(origin: &Origin) -> bool { - // give priority to inner status - if Inner::is_paused(origin) { - return true + // if the bridge queue is still congested, we don't want to do anything + let is_congested = enqueued_messages > OUTBOUND_LANE_UNCONGESTED_THRESHOLD; + if is_congested { + return } - // if we have suspended the queue before, do not even start processing its messages - let sender_and_lane = SL::get(); - let is_expected_origin = origin.clone().into() == sender_and_lane.location; - if is_expected_origin && - LocalXcmQueueManager::is_inbound_queue_suspended::(sender_and_lane.lane) - { - return true + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Sending 'uncongested' XCM message to {:?}. Lane {:?}: there are\ + {} messages queued at the bridge queue", + sender_and_lane.location, + sender_and_lane.lane, + enqueued_messages, + ); + + if let Err(e) = Self::send_uncongested_signal(sender_and_lane) { + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Failed to send the 'uncongested' XCM message to {:?}: {:?}", + sender_and_lane.location, + e, + ); } + } + + /// Returns true if we have sent "congested" signal to the `sending_chain_location`. + fn is_congested_signal_sent(lane: LaneId) -> bool { + OutboundLanesCongestedSignals::::get(lane) + } + + /// Send congested signal to the `sending_chain_location`. + fn send_congested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> { + send_xcm::(sender_and_lane.location, H::CongestedMessage::get())?; + OutboundLanesCongestedSignals::::insert( + sender_and_lane.lane, + true, + ); + Ok(()) + } - // else process message - false + /// Send `uncongested` signal to the `sending_chain_location`. + fn send_uncongested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> { + send_xcm::(sender_and_lane.location, H::UncongestedMessage::get())?; + OutboundLanesCongestedSignals::::remove( + sender_and_lane.lane, + ); + Ok(()) } } @@ -301,178 +342,154 @@ mod tests { use super::*; use crate::mock::*; + use bp_messages::OutboundLaneData; use frame_support::parameter_types; - use sp_runtime::traits::{ConstBool, Get}; + use pallet_bridge_messages::OutboundLanes; parameter_types! { - pub TestSenderAndLane: SenderAndLane = SenderAndLane::new(Here.into(), TEST_LANE_ID); + pub TestSenderAndLane: SenderAndLane = SenderAndLane { + location: MultiLocation::new(1, X1(Parachain(1000))), + lane: TEST_LANE_ID, + }; + pub DummyXcmMessage: Xcm<()> = Xcm::new(); } - fn test_origin_location() -> MultiLocation { - TestSenderAndLane::get().location + struct DummySendXcm; + + impl DummySendXcm { + fn messages_sent() -> u32 { + frame_support::storage::unhashed::get(b"DummySendXcm").unwrap_or(0) + } } - fn test_origin() -> MultiLocation { - test_origin_location() + impl SendXcm for DummySendXcm { + type Ticket = (); + + fn validate( + _destination: &mut Option, + _message: &mut Option>, + ) -> SendResult { + Ok(((), Default::default())) + } + + fn deliver(_ticket: Self::Ticket) -> Result { + let messages_sent: u32 = Self::messages_sent(); + frame_support::storage::unhashed::put(b"DummySendXcm", &(messages_sent + 1)); + Ok(XcmHash::default()) + } } - struct TestXcmBlobHauler; - impl XcmBlobHauler for TestXcmBlobHauler { - type MessageSender = BridgeMessages; - type MessageSenderOrigin = RuntimeOrigin; + struct TestBlobHauler; + + impl XcmBlobHauler for TestBlobHauler { + type Runtime = TestRuntime; + type MessagesInstance = (); type SenderAndLane = TestSenderAndLane; + type ToSourceChainSender = DummySendXcm; + type CongestedMessage = DummyXcmMessage; + type UncongestedMessage = DummyXcmMessage; + + type MessageSenderOrigin = RuntimeOrigin; + fn message_sender_origin() -> Self::MessageSenderOrigin { RuntimeOrigin::root() } } - struct TestInnerXcmQueueMessageProcessor; - impl ProcessMessage for TestInnerXcmQueueMessageProcessor { - type Origin = MultiLocation; - - fn process_message( - _message: &[u8], - _origin: Self::Origin, - _meter: &mut WeightMeter, - _id: &mut [u8; 32], - ) -> Result { - Ok(true) - } - } + type TestBlobHaulerAdapter = XcmBlobHaulerAdapter; - struct TestInnerXcmQueueSuspender(PhantomData); - impl> QueuePausedQuery - for TestInnerXcmQueueSuspender - { - fn is_paused(_: &MultiLocation) -> bool { - IsSuspended::get() - } + fn fill_up_lane_to_congestion() { + OutboundLanes::::insert( + TEST_LANE_ID, + OutboundLaneData { + oldest_unpruned_nonce: 0, + latest_received_nonce: 0, + latest_generated_nonce: OUTBOUND_LANE_CONGESTED_THRESHOLD, + }, + ); } - type TestXcmBlobHaulerAdapter = XcmBlobHaulerAdapter; - type TestLocalXcmQueueMessageProcessor = LocalXcmQueueMessageProcessor< - MultiLocation, - TestInnerXcmQueueMessageProcessor, - TestRuntime, - (), - TestSenderAndLane, - >; - type TestLocalXcmQueueSuspender = LocalXcmQueueSuspender< - MultiLocation, - TestInnerXcmQueueSuspender>, - TestRuntime, - (), - TestSenderAndLane, - >; - #[test] - fn inbound_xcm_queue_with_sending_chain_is_managed_by_blob_hauler() { + fn congested_signal_is_not_sent_twice() { run_test(|| { - // while we enqueue `MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE` messages to the bridge - // queue, the inbound channel with the sending chain stays opened - for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE { - TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); - assert!(!LocalXcmQueueManager::is_inbound_queue_suspended::( - TEST_LANE_ID - )); - } - - // then when we enqueue more messages, we suspend inbound queue. Note that messages - // are not dropped - they're enqueued at the bridge queue - TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); - assert!(LocalXcmQueueManager::is_inbound_queue_suspended::( - TEST_LANE_ID - )); + fill_up_lane_to_congestion(); + + // next sent message leads to congested signal + TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + + // next sent message => we don't sent another congested signal + TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); }); } #[test] - fn inbound_xcm_message_from_sibling_is_not_processed_when_bridge_queue_is_congested() { + fn congested_signal_is_not_sent_when_outbound_lane_is_not_congested() { run_test(|| { - for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + 1 { - TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); - } - - assert_eq!( - TestLocalXcmQueueMessageProcessor::process_message( - &[42], - test_origin(), - &mut WeightMeter::max_limit(), - &mut [0u8; 32], - ), - Err(ProcessMessageError::Yield), - ); - }) + TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 0); + }); } #[test] - fn inbound_xcm_message_from_other_origin_is_processed_normally() { + fn congested_signal_is_sent_when_outbound_lane_is_congested() { run_test(|| { - for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + 1 { - TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); - } - - assert_eq!( - TestLocalXcmQueueMessageProcessor::process_message( - &[42], - ParentThen(X1(Parachain(1000))).into(), - &mut WeightMeter::max_limit(), - &mut [0u8; 32], - ), - Ok(true), - ); - }) + fill_up_lane_to_congestion(); + + // next sent message leads to congested signal + TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + assert!(LocalXcmQueueManager::::is_congested_signal_sent(TEST_LANE_ID)); + }); } #[test] - fn inbound_xcm_message_from_sibling_is_processed_normally() { + fn uncongested_signal_is_not_sent_when_messages_are_delivered_at_other_lane() { run_test(|| { - assert_eq!( - TestLocalXcmQueueMessageProcessor::process_message( - &[42], - test_origin(), - &mut WeightMeter::max_limit(), - &mut [0u8; 32], - ), - Ok(true), - ); - }) + LocalXcmQueueManager::::send_congested_signal(&TestSenderAndLane::get()).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + + // when we receive a delivery report for other lane, we don't send an uncongested signal + TestBlobHaulerAdapter::on_messages_delivered(LaneId([42, 42, 42, 42]), 0); + assert_eq!(DummySendXcm::messages_sent(), 1); + }); } #[test] - fn local_xcm_queue_is_paused_when_inner_suspender_returns_paused() { + fn uncongested_signal_is_not_sent_when_we_havent_send_congested_signal_before() { run_test(|| { - assert!(LocalXcmQueueSuspender::< - MultiLocation, - TestInnerXcmQueueSuspender>, - TestRuntime, - (), - TestSenderAndLane, - >::is_paused(&test_origin())) - }) + TestBlobHaulerAdapter::on_messages_delivered(TEST_LANE_ID, 0); + assert_eq!(DummySendXcm::messages_sent(), 0); + }); } #[test] - fn local_xcm_queue_is_paused_when_bridge_queue_is_congested() { + fn uncongested_signal_is_not_sent_if_outbound_lane_is_still_congested() { run_test(|| { - for _ in 0..MAX_ENQUEUED_MESSAGES_AT_OUTBOUND_LANE + 1 { - TestXcmBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); - } + LocalXcmQueueManager::::send_congested_signal(&TestSenderAndLane::get()).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); - assert!(TestLocalXcmQueueSuspender::is_paused(&test_origin())) + TestBlobHaulerAdapter::on_messages_delivered( + TEST_LANE_ID, + OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1, + ); + assert_eq!(DummySendXcm::messages_sent(), 1); }); } #[test] - fn local_xcm_queue_with_other_origin_is_not_paused() { + fn uncongested_signal_is_sent_if_outbound_lane_is_uncongested() { run_test(|| { - assert!(!TestLocalXcmQueueSuspender::is_paused(&ParentThen(X1(Parachain(1000))).into())) - }); - } + LocalXcmQueueManager::::send_congested_signal(&TestSenderAndLane::get()).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); - #[test] - fn local_xcm_queue_is_not_paused_normally() { - run_test(|| assert!(!TestLocalXcmQueueSuspender::is_paused(&test_origin()))); + TestBlobHaulerAdapter::on_messages_delivered( + TEST_LANE_ID, + OUTBOUND_LANE_UNCONGESTED_THRESHOLD, + ); + assert_eq!(DummySendXcm::messages_sent(), 2); + }); } } diff --git a/bin/runtime-common/src/mock.rs b/bin/runtime-common/src/mock.rs index 8a2aae1e06..9c41d17fa9 100644 --- a/bin/runtime-common/src/mock.rs +++ b/bin/runtime-common/src/mock.rs @@ -250,6 +250,7 @@ impl pallet_bridge_messages::Config for TestRuntime { (), ConstU64<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = SourceHeaderChainAdapter; type MessageDispatch = DummyMessageDispatch; diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 2ffc253563..78f97745a5 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -53,7 +53,8 @@ use crate::{ use bp_messages::{ source_chain::{ - DeliveryConfirmationPayments, LaneMessageVerifier, SendMessageArtifacts, TargetHeaderChain, + DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, + SendMessageArtifacts, TargetHeaderChain, }, target_chain::{ DeliveryPayments, DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, @@ -158,6 +159,8 @@ pub mod pallet { type LaneMessageVerifier: LaneMessageVerifier; /// Delivery confirmation payments. type DeliveryConfirmationPayments: DeliveryConfirmationPayments; + /// Delivery confirmation callback. + type OnMessagesDelivered: OnMessagesDelivered; // Types that are used by inbound_lane (on target chain). @@ -492,6 +495,12 @@ pub mod pallet { lane_id, ); + // notify others about messages delivery + T::OnMessagesDelivered::on_messages_delivered( + lane_id, + lane.data().queued_messages().saturating_len(), + ); + // because of lags, the inbound lane state (`lane_data`) may have entries for // already rewarded relayers and messages (if all entries are duplicated, then // this transaction must be filtered out by our signed extension) @@ -587,6 +596,25 @@ pub mod pallet { MaxValues = MaybeOutboundLanesCount, >; + /// Map of lane id => is congested signal sent. It is managed by the + /// `bridge_runtime_common::LocalXcmQueueManager`. + /// + /// **bridges-v1**: this map is a temporary hack and will be dropped in the `v2`. We can emulate + /// a storage map using `sp_io::unhashed` storage functions, but then benchmarks are not + /// accounting its `proof_size`, so it is missing from the final weights. So we need to make it + /// a map inside some pallet. We could use a simply value instead of map here, because + /// in `v1` we'll only have a single lane. But in the case of adding another lane before `v2`, + /// it'll be easier to deal with the isolated storage map instead. + #[pallet::storage] + pub type OutboundLanesCongestedSignals, I: 'static = ()> = StorageMap< + Hasher = Blake2_128Concat, + Key = LaneId, + Value = bool, + QueryKind = ValueQuery, + OnEmpty = GetDefault, + MaxValues = MaybeOutboundLanesCount, + >; + /// All queued outbound messages. #[pallet::storage] pub type OutboundMessages, I: 'static = ()> = @@ -902,9 +930,10 @@ mod tests { inbound_unrewarded_relayers_state, message, message_payload, run_test, unrewarded_relayer, AccountId, DbWeight, RuntimeEvent as TestEvent, RuntimeOrigin, TestDeliveryConfirmationPayments, TestDeliveryPayments, TestMessageDispatch, - TestMessagesDeliveryProof, TestMessagesProof, TestRelayer, TestRuntime, TestWeightInfo, - MAX_OUTBOUND_PAYLOAD_SIZE, PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, - TEST_LANE_ID, TEST_LANE_ID_2, TEST_LANE_ID_3, TEST_RELAYER_A, TEST_RELAYER_B, + TestMessagesDeliveryProof, TestMessagesProof, TestOnMessagesDelivered, TestRelayer, + TestRuntime, TestWeightInfo, MAX_OUTBOUND_PAYLOAD_SIZE, + PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_LANE_ID_2, + TEST_LANE_ID_3, TEST_RELAYER_A, TEST_RELAYER_B, }, outbound_lane::ReceivalConfirmationError, }; @@ -1375,6 +1404,7 @@ mod tests { ); assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); + assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 1))); // this reports delivery of both message 1 and message 2 => reward is paid only to // TEST_RELAYER_B @@ -1417,6 +1447,7 @@ mod tests { ); assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); + assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0))); }); } diff --git a/modules/messages/src/mock.rs b/modules/messages/src/mock.rs index 8d93c66b4b..aac83998bb 100644 --- a/modules/messages/src/mock.rs +++ b/modules/messages/src/mock.rs @@ -21,7 +21,9 @@ use crate::Config; use bp_messages::{ calc_relayers_rewards, - source_chain::{DeliveryConfirmationPayments, LaneMessageVerifier, TargetHeaderChain}, + source_chain::{ + DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, TargetHeaderChain, + }, target_chain::{ DeliveryPayments, DispatchMessage, DispatchMessageData, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, @@ -161,6 +163,7 @@ impl Config for TestRuntime { type TargetHeaderChain = TestTargetHeaderChain; type LaneMessageVerifier = TestLaneMessageVerifier; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; + type OnMessagesDelivered = TestOnMessagesDelivered; type SourceHeaderChain = TestSourceHeaderChain; type MessageDispatch = TestMessageDispatch; @@ -440,6 +443,24 @@ impl MessageDispatch for TestMessageDispatch { } } +/// Test callback, called during message delivery confirmation transaction. +pub struct TestOnMessagesDelivered; + +impl TestOnMessagesDelivered { + pub fn call_arguments() -> Option<(LaneId, MessageNonce)> { + frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered") + } +} + +impl OnMessagesDelivered for TestOnMessagesDelivered { + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { + frame_support::storage::unhashed::put( + b"TestOnMessagesDelivered.OnMessagesDelivered", + &(lane, enqueued_messages), + ); + } +} + /// Return test lane message with given nonce and payload. pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { Message { key: MessageKey { lane_id: TEST_LANE_ID, nonce }, payload: payload.encode() } diff --git a/modules/xcm-bridge-hub-router/Cargo.toml b/modules/xcm-bridge-hub-router/Cargo.toml index 5a498271da..3d13e7cc3d 100644 --- a/modules/xcm-bridge-hub-router/Cargo.toml +++ b/modules/xcm-bridge-hub-router/Cargo.toml @@ -20,6 +20,7 @@ bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", de frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -29,7 +30,6 @@ xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", defau xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } [dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" } @@ -43,6 +43,7 @@ std = [ "frame-system/std", "log/std", "scale-info/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", "xcm/std", diff --git a/modules/xcm-bridge-hub-router/src/benchmarking.rs b/modules/xcm-bridge-hub-router/src/benchmarking.rs index 1a4338a6fc..f8c666eea7 100644 --- a/modules/xcm-bridge-hub-router/src/benchmarking.rs +++ b/modules/xcm-bridge-hub-router/src/benchmarking.rs @@ -18,11 +18,16 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{DeliveryFeeFactor, InitialFactor}; +use crate::{Bridge, Call}; +use bp_xcm_bridge_hub_router::{BridgeState, MINIMAL_DELIVERY_FEE_FACTOR}; use frame_benchmarking::benchmarks_instance_pallet; -use frame_support::traits::{Get, Hooks}; +use frame_support::{ + dispatch::UnfilteredDispatchable, + traits::{EnsureOrigin, Get, Hooks}, +}; use sp_runtime::traits::Zero; +use xcm::prelude::*; /// Pallet we're benchmarking here. pub struct Pallet, I: 'static = ()>(crate::Pallet); @@ -35,15 +40,50 @@ pub trait Config: crate::Config { benchmarks_instance_pallet! { on_initialize_when_non_congested { - DeliveryFeeFactor::::put(InitialFactor::get() + InitialFactor::get()); + Bridge::::put(BridgeState { + is_congested: false, + delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, + }); }: { crate::Pallet::::on_initialize(Zero::zero()) } on_initialize_when_congested { - DeliveryFeeFactor::::put(InitialFactor::get() + InitialFactor::get()); + Bridge::::put(BridgeState { + is_congested: false, + delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, + }); T::make_congested(); }: { crate::Pallet::::on_initialize(Zero::zero()) } + + report_bridge_status { + Bridge::::put(BridgeState::default()); + + let origin: T::RuntimeOrigin = T::BridgeHubOrigin::try_successful_origin().expect("expected valid BridgeHubOrigin"); + let bridge_id = Default::default(); + let is_congested = true; + + let call = Call::::report_bridge_status { bridge_id, is_congested }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(Bridge::::get().is_congested); + } + + send_message { + // make local queue congested, because it means additional db write + T::make_congested(); + + let dest = MultiLocation::new( + T::UniversalLocation::get().len() as u8, + X1(GlobalConsensus(T::BridgedNetworkId::get().unwrap())), + ); + let xcm = sp_std::vec![].into(); + }: { + send_xcm::>(dest, xcm).expect("message is sent") + } + verify { + assert!(Bridge::::get().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR); + } } diff --git a/modules/xcm-bridge-hub-router/src/lib.rs b/modules/xcm-bridge-hub-router/src/lib.rs index 33645dee8a..87e050b45c 100644 --- a/modules/xcm-bridge-hub-router/src/lib.rs +++ b/modules/xcm-bridge-hub-router/src/lib.rs @@ -30,10 +30,13 @@ #![cfg_attr(not(feature = "std"), no_std)] -use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; +use bp_xcm_bridge_hub_router::{ + BridgeState, XcmChannelStatusProvider, MINIMAL_DELIVERY_FEE_FACTOR, +}; use codec::Encode; use frame_support::traits::Get; -use sp_runtime::{traits::One, FixedPointNumber, FixedU128, Saturating}; +use sp_core::H256; +use sp_runtime::{FixedPointNumber, FixedU128, Saturating}; use xcm::prelude::*; use xcm_builder::{ExporterFor, SovereignPaidRemoteExporter}; @@ -87,6 +90,8 @@ pub mod pallet { /// networks/locations**. type Bridges: ExporterFor; + /// Origin of the sibling bridge hub that is allowed to report bridge status. + type BridgeHubOrigin: EnsureOrigin; /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. type ToBridgeHubSender: SendXcm; /// Underlying channel with the sibling bridge hub. It must match the channel, used @@ -105,67 +110,111 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - // if XCM channel is still congested, we don't change anything + // TODO: make sure that `WithBridgeHubChannel::is_congested` returns true if either + // of XCM channels (outbound/inbound) is suspended. Because if outbound is suspended + // that is definitely congestion. If inbound is suspended, then we are not able to + // receive the "report_bridge_status" signal (that maybe sent by the bridge hub). + + // if the channel with sibling/child bridge hub is suspended, we don't change + // anything if T::WithBridgeHubChannel::is_congested() { return T::WeightInfo::on_initialize_when_congested() } - DeliveryFeeFactor::::mutate(|f| { - let previous_factor = *f; - *f = InitialFactor::get().max(*f / EXPONENTIAL_FEE_BASE); - if previous_factor != *f { - log::info!( - target: LOG_TARGET, - "Bridge channel is uncongested. Decreased fee factor from {} to {}", - previous_factor, - f, - ); + // if bridge has reported congestion, we don't change anything + let mut bridge = Self::bridge(); + if bridge.is_congested { + return T::WeightInfo::on_initialize_when_congested() + } - T::WeightInfo::on_initialize_when_non_congested() - } else { - // we have not actually updated the `DeliveryFeeFactor`, so we may deduct - // single db write from maximal weight - T::WeightInfo::on_initialize_when_non_congested() - .saturating_sub(T::DbWeight::get().writes(1)) - } - }) + // if fee factor is already minimal, we don't change anything + if bridge.delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR { + return T::WeightInfo::on_initialize_when_congested() + } + + let previous_factor = bridge.delivery_fee_factor; + bridge.delivery_fee_factor = + MINIMAL_DELIVERY_FEE_FACTOR.max(bridge.delivery_fee_factor / EXPONENTIAL_FEE_BASE); + log::info!( + target: LOG_TARGET, + "Bridge queue is uncongested. Decreased fee factor from {} to {}", + previous_factor, + bridge.delivery_fee_factor, + ); + + Bridge::::put(bridge); + T::WeightInfo::on_initialize_when_non_congested() } } - /// Initialization value for the delivery fee factor. - #[pallet::type_value] - pub fn InitialFactor() -> FixedU128 { - FixedU128::one() + #[pallet::call] + impl, I: 'static> Pallet { + /// Notification about congested bridge queue. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::report_bridge_status())] + pub fn report_bridge_status( + origin: OriginFor, + // this argument is not currently used, but to ease future migration, we'll keep it + // here + bridge_id: H256, + is_congested: bool, + ) -> DispatchResult { + let _ = T::BridgeHubOrigin::ensure_origin(origin)?; + + log::info!( + target: LOG_TARGET, + "Received bridge status from {:?}: congested = {}", + bridge_id, + is_congested, + ); + + Bridge::::mutate(|bridge| { + bridge.is_congested = is_congested; + }); + Ok(()) + } } - /// The number to multiply the base delivery fee by. + /// Bridge that we are using. + /// + /// **bridges-v1** assumptions: all outbound messages through this router are using single lane + /// and to single remote consensus. If there is some other remote consensus that uses the same + /// bridge hub, the separate pallet instance shall be used, In `v2` we'll have all required + /// primitives (lane-id aka bridge-id, derived from XCM locations) to support multiple bridges + /// by the same pallet instance. #[pallet::storage] - #[pallet::getter(fn delivery_fee_factor)] - pub type DeliveryFeeFactor, I: 'static = ()> = - StorageValue<_, FixedU128, ValueQuery, InitialFactor>; + #[pallet::getter(fn bridge)] + pub type Bridge, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>; impl, I: 'static> Pallet { /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. pub(crate) fn on_message_sent_to_bridge(message_size: u32) { - // if outbound channel is not congested, do nothing - if !T::WithBridgeHubChannel::is_congested() { - return - } + let _ = Bridge::::try_mutate(|bridge| { + let is_channel_with_bridge_hub_congested = T::WithBridgeHubChannel::is_congested(); + let is_bridge_congested = bridge.is_congested; + + // if outbound queue is not congested AND bridge has not reported congestion, do + // nothing + if !is_channel_with_bridge_hub_congested && !is_bridge_congested { + return Err(()) + } + + // ok - we need to increase the fee factor, let's do that + let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) + .saturating_mul(MESSAGE_SIZE_FEE_BASE); + let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); + let previous_factor = bridge.delivery_fee_factor; + bridge.delivery_fee_factor = + bridge.delivery_fee_factor.saturating_mul(total_factor); - // ok - we need to increase the fee factor, let's do that - let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) - .saturating_mul(MESSAGE_SIZE_FEE_BASE); - let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); - DeliveryFeeFactor::::mutate(|f| { - let previous_factor = *f; - *f = f.saturating_mul(total_factor); log::info!( target: LOG_TARGET, "Bridge channel is congested. Increased fee factor from {} to {}", previous_factor, - f, + bridge.delivery_fee_factor, ); - *f + + Ok(()) }); } } @@ -242,14 +291,15 @@ impl, I: 'static> ExporterFor for Pallet { let message_size = message.encoded_size(); let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get()); let fee_sum = base_fee.saturating_add(message_fee); - let fee_factor = Self::delivery_fee_factor(); + let fee_factor = Self::bridge().delivery_fee_factor; let fee = fee_factor.saturating_mul_int(fee_sum); let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None }; log::info!( target: LOG_TARGET, - "Going to send message ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}", + "Going to send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}", + (network, remote_location), message_size, fee, fee_factor @@ -311,40 +361,67 @@ mod tests { use mock::*; use frame_support::traits::Hooks; + use sp_runtime::traits::One; + + fn congested_bridge(delivery_fee_factor: FixedU128) -> BridgeState { + BridgeState { is_congested: true, delivery_fee_factor } + } + + fn uncongested_bridge(delivery_fee_factor: FixedU128) -> BridgeState { + BridgeState { is_congested: false, delivery_fee_factor } + } #[test] fn initial_fee_factor_is_one() { run_test(|| { - assert_eq!(DeliveryFeeFactor::::get(), FixedU128::one()); + assert_eq!( + Bridge::::get(), + uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR), + ); }) } #[test] - fn fee_factor_is_not_decreased_from_on_initialize_when_queue_is_congested() { + fn fee_factor_is_not_decreased_from_on_initialize_when_xcm_channel_is_congested() { run_test(|| { - DeliveryFeeFactor::::put(FixedU128::from_rational(125, 100)); + Bridge::::put(uncongested_bridge(FixedU128::from_rational(125, 100))); TestWithBridgeHubChannel::make_congested(); - // it should not decrease, because queue is congested - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + // it should not decrease, because xcm channel is congested + let old_bridge = XcmBridgeHubRouter::bridge(); + XcmBridgeHubRouter::on_initialize(One::one()); + assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge); + }) + } + + #[test] + fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_has_reported_congestion() { + run_test(|| { + Bridge::::put(congested_bridge(FixedU128::from_rational(125, 100))); + + // it should not decrease, because bridge congested + let old_bridge = XcmBridgeHubRouter::bridge(); XcmBridgeHubRouter::on_initialize(One::one()); - assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), old_delivery_fee_factor); + assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge); }) } #[test] - fn fee_factor_is_decreased_from_on_initialize_when_queue_is_uncongested() { + fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() { run_test(|| { - DeliveryFeeFactor::::put(FixedU128::from_rational(125, 100)); + Bridge::::put(uncongested_bridge(FixedU128::from_rational(125, 100))); // it shold eventually decreased to one - while XcmBridgeHubRouter::delivery_fee_factor() > FixedU128::one() { + while XcmBridgeHubRouter::bridge().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR { XcmBridgeHubRouter::on_initialize(One::one()); } // verify that it doesn't decreases anymore XcmBridgeHubRouter::on_initialize(One::one()); - assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), FixedU128::one()); + assert_eq!( + XcmBridgeHubRouter::bridge(), + uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR) + ); }) } @@ -394,7 +471,7 @@ mod tests { // but when factor is larger than one, it increases the fee, so it becomes: // `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE` let factor = FixedU128::from_rational(125, 100); - DeliveryFeeFactor::::put(factor); + Bridge::::put(uncongested_bridge(factor)); let expected_fee = (FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) * factor) @@ -408,9 +485,9 @@ mod tests { } #[test] - fn sent_message_doesnt_increase_factor_if_queue_is_uncongested() { + fn sent_message_doesnt_increase_factor_if_xcm_channel_is_uncongested() { run_test(|| { - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + let old_bridge = XcmBridgeHubRouter::bridge(); assert_eq!( send_xcm::( MultiLocation::new( @@ -424,16 +501,16 @@ mod tests { ); assert!(TestToBridgeHubSender::is_message_sent()); - assert_eq!(old_delivery_fee_factor, XcmBridgeHubRouter::delivery_fee_factor()); + assert_eq!(old_bridge, XcmBridgeHubRouter::bridge()); }); } #[test] - fn sent_message_increases_factor_if_queue_is_congested() { + fn sent_message_increases_factor_if_xcm_channel_is_congested() { run_test(|| { TestWithBridgeHubChannel::make_congested(); - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + let old_bridge = XcmBridgeHubRouter::bridge(); assert_eq!( send_xcm::( MultiLocation::new( @@ -447,7 +524,34 @@ mod tests { ); assert!(TestToBridgeHubSender::is_message_sent()); - assert!(old_delivery_fee_factor < XcmBridgeHubRouter::delivery_fee_factor()); + assert!( + old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor + ); + }); + } + + #[test] + fn sent_message_increases_factor_if_bridge_has_reported_congestion() { + run_test(|| { + Bridge::::put(congested_bridge(MINIMAL_DELIVERY_FEE_FACTOR)); + + let old_bridge = XcmBridgeHubRouter::bridge(); + assert_eq!( + send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop), + Ok(()), + ); + + assert!(TestToBridgeHubSender::is_message_sent()); + assert!( + old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor + ); }); } } diff --git a/modules/xcm-bridge-hub-router/src/mock.rs b/modules/xcm-bridge-hub-router/src/mock.rs index 1392958b21..5ad7be4890 100644 --- a/modules/xcm-bridge-hub-router/src/mock.rs +++ b/modules/xcm-bridge-hub-router/src/mock.rs @@ -20,6 +20,7 @@ use crate as pallet_xcm_bridge_hub_router; use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; use frame_support::{construct_runtime, parameter_types}; +use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, ConstU128, IdentityLookup}, @@ -89,6 +90,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { type BridgedNetworkId = BridgedNetworkId; type Bridges = NetworkExportTable; + type BridgeHubOrigin = EnsureRoot; type ToBridgeHubSender = TestToBridgeHubSender; type WithBridgeHubChannel = TestWithBridgeHubChannel; diff --git a/modules/xcm-bridge-hub-router/src/weights.rs b/modules/xcm-bridge-hub-router/src/weights.rs index 497ab9e4f1..04909f3b2e 100644 --- a/modules/xcm-bridge-hub-router/src/weights.rs +++ b/modules/xcm-bridge-hub-router/src/weights.rs @@ -17,7 +17,7 @@ //! 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-07-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-08-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -52,6 +52,8 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn on_initialize_when_non_congested() -> Weight; fn on_initialize_when_congested() -> Weight; + fn report_bridge_status() -> Weight; + fn send_message() -> Weight; } /// Weights for `pallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets. @@ -59,25 +61,30 @@ pub trait WeightInfo { /// Those weights are test only and must never be used in production. pub struct BridgeWeight(PhantomData); impl WeightInfo for BridgeWeight { + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` /// (r:1 w:0) /// /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 /// w:0) - /// - /// Storage: `XcmBridgeHubRouter::DeliveryFeeFactor` (r:1 w:1) - /// - /// Proof: `XcmBridgeHubRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), - /// added: 511, mode: `MaxEncodedLen`) fn on_initialize_when_non_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `52` - // Estimated: `3517` - // Minimum execution time: 11_141 nanoseconds. - Weight::from_parts(11_339_000, 3517) + // Measured: `53` + // Estimated: `3518` + // Minimum execution time: 11_934 nanoseconds. + Weight::from_parts(12_201_000, 3518) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` /// (r:1 w:0) /// @@ -85,34 +92,73 @@ impl WeightInfo for BridgeWeight { /// w:0) fn on_initialize_when_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `82` - // Estimated: `3547` - // Minimum execution time: 4_239 nanoseconds. - Weight::from_parts(4_383_000, 3547).saturating_add(T::DbWeight::get().reads(1_u64)) + // Measured: `94` + // Estimated: `3559` + // Minimum execution time: 9_010 nanoseconds. + Weight::from_parts(9_594_000, 3559) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `1502` + // Minimum execution time: 10_427 nanoseconds. + Weight::from_parts(10_682_000, 1502) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn send_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3517` + // Minimum execution time: 19_709 nanoseconds. + Weight::from_parts(20_110_000, 3517) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` /// (r:1 w:0) /// /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 /// w:0) - /// - /// Storage: `XcmBridgeHubRouter::DeliveryFeeFactor` (r:1 w:1) - /// - /// Proof: `XcmBridgeHubRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), - /// added: 511, mode: `MaxEncodedLen`) fn on_initialize_when_non_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `52` - // Estimated: `3517` - // Minimum execution time: 11_141 nanoseconds. - Weight::from_parts(11_339_000, 3517) + // Measured: `53` + // Estimated: `3518` + // Minimum execution time: 11_934 nanoseconds. + Weight::from_parts(12_201_000, 3518) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` /// (r:1 w:0) /// @@ -120,9 +166,43 @@ impl WeightInfo for () { /// w:0) fn on_initialize_when_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `82` - // Estimated: `3547` - // Minimum execution time: 4_239 nanoseconds. - Weight::from_parts(4_383_000, 3547).saturating_add(RocksDbWeight::get().reads(1_u64)) + // Measured: `94` + // Estimated: `3559` + // Minimum execution time: 9_010 nanoseconds. + Weight::from_parts(9_594_000, 3559) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `1502` + // Minimum execution time: 10_427 nanoseconds. + Weight::from_parts(10_682_000, 1502) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn send_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3517` + // Minimum execution time: 19_709 nanoseconds. + Weight::from_parts(20_110_000, 3517) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs index cf2ac3a3df..ce612e83e7 100644 --- a/primitives/messages/src/source_chain.rs +++ b/primitives/messages/src/source_chain.rs @@ -116,6 +116,19 @@ impl DeliveryConfirmationPayments for () { } } +/// Callback that is called at the source chain (bridge hub) when we get delivery confirmation +/// for new messages. +pub trait OnMessagesDelivered { + /// New messages delivery has been confirmed. + /// + /// The only argument of the function is the number of yet undelivered messages + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce); +} + +impl OnMessagesDelivered for () { + fn on_messages_delivered(_lane: LaneId, _enqueued_messages: MessageNonce) {} +} + /// Send message artifacts. #[derive(Eq, RuntimeDebug, PartialEq)] pub struct SendMessageArtifacts { diff --git a/primitives/xcm-bridge-hub-router/Cargo.toml b/primitives/xcm-bridge-hub-router/Cargo.toml index 95b232fffd..611390b95a 100644 --- a/primitives/xcm-bridge-hub-router/Cargo.toml +++ b/primitives/xcm-bridge-hub-router/Cargo.toml @@ -6,6 +6,18 @@ authors = ["Parity Technologies "] edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive", "bit-vec"] } +scale-info = { version = "2.9.0", default-features = false, features = ["bit-vec", "derive"] } + +# Substrate Dependencies + +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + [features] default = ["std"] -std = [] +std = [ + "codec/std", + "scale-info/std", + "sp-runtime/std", +] diff --git a/primitives/xcm-bridge-hub-router/src/lib.rs b/primitives/xcm-bridge-hub-router/src/lib.rs index 88db2cad77..1f1a92cc75 100644 --- a/primitives/xcm-bridge-hub-router/src/lib.rs +++ b/primitives/xcm-bridge-hub-router/src/lib.rs @@ -18,6 +18,13 @@ #![cfg_attr(not(feature = "std"), no_std)] +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{FixedU128, RuntimeDebug}; + +/// Minimal delivery fee factor. +pub const MINIMAL_DELIVERY_FEE_FACTOR: FixedU128 = FixedU128::from_u32(1); + /// XCM channel status provider that may report whether it is congested or not. /// /// By channel we mean the physical channel that is used to deliver messages of one @@ -32,3 +39,18 @@ impl XcmChannelStatusProvider for () { false } } + +/// Current status of the bridge. +#[derive(Clone, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct BridgeState { + /// Current delivery fee factor. + pub delivery_fee_factor: FixedU128, + /// Bridge congestion flag. + pub is_congested: bool, +} + +impl Default for BridgeState { + fn default() -> BridgeState { + BridgeState { delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR, is_congested: false } + } +} diff --git a/scripts/update-weights.sh b/scripts/update-weights.sh index fbbbf0790a..155fe62ded 100755 --- a/scripts/update-weights.sh +++ b/scripts/update-weights.sh @@ -65,7 +65,7 @@ time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- --execution=wasm \ --wasm-execution=Compiled \ --heap-pages=4096 \ - --output=./modules/xcm_bridge_hub_router/src/weights.rs \ + --output=./modules/xcm-bridge-hub-router/src/weights.rs \ --template=./.maintain/bridge-weight-template.hbs # weights for Millau runtime. We want to provide runtime weight overhead for messages calls, From 8f86ec78b7747ba32807e8691f022edb4ad3040d Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Thu, 10 Aug 2023 13:48:02 +0000 Subject: [PATCH 30/31] ".git/.scripts/commands/fmt/fmt.sh" --- .../src/messages_xcm_extension.rs | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index a226ca2510..8b8f2a162c 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -169,40 +169,35 @@ where { fn haul_blob(blob: sp_std::prelude::Vec) -> Result<(), HaulBlobError> { let sender_and_lane = H::SenderAndLane::get(); - MessagesPallet::::send_message( - sender_and_lane.lane, - blob, - ) - .map(|artifacts| { - log::info!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "haul_blob result - ok: {:?} on lane: {:?}. Enqueued messages: {}", - artifacts.nonce, - sender_and_lane.lane, - artifacts.enqueued_messages, - ); + MessagesPallet::::send_message(sender_and_lane.lane, blob) + .map(|artifacts| { + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "haul_blob result - ok: {:?} on lane: {:?}. Enqueued messages: {}", + artifacts.nonce, + sender_and_lane.lane, + artifacts.enqueued_messages, + ); - // notify XCM queue manager about updated lane state - LocalXcmQueueManager::::on_bridge_message_enqueued( - &sender_and_lane, - artifacts.enqueued_messages, - ); - }) - .map_err(|error| { - log::error!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "haul_blob result - error: {:?} on lane: {:?}", - error, - sender_and_lane.lane, - ); - HaulBlobError::Transport("MessageSenderError") - }) + // notify XCM queue manager about updated lane state + LocalXcmQueueManager::::on_bridge_message_enqueued( + &sender_and_lane, + artifacts.enqueued_messages, + ); + }) + .map_err(|error| { + log::error!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "haul_blob result - error: {:?} on lane: {:?}", + error, + sender_and_lane.lane, + ); + HaulBlobError::Transport("MessageSenderError") + }) } } -impl OnMessagesDelivered - for XcmBlobHaulerAdapter -{ +impl OnMessagesDelivered for XcmBlobHaulerAdapter { fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { let sender_and_lane = H::SenderAndLane::get(); if sender_and_lane.lane != lane { From 91bf1647f1eefec657acf94141570607fda7a138 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 16 Aug 2023 15:28:29 +0200 Subject: [PATCH 31/31] Added `XcmBridgeHubRouterCall::report_bridge_status` encodings for AHK/P (#2350) * Added `XcmBridgeHubRouterCall::report_bridge_status` encodings for AHK/P * Spellcheck * Added const for `XcmBridgeHubRouterTransactCallMaxWeight` * Cargo.lock * Introduced base delivery fee constants * Congestion messages as Optional to turn on/off `supports_congestion_detection` * Spellcheck * Ability to externalize dest for benchmarks * Ability to externalize dest for benchmarks --- .config/lingua.dic | 3 ++ Cargo.lock | 21 ++++++++ Cargo.toml | 2 + .../src/messages_xcm_extension.rs | 41 +++++++++++----- .../xcm-bridge-hub-router/src/benchmarking.rs | 15 ++++-- primitives/chain-asset-hub-kusama/Cargo.toml | 26 ++++++++++ primitives/chain-asset-hub-kusama/src/lib.rs | 49 +++++++++++++++++++ .../chain-asset-hub-polkadot/Cargo.toml | 26 ++++++++++ .../chain-asset-hub-polkadot/src/lib.rs | 49 +++++++++++++++++++ primitives/xcm-bridge-hub-router/Cargo.toml | 3 +- primitives/xcm-bridge-hub-router/src/lib.rs | 10 ++++ 11 files changed, 229 insertions(+), 16 deletions(-) create mode 100644 primitives/chain-asset-hub-kusama/Cargo.toml create mode 100644 primitives/chain-asset-hub-kusama/src/lib.rs create mode 100644 primitives/chain-asset-hub-polkadot/Cargo.toml create mode 100644 primitives/chain-asset-hub-polkadot/src/lib.rs diff --git a/.config/lingua.dic b/.config/lingua.dic index 9b622aad77..4055f102b1 100644 --- a/.config/lingua.dic +++ b/.config/lingua.dic @@ -16,6 +16,9 @@ Best/MS BlockId BlockNumber BridgeStorage +AssetHub +AssetHubKusama +AssetHubPolkadot BridgeHub BridgeHubRococo BridgeHubWococo diff --git a/Cargo.lock b/Cargo.lock index 40e7f5d6c9..9526407c02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,6 +842,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-beefy" version = "0.1.0" @@ -1169,6 +1189,7 @@ version = "0.1.0" dependencies = [ "parity-scale-codec", "scale-info", + "sp-core", "sp-runtime", ] diff --git a/Cargo.toml b/Cargo.toml index ec92cddb07..17966c1a23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ members = [ "modules/shift-session-manager", "modules/xcm-bridge-hub-router", "primitives/beefy", + "primitives/chain-asset-hub-kusama", + "primitives/chain-asset-hub-polkadot", "primitives/chain-bridge-hub-cumulus", "primitives/chain-bridge-hub-kusama", "primitives/chain-bridge-hub-polkadot", diff --git a/bin/runtime-common/src/messages_xcm_extension.rs b/bin/runtime-common/src/messages_xcm_extension.rs index 8b8f2a162c..44e554ecb2 100644 --- a/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bin/runtime-common/src/messages_xcm_extension.rs @@ -151,10 +151,15 @@ pub trait XcmBlobHauler { /// location (`Self::SenderAndLane::get().location`). type ToSourceChainSender: SendXcm; /// An XCM message that is sent to the sending chain when the bridge queue becomes congested. - type CongestedMessage: Get>; + type CongestedMessage: Get>>; /// An XCM message that is sent to the sending chain when the bridge queue becomes not /// congested. - type UncongestedMessage: Get>; + type UncongestedMessage: Get>>; + + /// Returns `true` if we want to handle congestion. + fn supports_congestion_detection() -> bool { + Self::CongestedMessage::get().is_some() || Self::UncongestedMessage::get().is_some() + } } /// XCM bridge adapter which connects [`XcmBlobHauler`] with [`pallet_bridge_messages`] and @@ -233,6 +238,11 @@ impl LocalXcmQueueManager { sender_and_lane: &SenderAndLane, enqueued_messages: MessageNonce, ) { + // skip if we dont want to handle congestion + if !H::supports_congestion_detection() { + return + } + // if we have already sent the congestion signal, we don't want to do anything if Self::is_congested_signal_sent(sender_and_lane.lane) { return @@ -268,6 +278,11 @@ impl LocalXcmQueueManager { sender_and_lane: &SenderAndLane, enqueued_messages: MessageNonce, ) { + // skip if we dont want to handle congestion + if !H::supports_congestion_detection() { + return + } + // if we have not sent the congestion signal before, we don't want to do anything if !Self::is_congested_signal_sent(sender_and_lane.lane) { return @@ -305,20 +320,24 @@ impl LocalXcmQueueManager { /// Send congested signal to the `sending_chain_location`. fn send_congested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> { - send_xcm::(sender_and_lane.location, H::CongestedMessage::get())?; - OutboundLanesCongestedSignals::::insert( - sender_and_lane.lane, - true, - ); + if let Some(msg) = H::CongestedMessage::get() { + send_xcm::(sender_and_lane.location, msg)?; + OutboundLanesCongestedSignals::::insert( + sender_and_lane.lane, + true, + ); + } Ok(()) } /// Send `uncongested` signal to the `sending_chain_location`. fn send_uncongested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> { - send_xcm::(sender_and_lane.location, H::UncongestedMessage::get())?; - OutboundLanesCongestedSignals::::remove( - sender_and_lane.lane, - ); + if let Some(msg) = H::UncongestedMessage::get() { + send_xcm::(sender_and_lane.location, msg)?; + OutboundLanesCongestedSignals::::remove( + sender_and_lane.lane, + ); + } Ok(()) } } diff --git a/modules/xcm-bridge-hub-router/src/benchmarking.rs b/modules/xcm-bridge-hub-router/src/benchmarking.rs index f8c666eea7..b32b983daf 100644 --- a/modules/xcm-bridge-hub-router/src/benchmarking.rs +++ b/modules/xcm-bridge-hub-router/src/benchmarking.rs @@ -36,6 +36,16 @@ pub struct Pallet, I: 'static = ()>(crate::Pallet); pub trait Config: crate::Config { /// Fill up queue so it becomes congested. fn make_congested(); + + /// Returns destination which is valid for this router instance. + /// (Needs to pass `T::Bridges`) + /// Make sure that `SendXcm` will pass. + fn ensure_bridged_target_destination() -> MultiLocation { + MultiLocation::new( + Self::UniversalLocation::get().len() as u8, + X1(GlobalConsensus(Self::BridgedNetworkId::get().unwrap())), + ) + } } benchmarks_instance_pallet! { @@ -75,10 +85,7 @@ benchmarks_instance_pallet! { // make local queue congested, because it means additional db write T::make_congested(); - let dest = MultiLocation::new( - T::UniversalLocation::get().len() as u8, - X1(GlobalConsensus(T::BridgedNetworkId::get().unwrap())), - ); + let dest = T::ensure_bridged_target_destination(); let xcm = sp_std::vec![].into(); }: { send_xcm::>(dest, xcm).expect("message is sent") diff --git a/primitives/chain-asset-hub-kusama/Cargo.toml b/primitives/chain-asset-hub-kusama/Cargo.toml new file mode 100644 index 0000000000..6d5a4207ee --- /dev/null +++ b/primitives/chain-asset-hub-kusama/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bp-asset-hub-kusama" +description = "Primitives of AssetHubKusama parachain runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } + +# Substrate Dependencies +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Bridge Dependencies +bp-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-xcm-bridge-hub-router/std", + "frame-support/std", + "codec/std", + "scale-info/std", +] diff --git a/primitives/chain-asset-hub-kusama/src/lib.rs b/primitives/chain-asset-hub-kusama/src/lib.rs new file mode 100644 index 0000000000..b3b25ba6ed --- /dev/null +++ b/primitives/chain-asset-hub-kusama/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Module with configuration which reflects AssetHubKusama runtime setup. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; + +/// `AssetHubKusama` Runtime `Call` enum. +/// +/// The enum represents a subset of possible `Call`s we can send to `AssetHubKusama` chain. +/// Ideally this code would be auto-generated from metadata, because we want to +/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s. +/// +/// All entries here (like pretty much in the entire file) must be kept in sync with +/// `AssetHubKusama` `construct_runtime`, so that we maintain SCALE-compatibility. +#[allow(clippy::large_enum_variant)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +pub enum Call { + /// `ToPolkadotXcmRouter` bridge pallet. + #[codec(index = 43)] + ToPolkadotXcmRouter(XcmBridgeHubRouterCall), +} + +frame_support::parameter_types! { + /// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`. + pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); + + /// Base delivery fee to `BridgeHubKusama`. + /// (initially was calculated `170733333` + `10%` by test `BridgeHubKusama::can_calculate_weight_for_paid_export_message_with_reserve_transfer`) + pub const BridgeHubKusamaBaseFeeInDots: u128 = 187806666; +} diff --git a/primitives/chain-asset-hub-polkadot/Cargo.toml b/primitives/chain-asset-hub-polkadot/Cargo.toml new file mode 100644 index 0000000000..4ab562c6b3 --- /dev/null +++ b/primitives/chain-asset-hub-polkadot/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bp-asset-hub-polkadot" +description = "Primitives of AssetHubPolkadot parachain runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } + +# Substrate Dependencies +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Bridge Dependencies +bp-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-xcm-bridge-hub-router/std", + "frame-support/std", + "codec/std", + "scale-info/std", +] diff --git a/primitives/chain-asset-hub-polkadot/src/lib.rs b/primitives/chain-asset-hub-polkadot/src/lib.rs new file mode 100644 index 0000000000..7363e5af02 --- /dev/null +++ b/primitives/chain-asset-hub-polkadot/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Module with configuration which reflects AssetHubPolkadot runtime setup. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; + +/// `AssetHubPolkadot` Runtime `Call` enum. +/// +/// The enum represents a subset of possible `Call`s we can send to `AssetHubPolkadot` chain. +/// Ideally this code would be auto-generated from metadata, because we want to +/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s. +/// +/// All entries here (like pretty much in the entire file) must be kept in sync with +/// `AssetHubPolkadot` `construct_runtime`, so that we maintain SCALE-compatibility. +#[allow(clippy::large_enum_variant)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +pub enum Call { + /// `ToKusamaXcmRouter` bridge pallet. + #[codec(index = 43)] + ToKusamaXcmRouter(XcmBridgeHubRouterCall), +} + +frame_support::parameter_types! { + /// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`. + pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); + + /// Base delivery fee to `BridgeHubPolkadot`. + /// (initially was calculated `51220000` + `10%` by test `BridgeHubPolkadot::can_calculate_weight_for_paid_export_message_with_reserve_transfer`) + pub const BridgeHubPolkadotBaseFeeInDots: u128 = 56342000; +} diff --git a/primitives/xcm-bridge-hub-router/Cargo.toml b/primitives/xcm-bridge-hub-router/Cargo.toml index 611390b95a..ca17f52d16 100644 --- a/primitives/xcm-bridge-hub-router/Cargo.toml +++ b/primitives/xcm-bridge-hub-router/Cargo.toml @@ -11,8 +11,8 @@ codec = { package = "parity-scale-codec", version = "3.1.5", default-features = scale-info = { version = "2.9.0", default-features = false, features = ["bit-vec", "derive"] } # Substrate Dependencies - sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } [features] default = ["std"] @@ -20,4 +20,5 @@ std = [ "codec/std", "scale-info/std", "sp-runtime/std", + "sp-core/std", ] diff --git a/primitives/xcm-bridge-hub-router/src/lib.rs b/primitives/xcm-bridge-hub-router/src/lib.rs index 1f1a92cc75..0dd329f9e4 100644 --- a/primitives/xcm-bridge-hub-router/src/lib.rs +++ b/primitives/xcm-bridge-hub-router/src/lib.rs @@ -20,6 +20,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +use sp_core::H256; use sp_runtime::{FixedU128, RuntimeDebug}; /// Minimal delivery fee factor. @@ -54,3 +55,12 @@ impl Default for BridgeState { BridgeState { delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR, is_congested: false } } } + +/// A minimized version of `pallet-xcm-bridge-hub-router::Call` that can be used without a runtime. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum XcmBridgeHubRouterCall { + /// `pallet-xcm-bridge-hub-router::Call::report_bridge_status` + #[codec(index = 0)] + report_bridge_status { bridge_id: H256, is_congested: bool }, +}