diff --git a/Cargo.lock b/Cargo.lock index cbc72cac54..21eaf611a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1270,6 +1270,10 @@ dependencies = [ "sp-transaction-pool", "sp-version", "substrate-wasm-builder 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "xcm", + "xcm-builder", + "xcm-executor", + "xcm-handler", ] [[package]] @@ -10551,6 +10555,18 @@ dependencies = [ "xcm", ] +[[package]] +name = "xcm-handler" +version = "0.1.0" +dependencies = [ + "cumulus-primitives", + "frame-support", + "frame-system", + "parity-scale-codec", + "sp-std", + "xcm", +] + [[package]] name = "yamux" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 1606db4a4d..b935b7ea0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "test/runtime", "test/client", "test/service", + "xcm-handler", ] [profile.release] diff --git a/message-broker/src/lib.rs b/message-broker/src/lib.rs index fac5b4261b..eec8f2570c 100644 --- a/message-broker/src/lib.rs +++ b/message-broker/src/lib.rs @@ -34,7 +34,7 @@ use sp_std::{cmp, prelude::*}; use cumulus_primitives::{ inherents::{MessageIngestionType, MESSAGE_INGESTION_IDENTIFIER}, well_known_keys, DownwardMessageHandler, HrmpMessageHandler, OutboundHrmpMessage, ParaId, - UpwardMessage, + UpwardMessage, UpwardMessageSender, HrmpMessageSender, }; // TODO: these should be not a constant, but sourced from the relay-chain configuration. @@ -201,7 +201,7 @@ pub enum SendUpErr { } /// An error that can be raised upon sending a horizontal message. -pub enum SendHorizonalErr { +pub enum SendHorizontalErr { /// The message sent is too big. TooBig, /// There is no channel to the specified destination. @@ -216,7 +216,7 @@ impl Module { Ok(()) } - pub fn send_hrmp_message(message: OutboundHrmpMessage) -> Result<(), SendHorizonalErr> { + pub fn send_hrmp_message(message: OutboundHrmpMessage) -> Result<(), SendHorizontalErr> { // TODO: // (a) check against the size limit sourced from the relay-chain configuration // (b) check if the channel to the recipient is actually opened. @@ -234,6 +234,18 @@ impl Module { } } +impl UpwardMessageSender for Module { + fn send_upward_message(message: UpwardMessage) -> Result<(), ()> { + Self::send_upward_message(message).map_err(|_| ()) + } +} + +impl HrmpMessageSender for Module { + fn send_hrmp_message(message: OutboundHrmpMessage) -> Result<(), ()> { + Self::send_hrmp_message(message).map_err(|_| ()) + } +} + impl ProvideInherent for Module { type Call = Call; type Error = MakeFatalError<()>; diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 3ef5dbeb1b..645e622644 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -105,6 +105,18 @@ pub trait HrmpMessageHandler { fn handle_hrmp_message(sender: ParaId, msg: InboundHrmpMessage); } +/// Something that should be called when sending an upward message. +pub trait UpwardMessageSender { + /// Send the given upward message. + fn send_upward_message(msg: UpwardMessage) -> Result<(), ()>; +} + +/// Something that should be called when sending an HRMP message. +pub trait HrmpMessageSender { + /// Send the given HRMP message. + fn send_hrmp_message(msg: OutboundHrmpMessage) -> Result<(), ()>; +} + /// A trait which is called when the validation data is set. #[impl_trait_for_tuples::impl_for_tuples(30)] pub trait OnValidationData { diff --git a/rococo-parachains/runtime/Cargo.toml b/rococo-parachains/runtime/Cargo.toml index d7b1136f3a..f035ebbac6 100644 --- a/rococo-parachains/runtime/Cargo.toml +++ b/rococo-parachains/runtime/Cargo.toml @@ -38,9 +38,13 @@ cumulus-runtime = { path = "../../runtime", default-features = false } cumulus-parachain-upgrade = { path = "../../parachain-upgrade", default-features = false } cumulus-primitives = { path = "../../primitives", default-features = false } cumulus-message-broker = { path = "../../message-broker", default-features = false } +xcm-handler = { path = "../../xcm-handler", default-features = false } # Polkadot dependencies polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "master" } +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "master" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "master" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "master" } [build-dependencies] substrate-wasm-builder = "3.0.0" @@ -75,4 +79,8 @@ std = [ "cumulus-parachain-upgrade/std", "cumulus-message-broker/std", "cumulus-primitives/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm-handler/std", ] diff --git a/rococo-parachains/runtime/src/lib.rs b/rococo-parachains/runtime/src/lib.rs index f2a37ccade..b75d111d30 100644 --- a/rococo-parachains/runtime/src/lib.rs +++ b/rococo-parachains/runtime/src/lib.rs @@ -53,6 +53,19 @@ pub use pallet_timestamp::Call as TimestampCall; pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; +// XCM imports +use polkadot_parachain::primitives::Sibling; +use xcm::v0::{MultiLocation, NetworkId, Junction}; +use xcm_builder::{ + ParentIsDefault, SiblingParachainConvertsVia, AccountId32Aliases, LocationInverter, + SovereignSignedViaLocation, SiblingParachainAsNative, + RelayChainAsNative, SignedAccountId32AsNative, CurrencyAdapter +}; +use xcm_executor::{ + XcmExecutor, Config, + traits::{NativeAsset, IsConcrete}, +}; + pub type SessionHandlers = (); impl_opaque_keys! { @@ -226,6 +239,59 @@ impl cumulus_message_broker::Config for Runtime { impl parachain_info::Config for Runtime {} +parameter_types! { + pub const RococoLocation: MultiLocation = MultiLocation::X1(Junction::Parent); + pub const RococoNetwork: NetworkId = NetworkId::Polkadot; + pub RelayChainOrigin: Origin = xcm_handler::Origin::Relay.into(); + pub Ancestry: MultiLocation = Junction::Parachain { + id: ParachainInfo::parachain_id().into() + }.into(); +} + +type LocationConverter = ( + ParentIsDefault, + SiblingParachainConvertsVia, + AccountId32Aliases, +); + +type LocalAssetTransactor = + CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + LocationConverter, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + >; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + RelayChainAsNative, + SiblingParachainAsNative, + SignedAccountId32AsNative, +); + +pub struct XcmConfig; +impl Config for XcmConfig { + type Call = Call; + type XcmSender = XcmHandler; + // How to withdraw and deposit an asset. + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = NativeAsset; + type IsTeleporter = (); + type LocationInverter = LocationInverter; +} + +impl xcm_handler::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; + type UpwardMessageSender = MessageBroker; + type HrmpMessageSender = MessageBroker; +} + construct_runtime! { pub enum Runtime where Block = Block, @@ -241,6 +307,7 @@ construct_runtime! { MessageBroker: cumulus_message_broker::{Module, Storage, Call, Inherent}, TransactionPayment: pallet_transaction_payment::{Module, Storage}, ParachainInfo: parachain_info::{Module, Storage, Config}, + XcmHandler: xcm_handler::{Module, Event, Origin}, } } diff --git a/xcm-handler/Cargo.toml b/xcm-handler/Cargo.toml new file mode 100644 index 0000000000..d403ac0369 --- /dev/null +++ b/xcm-handler/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "xcm-handler" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +# Other dependencies +codec = { package = "parity-scale-codec", version = "1.3.0", features = [ "derive" ], default-features = false } + +# Substrate Dependencies +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" } + +# Polkadot Dependencies +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } + +# Cumulus Dependencies +cumulus-primitives = { path = "../primitives", default-features = false } + +[features] +default = [ "std" ] +std = [ + "codec/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "cumulus-primitives/std", + "xcm/std" +] diff --git a/xcm-handler/src/lib.rs b/xcm-handler/src/lib.rs new file mode 100644 index 0000000000..0295ca365b --- /dev/null +++ b/xcm-handler/src/lib.rs @@ -0,0 +1,199 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 Cumulus. If not, see . + +//! A pallet which implements the message-broker APIs for handling incoming XCM: +//! * `DownwardMessageHandler` +//! * `HrmpMessageHandler` +//! +//! Also provides an implementation of `SendXcm` to handle outgoing XCM. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Encode, Decode}; +use sp_std::convert::{TryFrom, TryInto}; +use frame_support::{ + decl_module, decl_event, decl_error, + sp_runtime::traits::Hash, +}; +use frame_system::ensure_root; +use cumulus_primitives::{ + ParaId, InboundHrmpMessage, InboundDownwardMessage, OutboundHrmpMessage, + DownwardMessageHandler, HrmpMessageHandler, UpwardMessageSender, HrmpMessageSender, +}; +use xcm::{ + VersionedXcm, + v0::{Xcm, MultiLocation, Error as XcmError, Junction, SendXcm, ExecuteXcm} +}; + +pub trait Config: frame_system::Config { + type Event: From> + Into<::Event>; + /// Something to execute an XCM message. + type XcmExecutor: ExecuteXcm; + /// Something to send an upward message. + type UpwardMessageSender: UpwardMessageSender; + /// Something to send an HRMP message. + type HrmpMessageSender: HrmpMessageSender; +} + +decl_event! { + pub enum Event where Hash = ::Hash { + /// Some XCM was executed ok. + Success(Hash), + /// Some XCM failed. + Fail(Hash, XcmError), + /// Bad XCM version used. + BadVersion(Hash), + /// Bad XCM format used. + BadFormat(Hash), + /// An upward message was sent to the relay chain. + UpwardMessageSent(Hash), + /// An HRMP message was sent to a sibling parachainchain. + HrmpMessageSent(Hash), + } +} + +decl_error! { + pub enum Error for Module { + /// Failed to send XCM message. + FailedToSend, + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + fn deposit_event() = default; + + #[weight = 1_000] + fn sudo_send_xcm(origin, dest: MultiLocation, message: Xcm) { + ensure_root(origin)?; + Self::send_xcm(dest, message).map_err(|_| Error::::FailedToSend)?; + } + + #[weight = 1_000] + fn sudo_send_upward_xcm(origin, message: VersionedXcm) { + ensure_root(origin)?; + let data = message.encode(); + T::UpwardMessageSender::send_upward_message(data).map_err(|_| Error::::FailedToSend)?; + } + + #[weight = 1_000] + fn sudo_send_hrmp_xcm(origin, recipient: ParaId, message: VersionedXcm) { + ensure_root(origin)?; + let data = message.encode(); + let outbound_message = OutboundHrmpMessage { + recipient, + data, + }; + T::HrmpMessageSender::send_hrmp_message(outbound_message).map_err(|_| Error::::FailedToSend)?; + } + } +} + +impl DownwardMessageHandler for Module { + fn handle_downward_message(msg: InboundDownwardMessage) { + let hash = msg.using_encoded(T::Hashing::hash); + frame_support::debug::print!("Processing Downward XCM: {:?}", &hash); + match VersionedXcm::decode(&mut &msg.msg[..]).map(Xcm::try_from) { + Ok(Ok(xcm)) => { + match T::XcmExecutor::execute_xcm(Junction::Parent.into(), xcm) { + Ok(..) => RawEvent::Success(hash), + Err(e) => RawEvent::Fail(hash, e), + }; + } + Ok(Err(..)) => Self::deposit_event(RawEvent::BadVersion(hash)), + Err(..) => Self::deposit_event(RawEvent::BadFormat(hash)), + } + } +} + +impl HrmpMessageHandler for Module { + fn handle_hrmp_message(sender: ParaId, msg: InboundHrmpMessage) { + let hash = msg.using_encoded(T::Hashing::hash); + frame_support::debug::print!("Processing HRMP XCM: {:?}", &hash); + match VersionedXcm::decode(&mut &msg.data[..]).map(Xcm::try_from) { + Ok(Ok(xcm)) => { + match T::XcmExecutor::execute_xcm(Junction::Parachain { id: sender.into() }.into(), xcm) { + Ok(..) => RawEvent::Success(hash), + Err(e) => RawEvent::Fail(hash, e), + }; + } + Ok(Err(..)) => Self::deposit_event(RawEvent::BadVersion(hash)), + Err(..) => Self::deposit_event(RawEvent::BadFormat(hash)), + } + } +} + +impl SendXcm for Module { + fn send_xcm(dest: MultiLocation, msg: Xcm) -> Result<(), XcmError> { + let msg: VersionedXcm = msg.into(); + match dest.first() { + // A message for us. Execute directly. + None => { + let msg = msg.try_into().map_err(|_| XcmError::UnhandledXcmVersion)?; + let res = T::XcmExecutor::execute_xcm(MultiLocation::Null, msg); + res + } + // An upward message for the relay chain. + Some(Junction::Parent) if dest.len() == 1 => { + let data = msg.encode(); + let hash = T::Hashing::hash(&data); + + T::UpwardMessageSender::send_upward_message(data).map_err(|_| XcmError::Undefined)?; + Self::deposit_event(RawEvent::UpwardMessageSent(hash)); + + Ok(()) + } + // An HRMP message for a sibling parachain. + Some(Junction::Parachain { id }) => { + let data = msg.encode(); + let hash = T::Hashing::hash(&data); + let message = OutboundHrmpMessage { + recipient: (*id).into(), + data, + }; + // TODO: Better error here + T::HrmpMessageSender::send_hrmp_message(message).map_err(|_| XcmError::Undefined)?; + Self::deposit_event(RawEvent::HrmpMessageSent(hash)); + Ok(()) + } + _ => { + /* TODO: Handle other cases, like downward message */ + Err(XcmError::UnhandledXcmMessage) + } + } + } +} + +/// Origin for the parachains module. +#[derive(PartialEq, Eq, Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Origin { + /// It comes from the (parent) relay chain. + Relay, + /// It comes from a (sibling) parachain. + SiblingParachain(ParaId), +} + +impl From for Origin { + fn from(id: ParaId) -> Origin { + Origin::SiblingParachain(id) + } +} +impl From for Origin { + fn from(id: u32) -> Origin { + Origin::SiblingParachain(id.into()) + } +}