diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs index fc2f19b94588..97f49c2f6ffc 100644 --- a/bridges/bin/millau/runtime/src/lib.rs +++ b/bridges/bin/millau/runtime/src/lib.rs @@ -88,6 +88,7 @@ pub use pallet_bridge_messages::Call as MessagesCall; pub use pallet_bridge_parachains::Call as BridgeParachainsCall; pub use pallet_sudo::Call as SudoCall; pub use pallet_timestamp::Call as TimestampCall; +pub use pallet_xcm::Call as XcmCall; use bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages; #[cfg(any(feature = "std", test))] diff --git a/bridges/bin/millau/runtime/src/rialto_messages.rs b/bridges/bin/millau/runtime/src/rialto_messages.rs index da44bcf8f8fe..ccca4222f61d 100644 --- a/bridges/bin/millau/runtime/src/rialto_messages.rs +++ b/bridges/bin/millau/runtime/src/rialto_messages.rs @@ -37,6 +37,8 @@ use scale_info::TypeInfo; use sp_runtime::{traits::Saturating, FixedPointNumber, FixedU128}; use sp_std::convert::TryFrom; +/// Default lane that is used to send messages to Rialto. +pub const DEFAULT_XCM_LANE_TO_RIALTO: LaneId = [0, 0, 0, 0]; /// Initial value of `RialtoToMillauConversionRate` parameter. pub const INITIAL_RIALTO_TO_MILLAU_CONVERSION_RATE: FixedU128 = FixedU128::from_inner(FixedU128::DIV); @@ -149,7 +151,7 @@ impl messages::ThisChainWithMessages for Millau { }, } - *lane == [0, 0, 0, 0] || *lane == [0, 0, 0, 1] + *lane == DEFAULT_XCM_LANE_TO_RIALTO || *lane == [0, 0, 0, 1] } fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { diff --git a/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs b/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs index ef1dac57d838..6840b703f4f4 100644 --- a/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs +++ b/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -38,6 +38,8 @@ use scale_info::TypeInfo; use sp_runtime::{traits::Saturating, FixedPointNumber, FixedU128}; use sp_std::convert::TryFrom; +/// Default lane that is used to send messages to Rialto parachain. +pub const DEFAULT_XCM_LANE_TO_RIALTO_PARACHAIN: LaneId = [0, 0, 0, 0]; /// Weight of 2 XCM instructions is for simple `Trap(42)` program, coming through bridge /// (it is prepended with `UniversalOrigin` instruction). It is used just for simplest manual /// tests, confirming that we don't break encoding somewhere between. @@ -138,7 +140,7 @@ impl messages::ThisChainWithMessages for Millau { >; fn is_message_accepted(_send_origin: &Self::Origin, lane: &LaneId) -> bool { - *lane == [0, 0, 0, 0] || *lane == [0, 0, 0, 1] + *lane == DEFAULT_XCM_LANE_TO_RIALTO_PARACHAIN || *lane == [0, 0, 0, 1] } fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { diff --git a/bridges/bin/millau/runtime/src/xcm_config.rs b/bridges/bin/millau/runtime/src/xcm_config.rs index 5ba8d97e65e6..19fb68b66265 100644 --- a/bridges/bin/millau/runtime/src/xcm_config.rs +++ b/bridges/bin/millau/runtime/src/xcm_config.rs @@ -17,22 +17,22 @@ //! XCM configurations for the Millau runtime. use super::{ - rialto_messages::WithRialtoMessageBridge, AccountId, AllPalletsWithSystem, Balances, - BridgeRialtoMessages, Call, Event, Origin, Runtime, XcmPallet, + rialto_messages::{WithRialtoMessageBridge, DEFAULT_XCM_LANE_TO_RIALTO}, + rialto_parachain_messages::{ + WithRialtoParachainMessageBridge, DEFAULT_XCM_LANE_TO_RIALTO_PARACHAIN, + }, + AccountId, AllPalletsWithSystem, Balances, Call, Event, Origin, Runtime, + WithRialtoMessagesInstance, WithRialtoParachainMessagesInstance, XcmPallet, }; -use bp_messages::source_chain::MessagesBridge; -use bp_millau::{Balance, WeightToFee}; -use bridge_runtime_common::messages::{ - source::{estimate_message_dispatch_and_delivery_fee, FromThisChainMessagePayload}, - MessageBridge, -}; -use codec::Encode; +use bp_messages::LaneId; +use bp_millau::WeightToFee; +use bp_rialto_parachain::RIALTO_PARACHAIN_ID; +use bridge_runtime_common::messages::source::{XcmBridge, XcmBridgeAdapter}; use frame_support::{ parameter_types, traits::{Everything, Nothing}, weights::Weight, }; -use sp_std::marker::PhantomData; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, @@ -49,6 +49,8 @@ parameter_types! { pub const ThisNetwork: NetworkId = Kusama; /// The Rialto network ID, associated with Polkadot. pub const RialtoNetwork: NetworkId = Polkadot; + /// The RialtoParachain network ID, associated with Westend. + pub const RialtoParachainNetwork: NetworkId = Westend; /// Our XCM location ancestry - i.e. our location within the Consensus Universe. /// @@ -105,7 +107,9 @@ parameter_types! { /// individual routers. pub type XcmRouter = ( // Router to send messages to Rialto. - ToRialtoBridge, + XcmBridgeAdapter, + // Router to send messages to RialtoParachains. + XcmBridgeAdapter, ); parameter_types! { @@ -189,60 +193,56 @@ impl pallet_xcm::Config for Runtime { type MaxLockers = frame_support::traits::ConstU32<8>; } -/// With-rialto bridge. -pub struct ToRialtoBridge(PhantomData); - -impl> SendXcm - for ToRialtoBridge -{ - type Ticket = (Balance, FromThisChainMessagePayload); - - fn validate( - dest: &mut Option, - msg: &mut Option>, - ) -> SendResult { - let d = dest.take().ok_or(SendError::MissingArgument)?; - if !matches!(d, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == RialtoNetwork::get()) - { - *dest = Some(d); - return Err(SendError::NotApplicable) - }; +/// With-Rialto bridge. +pub struct ToRialtoBridge; + +impl XcmBridge for ToRialtoBridge { + type MessageBridge = WithRialtoMessageBridge; + type MessageSender = pallet_bridge_messages::Pallet; + + fn universal_location() -> InteriorMultiLocation { + UniversalLocation::get() + } + + fn verify_destination(dest: &MultiLocation) -> bool { + matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == RialtoNetwork::get()) + } + fn build_destination() -> MultiLocation { let dest: InteriorMultiLocation = RialtoNetwork::get().into(); let here = UniversalLocation::get(); - let route = dest.relative_to(&here); - let msg = (route, msg.take().unwrap()).encode(); + dest.relative_to(&here) + } - let fee = estimate_message_dispatch_and_delivery_fee::( - &msg, - WithRialtoMessageBridge::RELAYER_FEE_PERCENT, - None, - ) - .map_err(SendError::Transport)?; - let fee_assets = MultiAssets::from((Here, fee)); + fn xcm_lane() -> LaneId { + DEFAULT_XCM_LANE_TO_RIALTO + } +} + +/// With-RialtoParachain bridge. +pub struct ToRialtoParachainBridge; + +impl XcmBridge for ToRialtoParachainBridge { + type MessageBridge = WithRialtoParachainMessageBridge; + type MessageSender = + pallet_bridge_messages::Pallet; - Ok(((fee, msg), fee_assets)) + fn universal_location() -> InteriorMultiLocation { + UniversalLocation::get() } - fn deliver(ticket: Self::Ticket) -> Result { - let lane = [0, 0, 0, 0]; - let (fee, msg) = ticket; - let result = MB::send_message( - pallet_xcm::Origin::from(MultiLocation::from(UniversalLocation::get())).into(), - lane, - msg, - fee, - ); - result - .map(|artifacts| { - let hash = (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256); - log::debug!(target: "runtime::bridge", "Sent XCM message {:?}/{} to Millau: {:?}", lane, artifacts.nonce, hash); - hash - }) - .map_err(|e| { - log::debug!(target: "runtime::bridge", "Failed to send XCM message over lane {:?} to Millau: {:?}", lane, e); - SendError::Transport("Bridge has rejected the message") - }) + fn verify_destination(dest: &MultiLocation) -> bool { + matches!(*dest, MultiLocation { parents: 1, interior: X2(GlobalConsensus(r), Parachain(RIALTO_PARACHAIN_ID)) } if r == RialtoNetwork::get()) + } + + fn build_destination() -> MultiLocation { + let dest: InteriorMultiLocation = RialtoParachainNetwork::get().into(); + let here = UniversalLocation::get(); + dest.relative_to(&here) + } + + fn xcm_lane() -> LaneId { + DEFAULT_XCM_LANE_TO_RIALTO_PARACHAIN } } @@ -255,6 +255,7 @@ mod tests { }; use bp_runtime::messages::MessageDispatchResult; use bridge_runtime_common::messages::target::FromBridgedChainMessageDispatch; + use codec::Encode; fn new_test_ext() -> sp_io::TestExternalities { sp_io::TestExternalities::new( @@ -263,17 +264,23 @@ mod tests { } #[test] - fn xcm_messages_to_rialto_are_sent() { + fn xcm_messages_are_sent_using_bridge_router() { new_test_ext().execute_with(|| { - // the encoded message (origin ++ xcm) is 0x010109020419A8 - let dest = (Parent, X1(GlobalConsensus(RialtoNetwork::get()))); let xcm: Xcm<()> = vec![Instruction::Trap(42)].into(); - - let send_result = send_xcm::(dest.into(), xcm); let expected_fee = MultiAssets::from((Here, 4_345_002_552_u64)); let expected_hash = ([0u8, 0u8, 0u8, 0u8], 1u64).using_encoded(sp_io::hashing::blake2_256); - assert_eq!(send_result, Ok((expected_hash, expected_fee)),); + + // message 1 to Rialto + let dest = (Parent, X1(GlobalConsensus(RialtoNetwork::get()))); + let send_result = send_xcm::(dest.into(), xcm.clone()); + assert_eq!(send_result, Ok((expected_hash, expected_fee.clone()))); + + // message 2 to RialtoParachain (expected hash is the same, since other lane is used) + let dest = + (Parent, X2(GlobalConsensus(RialtoNetwork::get()), Parachain(RIALTO_PARACHAIN_ID))); + let send_result = send_xcm::(dest.into(), xcm); + assert_eq!(send_result, Ok((expected_hash, expected_fee))); }) } diff --git a/bridges/bin/rialto-parachain/runtime/src/lib.rs b/bridges/bin/rialto-parachain/runtime/src/lib.rs index c52504c1950e..9f9a37b45775 100644 --- a/bridges/bin/rialto-parachain/runtime/src/lib.rs +++ b/bridges/bin/rialto-parachain/runtime/src/lib.rs @@ -26,10 +26,13 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge}; +use crate::millau_messages::{ + ToMillauMessagePayload, WithMillauMessageBridge, DEFAULT_XCM_LANE_TO_MILLAU, +}; use bridge_runtime_common::messages::{ - source::estimate_message_dispatch_and_delivery_fee, MessageBridge, + source::{estimate_message_dispatch_and_delivery_fee, XcmBridge, XcmBridgeAdapter}, + MessageBridge, }; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -71,6 +74,7 @@ pub use bp_rialto_parachain::{ pub use pallet_bridge_grandpa::Call as BridgeGrandpaCall; pub use pallet_bridge_messages::Call as MessagesCall; +pub use pallet_xcm::Call as XcmCall; // Polkadot & XCM imports use pallet_xcm::XcmPassthrough; @@ -302,6 +306,10 @@ parameter_types! { pub const RelayNetwork: NetworkId = NetworkId::Polkadot; pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + /// The Millau network ID, associated with Kusama. + pub const MillauNetwork: NetworkId = Kusama; + /// The RialtoParachain network ID, associated with Westend. + pub const ThisNetwork: NetworkId = Westend; } /// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used @@ -417,12 +425,36 @@ pub type LocalOriginToLocation = SignedToAccountId32, - // ..and XCMP to communicate with the sibling chains. - XcmpQueue, + // Bridge is used to communicate with other relay chain (Millau). + XcmBridgeAdapter, ); +/// With-Millau bridge. +pub struct ToMillauBridge; + +impl XcmBridge for ToMillauBridge { + type MessageBridge = WithMillauMessageBridge; + type MessageSender = pallet_bridge_messages::Pallet; + + fn universal_location() -> InteriorMultiLocation { + UniversalLocation::get() + } + + fn verify_destination(dest: &MultiLocation) -> bool { + matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == MillauNetwork::get()) + } + + fn build_destination() -> MultiLocation { + let dest: InteriorMultiLocation = MillauNetwork::get().into(); + let here = UniversalLocation::get(); + dest.relative_to(&here) + } + + fn xcm_lane() -> bp_messages::LaneId { + DEFAULT_XCM_LANE_TO_MILLAU + } +} + impl pallet_xcm::Config for Runtime { type Event = Event; type SendXcmOrigin = EnsureXcmOrigin; @@ -800,3 +832,72 @@ cumulus_pallet_parachain_system::register_validate_block!( BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, CheckInherents = CheckInherents, ); + +#[cfg(test)] +mod tests { + use super::*; + use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, + MessageKey, + }; + use bp_runtime::messages::MessageDispatchResult; + use bridge_runtime_common::messages::target::FromBridgedChainMessageDispatch; + use codec::Encode; + + fn new_test_ext() -> sp_io::TestExternalities { + sp_io::TestExternalities::new( + frame_system::GenesisConfig::default().build_storage::().unwrap(), + ) + } + + #[test] + fn xcm_messages_to_millau_are_sent() { + new_test_ext().execute_with(|| { + // the encoded message (origin ++ xcm) is 0x010109020419A8 + let dest = (Parent, X1(GlobalConsensus(MillauNetwork::get()))); + let xcm: Xcm<()> = vec![Instruction::Trap(42)].into(); + + let send_result = send_xcm::(dest.into(), xcm); + let expected_fee = MultiAssets::from((Here, Fungibility::Fungible(4_345_002_552_u128))); + let expected_hash = + ([0u8, 0u8, 0u8, 0u8], 1u64).using_encoded(sp_io::hashing::blake2_256); + assert_eq!(send_result, Ok((expected_hash, expected_fee)),); + }) + } + + #[test] + fn xcm_messages_from_millau_are_dispatched() { + type XcmExecutor = xcm_executor::XcmExecutor; + type MessageDispatcher = FromBridgedChainMessageDispatch< + WithMillauMessageBridge, + XcmExecutor, + XcmWeigher, + frame_support::traits::ConstU64, + >; + + new_test_ext().execute_with(|| { + let location: MultiLocation = + (Parent, X1(GlobalConsensus(MillauNetwork::get()))).into(); + let xcm: Xcm = vec![Instruction::Trap(42)].into(); + + let mut incoming_message = DispatchMessage { + key: MessageKey { lane_id: [0, 0, 0, 0], nonce: 1 }, + data: DispatchMessageData { payload: Ok((location, xcm).into()), fee: 0 }, + }; + + let dispatch_weight = MessageDispatcher::dispatch_weight(&mut incoming_message); + assert_eq!(dispatch_weight, 1_000_000_000); + + let dispatch_result = + MessageDispatcher::dispatch(&AccountId::from([0u8; 32]), incoming_message); + assert_eq!( + dispatch_result, + MessageDispatchResult { + dispatch_result: true, + unspent_weight: 0, + dispatch_fee_paid_during_dispatch: false, + } + ); + }) + } +} diff --git a/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs b/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs index 248ec7b834d4..136b4343c190 100644 --- a/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs +++ b/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -19,7 +19,7 @@ // TODO: this is almost exact copy of `millau_messages.rs` from Rialto runtime. // Should be extracted to a separate crate and reused here. -use crate::Runtime; +use crate::{OriginCaller, Runtime}; use bp_messages::{ source_chain::{SenderOrigin, TargetHeaderChain}, @@ -40,6 +40,8 @@ use scale_info::TypeInfo; use sp_runtime::{traits::Saturating, FixedPointNumber, FixedU128}; use sp_std::convert::TryFrom; +/// Default lane that is used to send messages to Millau. +pub const DEFAULT_XCM_LANE_TO_MILLAU: LaneId = [0, 0, 0, 0]; /// Initial value of `MillauToRialtoParachainConversionRate` parameter. pub const INITIAL_MILLAU_TO_RIALTO_PARACHAIN_CONVERSION_RATE: FixedU128 = FixedU128::from_inner(FixedU128::DIV); @@ -137,7 +139,22 @@ impl messages::ThisChainWithMessages for RialtoParachain { >; fn is_message_accepted(send_origin: &Self::Origin, lane: &LaneId) -> bool { - send_origin.linked_account().is_some() && (*lane == [0, 0, 0, 0] || *lane == [0, 0, 0, 1]) + let here_location = xcm::v3::MultiLocation::from(crate::UniversalLocation::get()); + match send_origin.caller { + OriginCaller::PolkadotXcm(pallet_xcm::Origin::Xcm(ref location)) + if *location == here_location => + { + log::trace!(target: "runtime::bridge", "Verifying message sent using XCM pallet to Millau"); + }, + _ => { + // keep in mind that in this case all messages are free (in term of fees) + // => it's just to keep testing bridge on our test deployments until we'll have a + // better option + log::trace!(target: "runtime::bridge", "Verifying message sent using messages pallet to Millau"); + }, + } + + *lane == [0, 0, 0, 0] || *lane == [0, 0, 0, 1] } fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs index f0bc0982e69c..9f0708ce9fd8 100644 --- a/bridges/bin/rialto/runtime/src/lib.rs +++ b/bridges/bin/rialto/runtime/src/lib.rs @@ -73,6 +73,7 @@ pub use pallet_bridge_grandpa::Call as BridgeGrandpaMillauCall; pub use pallet_bridge_messages::Call as MessagesCall; pub use pallet_sudo::Call as SudoCall; pub use pallet_timestamp::Call as TimestampCall; +pub use pallet_xcm::Call as XcmCall; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; diff --git a/bridges/bin/rialto/runtime/src/xcm_config.rs b/bridges/bin/rialto/runtime/src/xcm_config.rs index 8a230101f306..63b2d0bead0f 100644 --- a/bridges/bin/rialto/runtime/src/xcm_config.rs +++ b/bridges/bin/rialto/runtime/src/xcm_config.rs @@ -17,22 +17,16 @@ //! XCM configurations for the Rialto runtime. use super::{ - millau_messages::WithMillauMessageBridge, AccountId, AllPalletsWithSystem, Balances, - BridgeMillauMessages, Call, Event, Origin, Runtime, XcmPallet, + millau_messages::WithMillauMessageBridge, AccountId, AllPalletsWithSystem, Balances, Call, + Event, Origin, Runtime, WithMillauMessagesInstance, XcmPallet, }; -use bp_messages::source_chain::MessagesBridge; -use bp_rialto::{Balance, WeightToFee}; -use bridge_runtime_common::messages::{ - source::{estimate_message_dispatch_and_delivery_fee, FromThisChainMessagePayload}, - MessageBridge, -}; -use codec::Encode; +use bp_rialto::WeightToFee; +use bridge_runtime_common::messages::source::{XcmBridge, XcmBridgeAdapter}; use frame_support::{ parameter_types, traits::{Everything, Nothing}, weights::Weight, }; -use sp_std::marker::PhantomData; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, @@ -105,7 +99,7 @@ parameter_types! { /// individual routers. pub type XcmRouter = ( // Router to send messages to Millau. - ToMillauBridge, + XcmBridgeAdapter, ); parameter_types! { @@ -189,60 +183,29 @@ impl pallet_xcm::Config for Runtime { type MaxLockers = frame_support::traits::ConstU32<8>; } -/// With-rialto bridge. -pub struct ToMillauBridge(PhantomData); - -impl> SendXcm - for ToMillauBridge -{ - type Ticket = (Balance, FromThisChainMessagePayload); - - fn validate( - dest: &mut Option, - msg: &mut Option>, - ) -> SendResult { - let d = dest.take().ok_or(SendError::MissingArgument)?; - if !matches!(d, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == MillauNetwork::get()) - { - *dest = Some(d); - return Err(SendError::NotApplicable) - }; +/// With-Millau bridge. +pub struct ToMillauBridge; - let dest: InteriorMultiLocation = MillauNetwork::get().into(); - let here = UniversalLocation::get(); - let route = dest.relative_to(&here); - let msg = (route, msg.take().unwrap()).encode(); +impl XcmBridge for ToMillauBridge { + type MessageBridge = WithMillauMessageBridge; + type MessageSender = pallet_bridge_messages::Pallet; - let fee = estimate_message_dispatch_and_delivery_fee::( - &msg, - WithMillauMessageBridge::RELAYER_FEE_PERCENT, - None, - ) - .map_err(SendError::Transport)?; - let fee_assets = MultiAssets::from((Here, fee)); + fn universal_location() -> InteriorMultiLocation { + UniversalLocation::get() + } - Ok(((fee, msg), fee_assets)) + fn verify_destination(dest: &MultiLocation) -> bool { + matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == MillauNetwork::get()) + } + + fn build_destination() -> MultiLocation { + let dest: InteriorMultiLocation = MillauNetwork::get().into(); + let here = UniversalLocation::get(); + dest.relative_to(&here) } - fn deliver(ticket: Self::Ticket) -> Result { - let lane = [0, 0, 0, 0]; - let (fee, msg) = ticket; - let result = MB::send_message( - pallet_xcm::Origin::from(MultiLocation::from(UniversalLocation::get())).into(), - lane, - msg, - fee, - ); - result - .map(|artifacts| { - let hash = (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256); - log::debug!(target: "runtime::bridge", "Sent XCM message {:?}/{} to Rialto: {:?}", lane, artifacts.nonce, hash); - hash - }) - .map_err(|e| { - log::debug!(target: "runtime::bridge", "Failed to send XCM message over lane {:?} to Rialto: {:?}", lane, e); - SendError::Transport("Bridge has rejected the message") - }) + fn xcm_lane() -> bp_messages::LaneId { + [0, 0, 0, 0] } } @@ -255,6 +218,7 @@ mod tests { }; use bp_runtime::messages::MessageDispatchResult; use bridge_runtime_common::messages::target::FromBridgedChainMessageDispatch; + use codec::Encode; fn new_test_ext() -> sp_io::TestExternalities { sp_io::TestExternalities::new( diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index cb9f37fe66c4..121ef318fbb0 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -41,6 +41,7 @@ sp-version = { git = "https://github.com/paritytech/substrate", branch = "master # Polkadot dependencies +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } xcm = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } @@ -65,6 +66,7 @@ std = [ "pallet-bridge-messages/std", "pallet-bridge-parachains/std", "pallet-transaction-payment/std", + "pallet-xcm/std", "scale-info/std", "sp-api/std", "sp-core/std", diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs index 8153e0840cf5..ef0722a80bce 100644 --- a/bridges/bin/runtime-common/src/messages.rs +++ b/bridges/bin/runtime-common/src/messages.rs @@ -37,6 +37,7 @@ use sp_runtime::{ }; use sp_std::{cmp::PartialOrd, convert::TryFrom, fmt::Debug, marker::PhantomData, vec::Vec}; use sp_trie::StorageProof; +use xcm::latest::prelude::*; /// Bidirectional message bridge. pub trait MessageBridge { @@ -524,6 +525,111 @@ pub mod source { Ok((lane, inbound_lane_data)) } + + /// XCM bridge. + pub trait XcmBridge { + /// Runtime message bridge configuration. + type MessageBridge: MessageBridge; + /// Runtime message sender adapter. + type MessageSender: bp_messages::source_chain::MessagesBridge< + OriginOf>, + AccountIdOf>, + BalanceOf>, + FromThisChainMessagePayload, + >; + + /// Our location within the Consensus Universe. + fn universal_location() -> InteriorMultiLocation; + /// Verify that the adapter is responsible for handling given XCM destination. + fn verify_destination(dest: &MultiLocation) -> bool; + /// Build route from this chain to the XCM destination. + fn build_destination() -> MultiLocation; + /// Return message lane used to deliver XCM messages. + fn xcm_lane() -> LaneId; + } + + /// XCM bridge adapter for `bridge-messages` pallet. + pub struct XcmBridgeAdapter(PhantomData); + + impl SendXcm for XcmBridgeAdapter + where + BalanceOf>: Into, + OriginOf>: From, + { + type Ticket = (BalanceOf>, FromThisChainMessagePayload); + + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult { + let d = dest.take().ok_or(SendError::MissingArgument)?; + if !T::verify_destination(&d) { + *dest = Some(d); + return Err(SendError::NotApplicable) + } + + let route = T::build_destination(); + let msg = (route, msg.take().unwrap()).encode(); + + let fee = estimate_message_dispatch_and_delivery_fee::( + &msg, + T::MessageBridge::RELAYER_FEE_PERCENT, + None, + ); + let fee = match fee { + Ok(fee) => fee, + Err(e) => { + log::trace!( + target: "runtime::bridge", + "Failed to comupte fee for XCM message to {:?}: {:?}", + T::MessageBridge::BRIDGED_CHAIN_ID, + e, + ); + *dest = Some(d); + return Err(SendError::Transport(e)) + }, + }; + let fee_assets = MultiAssets::from((Here, fee)); + + Ok(((fee, msg), fee_assets)) + } + + fn deliver(ticket: Self::Ticket) -> Result { + use bp_messages::source_chain::MessagesBridge; + + let lane = T::xcm_lane(); + let (fee, msg) = ticket; + let result = T::MessageSender::send_message( + pallet_xcm::Origin::from(MultiLocation::from(T::universal_location())).into(), + lane, + msg, + fee, + ); + result + .map(|artifacts| { + let hash = (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256); + log::debug!( + target: "runtime::bridge", + "Sent XCM message {:?}/{} to {:?}: {:?}", + lane, + artifacts.nonce, + T::MessageBridge::BRIDGED_CHAIN_ID, + hash, + ); + hash + }) + .map_err(|e| { + log::debug!( + target: "runtime::bridge", + "Failed to send XCM message over lane {:?} to {:?}: {:?}", + lane, + T::MessageBridge::BRIDGED_CHAIN_ID, + e, + ); + SendError::Transport("Bridge has rejected the message") + }) + } + } } /// Sub-module that is declaring types required for processing Bridged -> This chain messages. @@ -641,8 +747,6 @@ pub mod target { _relayer_account: &AccountIdOf>, message: DispatchMessage>>, ) -> MessageDispatchResult { - use xcm::latest::*; - let message_id = (message.key.lane_id, message.key.nonce); let do_dispatch = move || -> sp_std::result::Result { let FromBridgedChainMessagePayload { xcm: (location, xcm), weight: weight_limit } = diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index 48ca546def8c..9806a264c994 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -812,7 +812,7 @@ fn send_message, I: 'static>( // the most lightweigh check is the message size check ensure!( - payload.size() < T::MaximalOutboundPayloadSize::get(), + payload.size() <= T::MaximalOutboundPayloadSize::get(), Error::::MessageIsTooLarge, ); @@ -1109,12 +1109,12 @@ fn verify_and_decode_messages_proof, Fee, Dispatch mod tests { use super::*; use crate::mock::{ - message, message_payload, run_test, unrewarded_relayer, Event as TestEvent, Origin, - TestMessageDeliveryAndDispatchPayment, TestMessagesDeliveryProof, TestMessagesParameter, - TestMessagesProof, TestOnDeliveryConfirmed1, TestOnDeliveryConfirmed2, - TestOnMessageAccepted, TestRuntime, TokenConversionRate, MAX_OUTBOUND_PAYLOAD_SIZE, - PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_RELAYER_A, - TEST_RELAYER_B, + message, message_payload, run_test, unrewarded_relayer, Balance, Event as TestEvent, + Origin, TestMessageDeliveryAndDispatchPayment, TestMessagesDeliveryProof, + TestMessagesParameter, TestMessagesProof, TestOnDeliveryConfirmed1, + TestOnDeliveryConfirmed2, TestOnMessageAccepted, TestRuntime, TokenConversionRate, + MAX_OUTBOUND_PAYLOAD_SIZE, PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, + TEST_RELAYER_A, TEST_RELAYER_B, }; use bp_messages::{UnrewardedRelayer, UnrewardedRelayersState}; use bp_test_utils::generate_owned_bridge_module_tests; @@ -1456,11 +1456,23 @@ mod tests { Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, - message_payload, - 0, + message_payload.clone(), + Balance::MAX, ), Error::::MessageIsTooLarge, ); + + // let's check that we're able to send `MAX_OUTBOUND_PAYLOAD_SIZE` messages + while message_payload.size() > MAX_OUTBOUND_PAYLOAD_SIZE { + message_payload.extra.pop(); + } + assert_eq!(message_payload.size(), MAX_OUTBOUND_PAYLOAD_SIZE); + assert_ok!(Pallet::::send_message( + Origin::signed(1), + TEST_LANE_ID, + message_payload, + Balance::MAX, + ),); }) } diff --git a/bridges/relays/bin-substrate/Cargo.toml b/bridges/relays/bin-substrate/Cargo.toml index ead0f664c073..63ccb4f918e2 100644 --- a/bridges/relays/bin-substrate/Cargo.toml +++ b/bridges/relays/bin-substrate/Cargo.toml @@ -60,10 +60,13 @@ sp-version = { git = "https://github.com/paritytech/substrate", branch = "master # Polkadot Dependencies +#pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3" } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3" } polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3" } polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3" } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } + [dev-dependencies] bp-test-utils = { path = "../../primitives/test-utils" } diff --git a/bridges/relays/bin-substrate/src/chains/millau.rs b/bridges/relays/bin-substrate/src/chains/millau.rs index 4b31e423ac02..2cf0258d4325 100644 --- a/bridges/relays/bin-substrate/src/chains/millau.rs +++ b/bridges/relays/bin-substrate/src/chains/millau.rs @@ -22,12 +22,49 @@ use crate::cli::{ CliChain, }; use bp_messages::LaneId; +use bp_rialto_parachain::RIALTO_PARACHAIN_ID; use bp_runtime::EncodedOrDecodedCall; use relay_millau_client::Millau; use relay_substrate_client::BalanceOf; use sp_version::RuntimeVersion; +use xcm::latest::prelude::*; impl CliEncodeMessage for Millau { + fn encode_send_xcm( + message: xcm::VersionedXcm<()>, + bridge_instance_index: u8, + ) -> anyhow::Result> { + Ok(match bridge_instance_index { + bridge::MILLAU_TO_RIALTO_INDEX => { + let dest = + (Parent, X1(GlobalConsensus(millau_runtime::xcm_config::RialtoNetwork::get()))); + millau_runtime::Call::XcmPallet(millau_runtime::XcmCall::send { + dest: Box::new(dest.into()), + message: Box::new(message), + }) + .into() + }, + bridge::MILLAU_TO_RIALTO_PARACHAIN_INDEX => { + let dest = ( + Parent, + X2( + GlobalConsensus(millau_runtime::xcm_config::RialtoNetwork::get()), + Parachain(RIALTO_PARACHAIN_ID), + ), + ); + millau_runtime::Call::XcmPallet(millau_runtime::XcmCall::send { + dest: Box::new(dest.into()), + message: Box::new(message), + }) + .into() + }, + _ => anyhow::bail!( + "Unsupported target bridge pallet with instance index: {}", + bridge_instance_index + ), + }) + } + fn encode_send_message_call( lane: LaneId, payload: RawMessage, diff --git a/bridges/relays/bin-substrate/src/chains/rialto.rs b/bridges/relays/bin-substrate/src/chains/rialto.rs index 0255a055a574..2753a917ad24 100644 --- a/bridges/relays/bin-substrate/src/chains/rialto.rs +++ b/bridges/relays/bin-substrate/src/chains/rialto.rs @@ -26,8 +26,30 @@ use bp_runtime::EncodedOrDecodedCall; use relay_rialto_client::Rialto; use relay_substrate_client::BalanceOf; use sp_version::RuntimeVersion; +use xcm::latest::prelude::*; impl CliEncodeMessage for Rialto { + fn encode_send_xcm( + message: xcm::VersionedXcm<()>, + bridge_instance_index: u8, + ) -> anyhow::Result> { + Ok(match bridge_instance_index { + bridge::RIALTO_TO_MILLAU_INDEX => { + let dest = + (Parent, X1(GlobalConsensus(rialto_runtime::xcm_config::MillauNetwork::get()))); + rialto_runtime::Call::XcmPallet(rialto_runtime::XcmCall::send { + dest: Box::new(dest.into()), + message: Box::new(message), + }) + .into() + }, + _ => anyhow::bail!( + "Unsupported target bridge pallet with instance index: {}", + bridge_instance_index + ), + }) + } + fn encode_send_message_call( lane: LaneId, payload: RawMessage, diff --git a/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs b/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs index 63322351d6d0..09edeaecea02 100644 --- a/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs +++ b/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs @@ -26,8 +26,32 @@ use bp_runtime::EncodedOrDecodedCall; use relay_rialto_parachain_client::RialtoParachain; use relay_substrate_client::BalanceOf; use sp_version::RuntimeVersion; +use xcm::latest::prelude::*; impl CliEncodeMessage for RialtoParachain { + fn encode_send_xcm( + message: xcm::VersionedXcm<()>, + bridge_instance_index: u8, + ) -> anyhow::Result> { + Ok(match bridge_instance_index { + bridge::RIALTO_PARACHAIN_TO_MILLAU_INDEX => { + let dest = + (Parent, X1(GlobalConsensus(rialto_parachain_runtime::MillauNetwork::get()))); + rialto_parachain_runtime::Call::PolkadotXcm( + rialto_parachain_runtime::XcmCall::send { + dest: Box::new(dest.into()), + message: Box::new(message), + }, + ) + .into() + }, + _ => anyhow::bail!( + "Unsupported target bridge pallet with instance index: {}", + bridge_instance_index + ), + }) + } + fn encode_send_message_call( lane: LaneId, payload: RawMessage, diff --git a/bridges/relays/bin-substrate/src/cli/encode_message.rs b/bridges/relays/bin-substrate/src/cli/encode_message.rs index ad6070fdb0f9..9f9ef37ff0ea 100644 --- a/bridges/relays/bin-substrate/src/cli/encode_message.rs +++ b/bridges/relays/bin-substrate/src/cli/encode_message.rs @@ -42,7 +42,13 @@ pub enum Message { pub type RawMessage = Vec; pub trait CliEncodeMessage: Chain { - /// Encode a send message call. + /// Encode a send XCM call of the XCM pallet. + fn encode_send_xcm( + message: xcm::VersionedXcm<()>, + bridge_instance_index: u8, + ) -> anyhow::Result>; + + /// Encode a send message call of the bridge-messages pallet. fn encode_send_message_call( lane: LaneId, message: RawMessage, diff --git a/bridges/relays/bin-substrate/src/cli/send_message.rs b/bridges/relays/bin-substrate/src/cli/send_message.rs index 2e4e3628b0b4..11625c3b8ea2 100644 --- a/bridges/relays/bin-substrate/src/cli/send_message.rs +++ b/bridges/relays/bin-substrate/src/cli/send_message.rs @@ -24,13 +24,13 @@ use crate::{ cli::{ bridge::{FullBridge, MessagesCliBridge}, chain_schema::*, - encode_message::{self, CliEncodeMessage}, + encode_message::{self, CliEncodeMessage, RawMessage}, estimate_fee::{estimate_message_delivery_and_dispatch_fee, ConversionRateOverride}, Balance, CliChain, HexBytes, HexLaneId, }, }; use async_trait::async_trait; -use codec::Encode; +use codec::{Decode, Encode}; use relay_substrate_client::{ AccountIdOf, AccountKeyPairOf, Chain, ChainBase, SignParam, TransactionSignScheme, UnsignedTransaction, @@ -70,6 +70,10 @@ pub struct SendMessage { source: SourceConnectionParams, #[structopt(flatten)] source_sign: SourceSigningParams, + /// Send message using XCM pallet instead. By default message is sent using + /// bridge messages pallet. + #[structopt(long)] + use_xcm_pallet: bool, /// Hex-encoded lane id. Defaults to `00000000`. #[structopt(long, default_value = "00000000")] lane: HexLaneId, @@ -124,12 +128,19 @@ where ), }; let payload_len = payload.encode().len(); - let send_message_call = Self::Source::encode_send_message_call( - data.lane.0, - payload, - fee.cast().into(), - data.bridge.bridge_instance_index(), - )?; + let send_message_call = if data.use_xcm_pallet { + Self::Source::encode_send_xcm( + decode_xcm(payload)?, + data.bridge.bridge_instance_index(), + )? + } else { + Self::Source::encode_send_message_call( + data.lane.0, + payload, + fee.cast().into(), + data.bridge.bridge_instance_index(), + )? + }; let source_genesis_hash = *source_client.genesis_hash(); let (spec_version, transaction_version) = source_client.simple_runtime_version().await?; @@ -210,6 +221,12 @@ impl SendMessage { } } +/// Decode SCALE encoded raw XCM message. +fn decode_xcm(message: RawMessage) -> anyhow::Result> { + Decode::decode(&mut &message[..]) + .map_err(|e| anyhow::format_err!("Failed to decode XCM program: {:?}", e)) +} + #[cfg(test)] mod tests { use super::*;