diff --git a/modules/xcm-bridge-hub/Cargo.toml b/modules/xcm-bridge-hub/Cargo.toml index 3d50b04c35..bbcf630d13 100644 --- a/modules/xcm-bridge-hub/Cargo.toml +++ b/modules/xcm-bridge-hub/Cargo.toml @@ -29,13 +29,13 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", d # Polkadot Dependencies xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master" } xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } [dev-dependencies] pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "master" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } -xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master" } [features] default = ["std"] @@ -52,6 +52,7 @@ std = [ "sp-runtime/std", "sp-std/std", "xcm/std", + "xcm-builder/std", "xcm-executor/std", ] runtime-benchmarks = [] diff --git a/modules/xcm-bridge-hub/src/dispatcher.rs b/modules/xcm-bridge-hub/src/dispatcher.rs new file mode 100644 index 0000000000..0d0662fdc1 --- /dev/null +++ b/modules/xcm-bridge-hub/src/dispatcher.rs @@ -0,0 +1,103 @@ +// 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 . + +//! The code that allows to use the pallet (`pallet-xcm-bridge-hub`) as inbound +//! bridge messages dispatcher. Internally, it just forwards inbound blob to the +//! XCM-level blob dispatcher, which pushes message to some other queue (e.g. +//! to HRMP queue with the sibling target chain). + +use crate::{Config, Pallet, XcmAsPlainPayload, LOG_TARGET}; + +use bp_messages::target_chain::{DispatchMessage, MessageDispatch}; +use bp_runtime::messages::MessageDispatchResult; +use codec::{Decode, Encode}; +use frame_support::{dispatch::Weight, CloneNoBound, EqNoBound, PartialEqNoBound}; +use pallet_bridge_messages::{Config as BridgeMessagesConfig, WeightInfoExt}; +use scale_info::TypeInfo; +use sp_runtime::SaturatedConversion; +use xcm_builder::{DispatchBlob, DispatchBlobError}; + +/// Message dispatch result type for single message. +#[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, Debug, TypeInfo)] +pub enum XcmBlobMessageDispatchResult { + /// We've been unable to decode message payload. + InvalidPayload, + /// Message has been dispatched. + Dispatched, + /// Message has **NOT** been dispatched because of given error. + NotDispatched(#[codec(skip)] Option), +} + +/// An easy way to access associated messages pallet weights. +type MessagesPalletWeights = + >::BridgeMessagesPalletInstance>>::WeightInfo; + +impl, I: 'static> MessageDispatch for Pallet +where + T: BridgeMessagesConfig, +{ + type DispatchPayload = XcmAsPlainPayload; + type DispatchLevelResult = XcmBlobMessageDispatchResult; + + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { + match message.data.payload { + Ok(ref payload) => { + let payload_size = payload.encoded_size().saturated_into(); + MessagesPalletWeights::::message_dispatch_weight(payload_size) + }, + Err(_) => Weight::zero(), + } + } + + fn dispatch( + message: DispatchMessage, + ) -> MessageDispatchResult { + let payload = match message.data.payload { + Ok(payload) => payload, + Err(e) => { + log::error!( + target: LOG_TARGET, + "[XcmBlobMessageDispatch] payload error: {:?} - message_nonce: {:?}", + e, + message.key.nonce + ); + return MessageDispatchResult { + unspent_weight: Weight::zero(), + dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload, + } + }, + }; + let dispatch_level_result = match T::BlobDispatcher::dispatch_blob(payload) { + Ok(_) => { + log::debug!( + target: LOG_TARGET, + "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob was ok - message_nonce: {:?}", + message.key.nonce + ); + XcmBlobMessageDispatchResult::Dispatched + }, + Err(e) => { + log::error!( + target: LOG_TARGET, + "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?} - message_nonce: {:?}", + e, message.key.nonce + ); + XcmBlobMessageDispatchResult::NotDispatched(Some(e)) + }, + }; + MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result } + } +} diff --git a/modules/xcm-bridge-hub/src/exporter.rs b/modules/xcm-bridge-hub/src/exporter.rs new file mode 100644 index 0000000000..5eab79b38a --- /dev/null +++ b/modules/xcm-bridge-hub/src/exporter.rs @@ -0,0 +1,184 @@ +// 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 . + +//! The code that allows to use the pallet (`pallet-xcm-bridge-hub`) as XCM message +//! exporter at the sending bridge hub. Internally, it just enqueues outbound blob +//! in the messages pallet queue. + +use crate::{Config, Pallet, XcmAsPlainPayload, LOG_TARGET}; + +use bp_messages::{source_chain::MessagesBridge, LaneId}; +use frame_support::traits::Get; +use pallet_bridge_messages::{Config as BridgeMessagesConfig, Pallet as BridgeMessagesPallet}; +use xcm::prelude::*; +use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter}; +use xcm_executor::traits::ExportXcm; + +// An easy way to access `HaulBlobExporter`. +type PalletAsHaulBlobExporter = HaulBlobExporter< + DummyHaulBlob, + >::BridgedNetworkId, + >::MessageExportPrice, +>; +/// An easy way to access associated messages pallet. +type MessagesPallet = BridgeMessagesPallet>::BridgeMessagesPalletInstance>; + +impl, I: 'static> ExportXcm for Pallet +where + T: BridgeMessagesConfig< + >::BridgeMessagesPalletInstance, + OutboundPayload = XcmAsPlainPayload, + >, +{ + type Ticket = (LaneId, XcmAsPlainPayload, XcmHash); + + fn validate( + network: NetworkId, + channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> Result<(Self::Ticket, MultiAssets), SendError> { + // `HaulBlobExporter` may consume the `universal_source` and `destination` arguments, so + // let's save them before + let bridge_origin_universal_location = + universal_source.clone().take().ok_or(SendError::MissingArgument)?; + let bridge_destination_interior_location = + destination.clone().take().ok_or(SendError::MissingArgument)?; + + // check if we are able to route the message. We use existing `HaulBlobExporter` for that. + // It will make all required changes and will encode message properly, so that the + // `DispatchBlob` at the bridged bridge hub will be able to decode it + let ((blob, id), price) = PalletAsHaulBlobExporter::::validate( + network, + channel, + universal_source, + destination, + message, + )?; + + // ok - now we know that the message may be routed by the pallet, let's prepare the + // destination universal location + let mut bridge_destination_universal_location = + X1(GlobalConsensus(T::BridgedNetworkId::get())); + bridge_destination_universal_location + .append_with(bridge_destination_interior_location) + .map_err(|_| SendError::Unroutable)?; + + // .. and the origin relative location + let bridge_origin_relative_location = + bridge_origin_universal_location.relative_to(&T::UniversalLocation::get()); + + // then we are able to compute the lane id used to send messages + let bridge_locations = Self::bridge_locations( + Box::new(bridge_origin_relative_location), + Box::new(bridge_destination_universal_location.into()), + ) + .map_err(|_| SendError::Unroutable)?; + + Ok(((bridge_locations.lane_id, blob, id), price)) + } + + fn deliver( + (lane_id, blob, id): (LaneId, XcmAsPlainPayload, XcmHash), + ) -> Result { + let send_result = MessagesPallet::::send_message(lane_id, blob); + + match send_result { + Ok(artifacts) => { + log::info!( + target: LOG_TARGET, + "XCM message {:?} has been enqueued at lane {:?} with nonce {}", + id, + lane_id, + artifacts.nonce, + ); + }, + Err(error) => { + log::debug!( + target: LOG_TARGET, + "XCM message {:?} has been dropped because of bridge error {:?} on lane {:?}", + id, + error, + lane_id, + ); + return Err(SendError::Transport("BridgeSendError")) + }, + } + + Ok(id) + } +} + +/// Dummy implementation of the `HaulBlob` trait that is never called. +/// +/// We are using `HaulBlobExporter`, which requires `HaulBlob` implementation. It assumes that +/// there's a single channel between two bridge hubs - `HaulBlob` only accepts the blob and nothing +/// else. But bridge messages pallet may have a dedicated channel (lane) for every pair of bridged +/// chains. So we are using our own `ExportXcm` implementation, but to utilize `HaulBlobExporter` we +/// still need this `DummyHaulBlob`. +struct DummyHaulBlob; + +impl HaulBlob for DummyHaulBlob { + fn haul_blob(_blob: XcmAsPlainPayload) -> Result<(), HaulBlobError> { + Err(HaulBlobError::Transport("DummyHaulBlob")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{mock::*, LanesManagerOf}; + + use xcm_executor::traits::export_xcm; + + #[test] + fn proper_lane_is_used_by_export_xcm() { + run_test(|| { + // open expected outbound lane + let origin = OpenBridgeOrigin::sibling_parachain_origin(); + let with = bridged_asset_hub_location(); + let locations = + XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap(); + + let lanes_manager = LanesManagerOf::::new(); + lanes_manager.create_outbound_lane(locations.lane_id).unwrap(); + assert!(lanes_manager + .active_outbound_lane(locations.lane_id) + .unwrap() + .queued_messages() + .is_empty()); + + // now let's try to enqueue message using our `ExportXcm` implementation + export_xcm::( + BridgedRelayNetwork::get(), + 0, + locations.bridge_origin_universal_location, + locations.bridge_destination_universal_location.split_first().0, + vec![Instruction::ClearOrigin].into(), + ) + .unwrap(); + + // double check that the message has been pushed to the expected lane + // (it should already been checked during `send_message` call) + assert!(!lanes_manager + .active_outbound_lane(locations.lane_id) + .unwrap() + .queued_messages() + .is_empty()); + }); + } +} diff --git a/modules/xcm-bridge-hub/src/lib.rs b/modules/xcm-bridge-hub/src/lib.rs index 2516b95c6c..1f23df7f30 100644 --- a/modules/xcm-bridge-hub/src/lib.rs +++ b/modules/xcm-bridge-hub/src/lib.rs @@ -61,12 +61,20 @@ use frame_system::Config as SystemConfig; use pallet_bridge_messages::{Config as BridgeMessagesConfig, LanesManagerError}; use sp_runtime::traits::{Header as HeaderT, HeaderProvider, Zero}; use xcm::prelude::*; +use xcm_builder::DispatchBlob; use xcm_executor::traits::ConvertLocation; +pub use dispatcher::XcmBlobMessageDispatchResult; pub use pallet::*; +mod dispatcher; +mod exporter; mod mock; +/// Encoded XCM blob. We expect the bridge messages pallet to use this blobtype for both inbound +/// and outbound payloads. +pub type XcmAsPlainPayload = Vec; + /// The target that will be used when publishing logs related to this pallet. pub const LOG_TARGET: &str = "runtime::bridge-xcm"; @@ -115,6 +123,11 @@ pub mod pallet { type BridgeReserve: Get>>; /// Currency used to pay for bridge registration. type NativeCurrency: ReservableCurrency; + + /// XCM-level dispatcher for inbound bridge messages. + type BlobDispatcher: DispatchBlob; + /// Price of single message export. + type MessageExportPrice: Get; } /// An alias for the bridge metadata. @@ -155,7 +168,8 @@ pub mod pallet { bridge_destination_universal_location: Box, ) -> DispatchResult { // check and compute required bridge locations - let locations = Self::bridge_locations(origin, bridge_destination_universal_location)?; + let locations = + Self::bridge_locations_from_origin(origin, bridge_destination_universal_location)?; // reserve balance on the parachain sovereign account let reserve = T::BridgeReserve::get(); @@ -235,7 +249,8 @@ pub mod pallet { may_prune_messages: MessageNonce, ) -> DispatchResult { // compute required bridge locations - let locations = Self::bridge_locations(origin, bridge_destination_universal_location)?; + let locations = + Self::bridge_locations_from_origin(origin, bridge_destination_universal_location)?; // TODO: https://github.com/paritytech/parity-bridges-common/issues/1760 - may do refund here, if // bridge/lanes are already closed + for messages that are not pruned @@ -335,21 +350,28 @@ pub mod pallet { } } - impl, I: 'static> Pallet - where - T: frame_system::Config>>, - <::Block as HeaderProvider>::HeaderT: - HeaderT>>, - T::NativeCurrency: Currency>>, - { + impl, I: 'static> Pallet { + /// Return bridge endpoint locations and dedicated lane identifier. This method converts + /// runtime `origin` argument to relative `MultiLocation` using the `T::OpenBridgeOrigin` + /// converter. + pub fn bridge_locations_from_origin( + origin: OriginFor, + bridge_destination_universal_location: Box, + ) -> Result, sp_runtime::DispatchError> { + Self::bridge_locations( + Box::new(T::OpenBridgeOrigin::ensure_origin(origin)?), + bridge_destination_universal_location, + ) + } + /// Return bridge endpoint locations and dedicated lane identifier. pub fn bridge_locations( - origin: OriginFor, + bridge_origin_relative_location: Box, bridge_destination_universal_location: Box, ) -> Result, sp_runtime::DispatchError> { bridge_locations( Box::new(T::UniversalLocation::get()), - Box::new(T::OpenBridgeOrigin::ensure_origin(origin)?), + bridge_origin_relative_location, Box::new( (*bridge_destination_universal_location) .try_into() @@ -439,7 +461,8 @@ mod tests { with: InteriorMultiLocation, ) -> (BridgeOf, BridgeLocations) { let reserve = BridgeReserve::get(); - let locations = XcmOverBridge::bridge_locations(origin, Box::new(with.into())).unwrap(); + let locations = + XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap(); let bridge_owner_account = fund_origin_sovereign_account(&locations, reserve + ExistentialDeposit::get()); Balances::reserve(&bridge_owner_account, reserve).unwrap(); @@ -581,7 +604,7 @@ mod tests { fn open_bridge_fails_if_it_already_exists() { run_test(|| { let origin = OpenBridgeOrigin::parent_relay_chain_origin(); - let locations = XcmOverBridge::bridge_locations( + let locations = XcmOverBridge::bridge_locations_from_origin( origin.clone(), Box::new(bridged_asset_hub_location().into()), ) @@ -614,7 +637,7 @@ mod tests { fn open_bridge_fails_if_its_lanes_already_exists() { run_test(|| { let origin = OpenBridgeOrigin::parent_relay_chain_origin(); - let locations = XcmOverBridge::bridge_locations( + let locations = XcmOverBridge::bridge_locations_from_origin( origin.clone(), Box::new(bridged_asset_hub_location().into()), ) @@ -666,7 +689,7 @@ mod tests { System::reset_events(); // compute all other locations - let locations = XcmOverBridge::bridge_locations( + let locations = XcmOverBridge::bridge_locations_from_origin( origin.clone(), Box::new(bridged_asset_hub_location().into()), ) diff --git a/modules/xcm-bridge-hub/src/mock.rs b/modules/xcm-bridge-hub/src/mock.rs index 64300dfb6f..79d96a29d0 100644 --- a/modules/xcm-bridge-hub/src/mock.rs +++ b/modules/xcm-bridge-hub/src/mock.rs @@ -35,7 +35,7 @@ use sp_runtime::{ AccountId32, BuildStorage, }; use xcm::prelude::*; -use xcm_builder::{ParentIsPreset, SiblingParachainConvertsVia}; +use xcm_builder::{DispatchBlob, DispatchBlobError, ParentIsPreset, SiblingParachainConvertsVia}; pub type AccountId = AccountId32; pub type Balance = u64; @@ -219,6 +219,17 @@ impl pallet_xcm_bridge_hub::Config for TestRuntime { type BridgeReserve = BridgeReserve; type NativeCurrency = Balances; + + type BlobDispatcher = TestBlobDispatcher; + type MessageExportPrice = (); +} + +pub struct TestBlobDispatcher; + +impl DispatchBlob for TestBlobDispatcher { + fn dispatch_blob(_blob: Vec) -> Result<(), DispatchBlobError> { + Ok(()) + } } pub struct ThisChain;