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;