diff --git a/bridges/bin/millau/node/src/chain_spec.rs b/bridges/bin/millau/node/src/chain_spec.rs index a7e3c7c877183..ed7ee2440a898 100644 --- a/bridges/bin/millau/node/src/chain_spec.rs +++ b/bridges/bin/millau/node/src/chain_spec.rs @@ -217,6 +217,7 @@ fn testnet_genesis( owner: Some(get_account_id_from_seed::("RialtoMessagesOwner")), ..Default::default() }, + xcm_pallet: Default::default(), } } diff --git a/bridges/bin/millau/runtime/Cargo.toml b/bridges/bin/millau/runtime/Cargo.toml index 80d961388831a..598b100e1ab82 100644 --- a/bridges/bin/millau/runtime/Cargo.toml +++ b/bridges/bin/millau/runtime/Cargo.toml @@ -11,6 +11,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" hex-literal = "0.3" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } libsecp256k1 = { version = "0.7", optional = true, default-features = false, features = ["hmac"] } +log = { version = "0.4.14", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0", optional = true, features = ["derive"] } @@ -53,6 +54,7 @@ sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-mmr-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -62,8 +64,16 @@ sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +# 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 } + [dev-dependencies] bridge-runtime-common = { path = "../../runtime-common", features = ["integrity-test"] } +env_logger = "0.8" static_assertions = "1.1" [build-dependencies] @@ -85,6 +95,7 @@ std = [ "frame-support/std", "frame-system-rpc-runtime-api/std", "frame-system/std", + "log/std", "pallet-aura/std", "pallet-balances/std", "pallet-beefy/std", @@ -100,6 +111,7 @@ std = [ "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", + "pallet-xcm/std", "scale-info/std", "serde", "sp-api/std", @@ -108,6 +120,7 @@ std = [ "sp-core/std", "sp-finality-grandpa/std", "sp-inherents/std", + "sp-io/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", @@ -115,6 +128,9 @@ std = [ "sp-transaction-pool/std", "sp-trie/std", "sp-version/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", ] runtime-benchmarks = [ "bridge-runtime-common/runtime-benchmarks", @@ -123,5 +139,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "libsecp256k1", "pallet-bridge-messages/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", ] diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs index 4d44f34b624b3..178ec2e18e567 100644 --- a/bridges/bin/millau/runtime/src/lib.rs +++ b/bridges/bin/millau/runtime/src/lib.rs @@ -29,6 +29,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); pub mod rialto_messages; +pub mod xcm_config; use crate::rialto_messages::{ToRialtoMessagePayload, WithRialtoMessageBridge}; @@ -451,13 +452,7 @@ impl pallet_bridge_messages::Config for Runtime { type TargetHeaderChain = crate::rialto_messages::Rialto; type LaneMessageVerifier = crate::rialto_messages::ToRialtoMessageVerifier; - type MessageDeliveryAndDispatchPayment = - pallet_bridge_messages::instant_payments::InstantCurrencyPayments< - Runtime, - WithRialtoMessagesInstance, - pallet_balances::Pallet, - GetDeliveryConfirmationTransactionFee, - >; + type MessageDeliveryAndDispatchPayment = (); type OnMessageAccepted = (); type OnDeliveryConfirmed = (); @@ -499,6 +494,9 @@ construct_runtime!( // Westend bridge modules. BridgeWestendGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Config, Storage}, + + // Pallet for sending XCM. + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 99, } ); @@ -750,6 +748,7 @@ impl_runtime_apis! { Runtime, WithRialtoMessagesInstance, WithRialtoMessageBridge, + xcm_config::OutboundXcmWeigher, >(lane, begin, end) } } diff --git a/bridges/bin/millau/runtime/src/rialto_messages.rs b/bridges/bin/millau/runtime/src/rialto_messages.rs index 9b2c623757148..aac568e077f64 100644 --- a/bridges/bin/millau/runtime/src/rialto_messages.rs +++ b/bridges/bin/millau/runtime/src/rialto_messages.rs @@ -16,7 +16,7 @@ //! Everything required to serve Millau <-> Rialto messages. -use crate::Runtime; +use crate::{Call, OriginCaller, Runtime}; use bp_messages::{ source_chain::{SenderOrigin, TargetHeaderChain}, @@ -40,6 +40,10 @@ pub const INITIAL_RIALTO_TO_MILLAU_CONVERSION_RATE: FixedU128 = FixedU128::from_inner(FixedU128::DIV); /// Initial value of `RialtoFeeMultiplier` parameter. pub const INITIAL_RIALTO_FEE_MULTIPLIER: FixedU128 = FixedU128::from_inner(FixedU128::DIV); +/// 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. +pub const BASE_XCM_WEIGHT_TWICE: Weight = 2 * crate::xcm_config::BASE_XCM_WEIGHT; parameter_types! { /// Rialto to Millau conversion rate. Initially we treat both tokens as equal. @@ -56,7 +60,7 @@ pub type ToRialtoMessageVerifier = messages::source::FromThisChainMessageVerifier; /// Message payload for Rialto -> Millau messages. -pub type FromRialtoMessagePayload = messages::target::FromBridgedChainMessagePayload; +pub type FromRialtoMessagePayload = messages::target::FromBridgedChainMessagePayload; /// Messages proof for Rialto -> Millau messages. pub type FromRialtoMessagesProof = messages::target::FromBridgedChainMessagesProof; @@ -68,9 +72,11 @@ pub type ToRialtoMessagesDeliveryProof = /// Call-dispatch based message dispatch for Rialto -> Millau messages. pub type FromRialtoMessageDispatch = messages::target::FromBridgedChainMessageDispatch< WithRialtoMessageBridge, - crate::Runtime, - pallet_balances::Pallet, - (), + xcm_executor::XcmExecutor, + crate::xcm_config::XcmWeigher, + // 2 XCM instructions is for simple `Trap(42)` program, coming through bridge + // (it is prepended with `UniversalOrigin` instruction) + frame_support::traits::ConstU64, >; /// Millau <-> Rialto message bridge. @@ -115,7 +121,23 @@ impl messages::ThisChainWithMessages for Millau { type Call = crate::Call; fn is_message_accepted(send_origin: &Self::Origin, lane: &LaneId) -> bool { - (*lane == [0, 0, 0, 0] || *lane == [0, 0, 0, 1]) && send_origin.linked_account().is_some() + let here_location = + xcm::v3::MultiLocation::from(crate::xcm_config::UniversalLocation::get()); + match send_origin.caller { + OriginCaller::XcmPallet(pallet_xcm::Origin::Xcm(ref location)) + if *location == here_location => + { + log::trace!(target: "runtime::bridge", "Verifying message sent using XCM pallet to Rialto"); + }, + _ => { + // 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 Rialto"); + }, + } + + *lane == [0, 0, 0, 0] || *lane == [0, 0, 0, 1] } fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { @@ -262,14 +284,8 @@ impl SourceHeaderChain for Rialto { impl SenderOrigin for crate::Origin { fn linked_account(&self) -> Option { - match self.caller { - crate::OriginCaller::system(frame_system::RawOrigin::Signed(ref submitter)) => - Some(submitter.clone()), - crate::OriginCaller::system(frame_system::RawOrigin::Root) | - crate::OriginCaller::system(frame_system::RawOrigin::None) => - crate::RootAccountForPayments::get(), - _ => None, - } + // XCM deals wit fees in our deployments + None } } diff --git a/bridges/bin/millau/runtime/src/xcm_config.rs b/bridges/bin/millau/runtime/src/xcm_config.rs new file mode 100644 index 0000000000000..a274b25924a5d --- /dev/null +++ b/bridges/bin/millau/runtime/src/xcm_config.rs @@ -0,0 +1,317 @@ +// 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 configurations for the Millau runtime. + +use super::{ + rialto_messages::WithRialtoMessageBridge, AccountId, AllPalletsWithSystem, Balances, + BridgeRialtoMessages, Call, Event, Origin, Runtime, 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 frame_support::{ + parameter_types, + traits::{Everything, Nothing}, + weights::Weight, +}; +use sp_std::marker::PhantomData; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, + CurrencyAdapter as XcmCurrencyAdapter, IsConcrete, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, +}; + +parameter_types! { + /// The location of the `MLAU` token, from the context of this chain. Since this token is native to this + /// 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(); + /// The Millau network ID, associated with Kusama. + pub const ThisNetwork: NetworkId = Kusama; + /// The Rialto network ID, associated with Polkadot. + pub const RialtoNetwork: NetworkId = Polkadot; + + /// Our XCM location ancestry - i.e. our location within the Consensus Universe. + /// + /// Since Kusama is a top-level relay-chain with its own consensus, it's just our network ID. + pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); + /// The check account, which holds any native assets that have been teleported out and not back in (yet). + pub CheckAccount: AccountId = XcmPallet::check_account(); +} + +/// The canonical means of converting a `MultiLocation` into an `AccountId`, used when we want to +/// determine the sovereign account controlled by a location. +pub type SovereignAccountOf = ( + // We can directly alias an `AccountId32` into a local account. + AccountId32Aliases, +); + +/// Our asset transactor. This is what allows us to interest with the runtime facilities from the +/// point of view of XCM-only concepts like `MultiLocation` and `MultiAsset`. +/// +/// Ours is only aware of the Balances pallet, which is mapped to `TokenLocation`. +pub type LocalAssetTransactor = XcmCurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // We can convert the MultiLocations with our converter above: + SovereignAccountOf, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We track our teleports in/out to keep total issuance correct. + CheckAccount, +>; + +/// The means that we convert the XCM message origin location into a local dispatch origin. +type LocalOriginConverter = ( + // A `Signed` origin of the sovereign account that the original location controls. + SovereignSignedViaLocation, + // The AccountId32 location type can be expressed natively as a `Signed` origin. + SignedAccountId32AsNative, +); + +/// The amount of weight an XCM operation takes. This is a safe overestimate. +pub const BASE_XCM_WEIGHT: Weight = 1_000_000_000; + +parameter_types! { + /// The amount of weight an XCM operation takes. This is a safe overestimate. + pub const BaseXcmWeight: Weight = BASE_XCM_WEIGHT; + /// Maximum number of instructions in a single XCM fragment. A sanity check against weight + /// calculations getting too crazy. + pub const MaxInstructions: u32 = 100; +} + +/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our +/// individual routers. +pub type XcmRouter = ( + // Router to send messages to Rialto. + ToRialtoBridge, +); + +parameter_types! { + pub const MaxAssetsIntoHolding: u32 = 64; +} + +/// The barriers one of which must be passed for an XCM message to be executed. +pub type Barrier = ( + // Weight that is paid for may be consumed. + TakeWeightCredit, + // If the message is one that immediately attemps to pay for execution, then allow it. + AllowTopLevelPaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, +); + +/// Outbound XCM weigher type. +pub type OutboundXcmWeigher = xcm_builder::FixedWeightBounds; +/// XCM weigher type. +pub type XcmWeigher = xcm_builder::FixedWeightBounds; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type Call = Call; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = XcmWeigher; + // The weight trader piggybacks on the existing transaction-fee conversion logic. + type Trader = UsingComponents; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; +} + +/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior +/// location of this chain. +pub type LocalOriginToLocation = ( + // Usual Signed origin to be used in XCM as a corresponding AccountId32 + SignedToAccountId32, +); + +impl pallet_xcm::Config for Runtime { + type Event = Event; + // We don't allow any messages to be sent via the transaction yet. This is basically safe to + // enable, (safe the possibility of someone spamming the parachain if they're willing to pay + // the DOT to send from the Relay-chain). But it's useless until we bring in XCM v3 which will + // make `DescendOrigin` a bit more useful. + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // Anyone can execute XCM messages locally. + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = xcm_executor::XcmExecutor; + // Anyone is able to use teleportation regardless of who they are and what they want to + // teleport. + type XcmTeleportFilter = Everything; + // Anyone is able to use reserve transfers regardless of who they are and what they want to + // transfer. + type XcmReserveTransferFilter = Everything; + type Weigher = XcmWeigher; + type UniversalLocation = UniversalLocation; + type Origin = Origin; + type Call = Call; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + 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) + }; + + let dest: InteriorMultiLocation = RialtoNetwork::get().into(); + let here = UniversalLocation::get(); + let route = dest.relative_to(&here); + let msg = (route, msg.take().unwrap()).encode(); + + 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)); + + Ok(((fee, msg), fee_assets)) + } + + 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") + }) + } +} + +#[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; + + fn new_test_ext() -> sp_io::TestExternalities { + sp_io::TestExternalities::new( + frame_system::GenesisConfig::default().build_storage::().unwrap(), + ) + } + + #[test] + fn xcm_messages_to_rialto_are_sent() { + 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)),); + }) + } + + #[test] + fn xcm_messages_from_rialto_are_dispatched() { + type XcmExecutor = xcm_executor::XcmExecutor; + type MessageDispatcher = FromBridgedChainMessageDispatch< + WithRialtoMessageBridge, + XcmExecutor, + XcmWeigher, + frame_support::traits::ConstU64, + >; + + new_test_ext().execute_with(|| { + let location: MultiLocation = + (Parent, X1(GlobalConsensus(RialtoNetwork::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/node/src/chain_spec.rs b/bridges/bin/rialto/node/src/chain_spec.rs index 10315e33c853a..9b3dc288c4939 100644 --- a/bridges/bin/rialto/node/src/chain_spec.rs +++ b/bridges/bin/rialto/node/src/chain_spec.rs @@ -290,6 +290,7 @@ fn testnet_genesis( owner: Some(get_account_id_from_seed::("MillauMessagesOwner")), ..Default::default() }, + xcm_pallet: Default::default(), } } diff --git a/bridges/bin/rialto/runtime/Cargo.toml b/bridges/bin/rialto/runtime/Cargo.toml index ccfa65090e6d1..4c6bb990e19c6 100644 --- a/bridges/bin/rialto/runtime/Cargo.toml +++ b/bridges/bin/rialto/runtime/Cargo.toml @@ -66,12 +66,17 @@ sp-version = { git = "https://github.com/paritytech/substrate", branch = "master # Polkadot (parachain) Dependencies +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", branch = "gav-xcm-v3", default-features = false } polkadot-runtime-parachains = { 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 } [dev-dependencies] bridge-runtime-common = { path = "../../runtime-common", features = ["integrity-test"] } +env_logger = "0.8" libsecp256k1 = { version = "0.7", features = ["hmac"] } static_assertions = "1.1" @@ -104,6 +109,7 @@ std = [ "pallet-bridge-messages/std", "pallet-grandpa/std", "pallet-mmr/std", + "pallet-xcm/std", "sp-mmr-primitives/std", "pallet-shift-session-manager/std", "pallet-sudo/std", @@ -130,6 +136,9 @@ std = [ "sp-transaction-pool/std", "sp-trie/std", "sp-version/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", ] runtime-benchmarks = [ "bridge-runtime-common/runtime-benchmarks", @@ -138,5 +147,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "libsecp256k1", "pallet-bridge-messages/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", ] diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs index 4d2602a20a608..52600c1a173a9 100644 --- a/bridges/bin/rialto/runtime/src/lib.rs +++ b/bridges/bin/rialto/runtime/src/lib.rs @@ -30,6 +30,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); pub mod millau_messages; pub mod parachains; +pub mod xcm_config; use crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge}; @@ -449,13 +450,7 @@ impl pallet_bridge_messages::Config for Runtime { type TargetHeaderChain = crate::millau_messages::Millau; type LaneMessageVerifier = crate::millau_messages::ToMillauMessageVerifier; - type MessageDeliveryAndDispatchPayment = - pallet_bridge_messages::instant_payments::InstantCurrencyPayments< - Runtime, - WithMillauMessagesInstance, - pallet_balances::Pallet, - GetDeliveryConfirmationTransactionFee, - >; + type MessageDeliveryAndDispatchPayment = (); type OnMessageAccepted = (); type OnDeliveryConfirmed = (); @@ -513,6 +508,9 @@ construct_runtime!( Registrar: polkadot_runtime_common::paras_registrar::{Pallet, Call, Storage, Event}, Slots: polkadot_runtime_common::slots::{Pallet, Call, Storage, Event}, ParasSudoWrapper: polkadot_runtime_common::paras_sudo_wrapper::{Pallet, Call}, + + // Pallet for sending XCM. + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 99, } ); @@ -902,6 +900,7 @@ impl_runtime_apis! { Runtime, WithMillauMessagesInstance, WithMillauMessageBridge, + xcm_config::OutboundXcmWeigher, >(lane, begin, end) } } diff --git a/bridges/bin/rialto/runtime/src/millau_messages.rs b/bridges/bin/rialto/runtime/src/millau_messages.rs index 8088ee56ac754..ce1bd6b6fb0bc 100644 --- a/bridges/bin/rialto/runtime/src/millau_messages.rs +++ b/bridges/bin/rialto/runtime/src/millau_messages.rs @@ -16,7 +16,7 @@ //! Everything required to serve Millau <-> Rialto messages. -use crate::Runtime; +use crate::{Call, OriginCaller, Runtime}; use bp_messages::{ source_chain::{SenderOrigin, TargetHeaderChain}, @@ -40,6 +40,10 @@ pub const INITIAL_MILLAU_TO_RIALTO_CONVERSION_RATE: FixedU128 = FixedU128::from_inner(FixedU128::DIV); /// Initial value of `MillauFeeMultiplier` parameter. pub const INITIAL_MILLAU_FEE_MULTIPLIER: FixedU128 = FixedU128::from_inner(FixedU128::DIV); +/// 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. +pub const BASE_XCM_WEIGHT_TWICE: Weight = 2 * crate::xcm_config::BASE_XCM_WEIGHT; parameter_types! { /// Millau to Rialto conversion rate. Initially we treat both tokens as equal. @@ -56,14 +60,15 @@ pub type ToMillauMessageVerifier = messages::source::FromThisChainMessageVerifier; /// Message payload for Millau -> Rialto messages. -pub type FromMillauMessagePayload = messages::target::FromBridgedChainMessagePayload; +pub type FromMillauMessagePayload = messages::target::FromBridgedChainMessagePayload; /// Call-dispatch based message dispatch for Millau -> Rialto messages. pub type FromMillauMessageDispatch = messages::target::FromBridgedChainMessageDispatch< WithMillauMessageBridge, - crate::Runtime, - pallet_balances::Pallet, - (), + xcm_executor::XcmExecutor, + crate::xcm_config::XcmWeigher, + // + frame_support::traits::ConstU64, >; /// Messages proof for Millau -> Rialto messages. @@ -115,7 +120,23 @@ impl messages::ThisChainWithMessages for Rialto { type Call = crate::Call; 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::xcm_config::UniversalLocation::get()); + match send_origin.caller { + OriginCaller::XcmPallet(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 { @@ -262,14 +283,8 @@ impl SourceHeaderChain for Millau { impl SenderOrigin for crate::Origin { fn linked_account(&self) -> Option { - match self.caller { - crate::OriginCaller::system(frame_system::RawOrigin::Signed(ref submitter)) => - Some(submitter.clone()), - crate::OriginCaller::system(frame_system::RawOrigin::Root) | - crate::OriginCaller::system(frame_system::RawOrigin::None) => - crate::RootAccountForPayments::get(), - _ => None, - } + // XCM deals wit fees in our deployments + None } } diff --git a/bridges/bin/rialto/runtime/src/xcm_config.rs b/bridges/bin/rialto/runtime/src/xcm_config.rs new file mode 100644 index 0000000000000..8cad3896b1b4b --- /dev/null +++ b/bridges/bin/rialto/runtime/src/xcm_config.rs @@ -0,0 +1,318 @@ +// 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 configurations for the Rialto runtime. + +use super::{ + millau_messages::WithMillauMessageBridge, AccountId, AllPalletsWithSystem, Balances, + BridgeMillauMessages, Call, Event, Origin, Runtime, 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 frame_support::{ + parameter_types, + traits::{Everything, Nothing}, + weights::Weight, +}; +use sp_std::marker::PhantomData; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, + CurrencyAdapter as XcmCurrencyAdapter, IsConcrete, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, +}; + +parameter_types! { + /// The location of the `MLAU` token, from the context of this chain. Since this token is native to this + /// 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(); + /// The Rialto network ID, associated with Polkadot. + pub const ThisNetwork: NetworkId = Polkadot; + /// The Millau network ID, associated with Kusama. + pub const MillauNetwork: NetworkId = Kusama; + + /// Our XCM location ancestry - i.e. our location within the Consensus Universe. + /// + /// Since Polkadot is a top-level relay-chain with its own consensus, it's just our network ID. + pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); + /// The check account, which holds any native assets that have been teleported out and not back in (yet). + pub CheckAccount: AccountId = XcmPallet::check_account(); +} + +/// The canonical means of converting a `MultiLocation` into an `AccountId`, used when we want to +/// determine the sovereign account controlled by a location. +pub type SovereignAccountOf = ( + // We can directly alias an `AccountId32` into a local account. + AccountId32Aliases, +); + +/// Our asset transactor. This is what allows us to interest with the runtime facilities from the +/// point of view of XCM-only concepts like `MultiLocation` and `MultiAsset`. +/// +/// Ours is only aware of the Balances pallet, which is mapped to `TokenLocation`. +pub type LocalAssetTransactor = XcmCurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // We can convert the MultiLocations with our converter above: + SovereignAccountOf, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We track our teleports in/out to keep total issuance correct. + CheckAccount, +>; + +/// The means that we convert the XCM message origin location into a local dispatch origin. +type LocalOriginConverter = ( + // A `Signed` origin of the sovereign account that the original location controls. + SovereignSignedViaLocation, + // The AccountId32 location type can be expressed natively as a `Signed` origin. + SignedAccountId32AsNative, +); + +/// The amount of weight an XCM operation takes. This is a safe overestimate. +pub const BASE_XCM_WEIGHT: Weight = 1_000_000_000; + +parameter_types! { + /// The amount of weight an XCM operation takes. This is a safe overestimate. + pub const BaseXcmWeight: Weight = BASE_XCM_WEIGHT; + /// Maximum number of instructions in a single XCM fragment. A sanity check against weight + /// calculations getting too crazy. + pub const MaxInstructions: u32 = 100; +} + +/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our +/// individual routers. +pub type XcmRouter = ( + // Router to send messages to Millau. + ToMillauBridge, +); + +parameter_types! { + pub const MaxAssetsIntoHolding: u32 = 64; +} + +/// The barriers one of which must be passed for an XCM message to be executed. +pub type Barrier = ( + // Weight that is paid for may be consumed. + TakeWeightCredit, + // If the message is one that immediately attemps to pay for execution, then allow it. + AllowTopLevelPaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, +); + +/// Outbound XCM weigher type. +pub type OutboundXcmWeigher = xcm_builder::FixedWeightBounds; +/// Incoming XCM weigher type. +pub type XcmWeigher = xcm_builder::FixedWeightBounds; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type Call = Call; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = XcmWeigher; + // The weight trader piggybacks on the existing transaction-fee conversion logic. + type Trader = UsingComponents; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; +} + +/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior +/// location of this chain. +pub type LocalOriginToLocation = ( + // Usual Signed origin to be used in XCM as a corresponding AccountId32 + SignedToAccountId32, +); + +impl pallet_xcm::Config for Runtime { + type Event = Event; + // We don't allow any messages to be sent via the transaction yet. This is basically safe to + // enable, (safe the possibility of someone spamming the parachain if they're willing to pay + // the DOT to send from the Relay-chain). But it's useless until we bring in XCM v3 which will + // make `DescendOrigin` a bit more useful. + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // Anyone can execute XCM messages locally. + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = xcm_executor::XcmExecutor; + // Anyone is able to use teleportation regardless of who they are and what they want to + // teleport. + type XcmTeleportFilter = Everything; + // Anyone is able to use reserve transfers regardless of who they are and what they want to + // transfer. + type XcmReserveTransferFilter = Everything; + type Weigher = XcmWeigher; + type UniversalLocation = UniversalLocation; + type Origin = Origin; + type Call = Call; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + 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) + }; + + let dest: InteriorMultiLocation = MillauNetwork::get().into(); + let here = UniversalLocation::get(); + let route = dest.relative_to(&here); + let msg = (route, msg.take().unwrap()).encode(); + + 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)); + + Ok(((fee, msg), fee_assets)) + } + + 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") + }) + } +} + +#[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; + + 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 0x010109030419A8 + 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, 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 }, + } + .into(); + + 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/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 7e8609071f86c..8177f11512574 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -11,6 +11,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hash-db = { version = "0.15.2", default-features = false } log = { version = "0.4.14", default-features = false } +num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } static_assertions = { version = "1.1", optional = true } @@ -29,12 +30,19 @@ pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "m pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-api = { 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-io = { 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-state-machine = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +# Polkadot dependencies + +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 } + [features] default = ["std"] std = [ @@ -45,16 +53,21 @@ std = [ "frame-system/std", "hash-db/std", "log/std", + "num-traits/std", "pallet-bridge-grandpa/std", "pallet-bridge-messages/std", "pallet-transaction-payment/std", "scale-info/std", "sp-api/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-state-machine/std", "sp-std/std", "sp-trie/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", ] runtime-benchmarks = [ "pallet-balances", @@ -62,6 +75,7 @@ runtime-benchmarks = [ "pallet-bridge-messages/runtime-benchmarks", "sp-state-machine", "sp-version", + "xcm-builder/runtime-benchmarks", ] integrity-test = [ "static_assertions", diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs index 603f385f29ed5..a145d81e9b230 100644 --- a/bridges/bin/runtime-common/src/integrity.rs +++ b/bridges/bin/runtime-common/src/integrity.rs @@ -115,7 +115,7 @@ macro_rules! assert_bridge_messages_pallet_types( use $crate::messages::{ source::FromThisChainMessagePayload, target::FromBridgedChainMessagePayload, - AccountIdOf, BalanceOf, BridgedChain, ThisChain, WeightOf, + AccountIdOf, BalanceOf, BridgedChain, CallOf, ThisChain, WeightOf, }; use pallet_bridge_messages::Config as MessagesConfig; use static_assertions::assert_type_eq_all; @@ -123,7 +123,7 @@ macro_rules! assert_bridge_messages_pallet_types( assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, FromThisChainMessagePayload); assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundMessageFee, BalanceOf>); - assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundPayload, FromBridgedChainMessagePayload); + assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundPayload, FromBridgedChainMessagePayload>>); assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundMessageFee, BalanceOf>); assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundRelayer, AccountIdOf>); diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs index f7eb0b2975d6b..d3e3ca28ab736 100644 --- a/bridges/bin/runtime-common/src/messages.rs +++ b/bridges/bin/runtime-common/src/messages.rs @@ -26,12 +26,12 @@ use bp_messages::{ InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData, }; use bp_runtime::{messages::MessageDispatchResult, ChainId, Size, StorageProofChecker}; -use codec::{Decode, Encode}; -use frame_support::{traits::Currency, weights::Weight, RuntimeDebug}; +use codec::{Decode, DecodeLimit, Encode}; +use frame_support::{traits::Get, weights::Weight, RuntimeDebug}; use hash_db::Hasher; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedDiv, CheckedMul, Saturating}, + traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedDiv, CheckedMul}, FixedPointNumber, FixedPointOperand, FixedU128, }; use sp_std::{cmp::PartialOrd, convert::TryFrom, fmt::Debug, marker::PhantomData, vec::Vec}; @@ -430,7 +430,35 @@ pub mod target { use super::*; /// Decoded Bridged -> This message payload. - pub type FromBridgedChainMessagePayload = Vec; + #[derive(RuntimeDebug, PartialEq)] + pub struct FromBridgedChainMessagePayload { + /// Data that is actually sent over the wire. + pub xcm: (xcm::v3::MultiLocation, xcm::v3::Xcm), + /// Weight of the message, computed by the weigher. Unknown initially. + pub weight: Option, + } + + impl Decode for FromBridgedChainMessagePayload { + fn decode(input: &mut I) -> Result { + let _: codec::Compact = Decode::decode(input)?; + type XcmPairType = (xcm::v3::MultiLocation, xcm::v3::Xcm); + Ok(FromBridgedChainMessagePayload { + xcm: XcmPairType::::decode_with_depth_limit( + sp_api::MAX_EXTRINSIC_DEPTH, + input, + )?, + weight: None, + }) + } + } + + impl From<(xcm::v3::MultiLocation, xcm::v3::Xcm)> + for FromBridgedChainMessagePayload + { + fn from(xcm: (xcm::v3::MultiLocation, xcm::v3::Xcm)) -> Self { + FromBridgedChainMessagePayload { xcm, weight: None } + } + } /// Messages proof from bridged chain: /// @@ -464,38 +492,84 @@ pub mod target { /// Dispatching Bridged -> This chain messages. #[derive(RuntimeDebug, Clone, Copy)] - pub struct FromBridgedChainMessageDispatch { - _marker: PhantomData<(B, ThisRuntime, ThisCurrency, ThisDispatchInstance)>, + pub struct FromBridgedChainMessageDispatch { + _marker: PhantomData<(B, XcmExecutor, XcmWeigher, WeightCredit)>, } - impl + impl MessageDispatch>, BalanceOf>> - for FromBridgedChainMessageDispatch + for FromBridgedChainMessageDispatch where - BalanceOf>: Saturating + FixedPointOperand, - ThisDispatchInstance: 'static, - ThisRuntime: pallet_transaction_payment::Config, - ::OnChargeTransaction: - pallet_transaction_payment::OnChargeTransaction< - ThisRuntime, - Balance = BalanceOf>, - >, - ThisCurrency: Currency>, Balance = BalanceOf>>, + XcmExecutor: xcm::v3::ExecuteXcm>>, + XcmWeigher: xcm_executor::traits::WeightBounds>>, + WeightCredit: Get, { - type DispatchPayload = FromBridgedChainMessagePayload; + type DispatchPayload = FromBridgedChainMessagePayload>>; fn dispatch_weight( - _message: &DispatchMessage>>, + message: &mut DispatchMessage>>, ) -> frame_support::weights::Weight { - 0 + match message.data.payload { + Ok(ref mut payload) => { + // I have no idea why this method takes `&mut` reference and there's nothing + // about that in documentation. Hope it'll only mutate iff error is returned. + let weight = XcmWeigher::weight(&mut payload.xcm.1); + let weight = weight.unwrap_or_else(|e| { + log::debug!( + target: "runtime::bridge-dispatch", + "Failed to compute dispatch weight of incoming XCM message {:?}/{}: {:?}", + message.key.lane_id, + message.key.nonce, + e, + ); + + // we shall return 0 and then the XCM executor will fail to execute XCM + // if we'll return something else (e.g. maximal value), the lane may stuck + 0 + }); + + payload.weight = Some(weight); + weight + }, + _ => 0, + } } fn dispatch( _relayer_account: &AccountIdOf>, message: DispatchMessage>>, ) -> MessageDispatchResult { + use xcm::latest::*; + let message_id = (message.key.lane_id, message.key.nonce); - log::trace!(target: "runtime::bridge-dispatch", "Incoming message {:?}: {:?}", message_id, message.data.payload); + let do_dispatch = move || -> sp_std::result::Result { + let FromBridgedChainMessagePayload { xcm: (location, xcm), weight: weight_limit } = + message.data.payload?; + log::trace!( + target: "runtime::bridge-dispatch", + "Going to execute message {:?} (weight limit: {:?}): {:?} {:?}", + message_id, + weight_limit, + location, + xcm, + ); + let hash = message_id.using_encoded(sp_io::hashing::blake2_256); + + // if this cod will end up in production, this most likely needs to be set to zero + let weight_credit = WeightCredit::get(); + + let xcm_outcome = XcmExecutor::execute_xcm_in_credit( + location, + xcm, + hash, + weight_limit.unwrap_or(0), + weight_credit, + ); + Ok(xcm_outcome) + }; + + let xcm_outcome = do_dispatch(); + log::trace!(target: "runtime::bridge-dispatch", "Incoming message {:?} dispatched with result: {:?}", message_id, xcm_outcome); MessageDispatchResult { dispatch_result: true, unspent_weight: 0, @@ -685,6 +759,112 @@ pub mod target { } } +pub use xcm_copy::*; + +// copy of private types from xcm-builder/src/universal_exports.rs +pub mod xcm_copy { + use codec::{Decode, Encode}; + use frame_support::{ensure, traits::Get}; + use sp_std::{convert::TryInto, marker::PhantomData, prelude::*}; + use xcm::prelude::*; + use xcm_executor::traits::ExportXcm; + + pub trait DispatchBlob { + /// Dispatches an incoming blob and returns the unexpectable weight consumed by the + /// dispatch. + fn dispatch_blob(blob: Vec) -> Result<(), DispatchBlobError>; + } + + pub trait HaulBlob { + /// Sends a blob over some point-to-point link. This will generally be implemented by a + /// bridge. + fn haul_blob(blob: Vec); + } + + #[derive(Clone, Encode, Decode)] + pub struct BridgeMessage { + /// The message destination as a *Universal Location*. This means it begins with a + /// `GlobalConsensus` junction describing the network under which global consensus happens. + /// If this does not match our global consensus then it's a fatal error. + universal_dest: VersionedInteriorMultiLocation, + message: VersionedXcm<()>, + } + + pub enum DispatchBlobError { + Unbridgable, + InvalidEncoding, + UnsupportedLocationVersion, + UnsupportedXcmVersion, + RoutingError, + NonUniversalDestination, + WrongGlobal, + } + + pub struct BridgeBlobDispatcher(PhantomData<(Router, OurPlace)>); + impl> DispatchBlob + for BridgeBlobDispatcher + { + fn dispatch_blob(blob: Vec) -> Result<(), DispatchBlobError> { + let our_universal = OurPlace::get(); + let our_global = + our_universal.global_consensus().map_err(|()| DispatchBlobError::Unbridgable)?; + let BridgeMessage { universal_dest, message } = + Decode::decode(&mut &blob[..]).map_err(|_| DispatchBlobError::InvalidEncoding)?; + let universal_dest: InteriorMultiLocation = universal_dest + .try_into() + .map_err(|_| DispatchBlobError::UnsupportedLocationVersion)?; + // `universal_dest` is the desired destination within the universe: first we need to + // check we're in the right global consensus. + let intended_global = universal_dest + .global_consensus() + .map_err(|()| DispatchBlobError::NonUniversalDestination)?; + ensure!(intended_global == our_global, DispatchBlobError::WrongGlobal); + let dest = universal_dest.relative_to(&our_universal); + let message: Xcm<()> = + message.try_into().map_err(|_| DispatchBlobError::UnsupportedXcmVersion)?; + send_xcm::(dest, message).map_err(|_| DispatchBlobError::RoutingError)?; + Ok(()) + } + } + + pub struct HaulBlobExporter( + PhantomData<(Bridge, BridgedNetwork, Price)>, + ); + impl, Price: Get> ExportXcm + for HaulBlobExporter + { + type Ticket = (Vec, XcmHash); + + fn validate( + network: NetworkId, + _channel: u32, + destination: &mut Option, + message: &mut Option>, + ) -> Result<((Vec, XcmHash), MultiAssets), SendError> { + let bridged_network = BridgedNetwork::get(); + ensure!(&network == &bridged_network, SendError::NotApplicable); + // We don't/can't use the `channel` for this adapter. + let dest = destination.take().ok_or(SendError::MissingArgument)?; + let universal_dest = match dest.pushed_front_with(GlobalConsensus(bridged_network)) { + Ok(d) => d.into(), + Err((dest, _)) => { + *destination = Some(dest); + return Err(SendError::NotApplicable) + }, + }; + let message = VersionedXcm::from(message.take().ok_or(SendError::MissingArgument)?); + let hash = message.using_encoded(sp_io::hashing::blake2_256); + let blob = BridgeMessage { universal_dest, message }.encode(); + Ok(((blob, hash), Price::get())) + } + + fn deliver((blob, hash): (Vec, XcmHash)) -> Result { + Bridge::haul_blob(blob); + Ok(hash) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/bridges/bin/runtime-common/src/messages_api.rs b/bridges/bin/runtime-common/src/messages_api.rs index 274d2ec2e00f8..4f5a175e24046 100644 --- a/bridges/bin/runtime-common/src/messages_api.rs +++ b/bridges/bin/runtime-common/src/messages_api.rs @@ -16,13 +16,15 @@ //! Helpers for implementing various message-related runtime API mthods. -use crate::messages::MessageBridge; +use crate::messages::{target::FromBridgedChainMessagePayload, MessageBridge}; -use bp_messages::{LaneId, MessageDetails, MessageNonce}; +use bp_messages::{LaneId, MessageDetails, MessageKey, MessageNonce}; +use codec::Decode; +use frame_support::weights::Weight; use sp_std::vec::Vec; /// Implementation of the `To*OutboundLaneApi::message_details`. -pub fn outbound_message_details( +pub fn outbound_message_details( lane: LaneId, begin: MessageNonce, end: MessageNonce, @@ -31,6 +33,7 @@ where Runtime: pallet_bridge_messages::Config, MessagesPalletInstance: 'static, BridgeConfig: MessageBridge, + XcmWeigher: xcm_executor::traits::WeightBounds<()>, { (begin..=end) .filter_map(|nonce| { @@ -38,7 +41,12 @@ where pallet_bridge_messages::Pallet::::outbound_message_data(lane, nonce)?; Some(MessageDetails { nonce, - dispatch_weight: 0, + // this shall match the similar code in the `FromBridgedChainMessageDispatch` - if we have failed + // to decode or estimate dispatch weight, we'll just return 0 to disable actual execution + dispatch_weight: compute_message_weight::( + MessageKey { lane_id: lane, nonce }, + &message_data.payload, + ).unwrap_or(0), size: message_data.payload.len() as _, delivery_and_dispatch_fee: message_data.fee, dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtTargetChain, @@ -46,3 +54,30 @@ where }) .collect() } + +// at the source chain we don't know the type of target chain `Call` => `()` is used (it is +// similarly currently used in Polkadot codebase) +fn compute_message_weight>( + message_key: MessageKey, + encoded_payload: &[u8], +) -> Result { + let mut payload = FromBridgedChainMessagePayload::<()>::decode(&mut &encoded_payload[..]) + .map_err(|e| { + log::debug!( + target: "runtime::bridge-dispatch", + "Failed to decode outbound XCM message {:?}: {:?}", + message_key, + e, + ); + })?; + let weight = XcmWeigher::weight(&mut payload.xcm.1); + let weight = weight.map_err(|e| { + log::debug!( + target: "runtime::bridge-dispatch", + "Failed to compute dispatch weight of outbound XCM message {:?}: {:?}", + message_key, + e, + ); + })?; + Ok(weight) +} diff --git a/bridges/modules/messages/src/instant_payments.rs b/bridges/modules/messages/src/instant_payments.rs deleted file mode 100644 index 2a620a9522258..0000000000000 --- a/bridges/modules/messages/src/instant_payments.rs +++ /dev/null @@ -1,350 +0,0 @@ -// 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 . - -//! Implementation of `MessageDeliveryAndDispatchPayment` trait on top of `Currency` trait. -//! -//! The payment is first transferred to a special `relayers-fund` account and only transferred -//! to the actual relayer in case confirmation is received. - -use crate::OutboundMessages; - -use bp_messages::{ - source_chain::{MessageDeliveryAndDispatchPayment, RelayersRewards, SenderOrigin}, - LaneId, MessageKey, MessageNonce, UnrewardedRelayer, -}; -use codec::Encode; -use frame_support::traits::{Currency as CurrencyT, ExistenceRequirement, Get}; -use num_traits::{SaturatingAdd, Zero}; -use sp_runtime::traits::Saturating; -use sp_std::{collections::vec_deque::VecDeque, fmt::Debug, ops::RangeInclusive}; - -/// Error that occurs when message fee is non-zero, but payer is not defined. -const NON_ZERO_MESSAGE_FEE_CANT_BE_PAID_BY_NONE: &str = - "Non-zero message fee can't be paid by "; - -/// Instant message payments made in given currency. -/// -/// The balance is initially reserved in a special `relayers-fund` account, and transferred -/// to the relayer when message delivery is confirmed. -/// -/// Additionally, confirmation transaction submitter (`confirmation_relayer`) is reimbursed -/// with the confirmation rewards (part of message fee, reserved to pay for delivery confirmation). -/// -/// NOTE The `relayers-fund` account must always exist i.e. be over Existential Deposit (ED; the -/// pallet enforces that) to make sure that even if the message cost is below ED it is still paid -/// to the relayer account. -/// NOTE It's within relayer's interest to keep their balance above ED as well, to make sure they -/// can receive the payment. -pub struct InstantCurrencyPayments { - _phantom: sp_std::marker::PhantomData<(T, I, Currency, GetConfirmationFee)>, -} - -impl - MessageDeliveryAndDispatchPayment - for InstantCurrencyPayments -where - T: frame_system::Config + crate::Config, - I: 'static, - T::Origin: SenderOrigin, - Currency: CurrencyT, - Currency::Balance: From, - GetConfirmationFee: Get, -{ - type Error = &'static str; - - fn pay_delivery_and_dispatch_fee( - submitter: &T::Origin, - fee: &Currency::Balance, - relayer_fund_account: &T::AccountId, - ) -> Result<(), Self::Error> { - let submitter_account = match submitter.linked_account() { - Some(submitter_account) => submitter_account, - None if !fee.is_zero() => { - // if we'll accept some message that has declared that the `fee` has been paid but - // it isn't actually paid, then it'll lead to problems with delivery confirmation - // payments (see `pay_relayer_rewards` && `confirmation_relayer` in particular) - return Err(NON_ZERO_MESSAGE_FEE_CANT_BE_PAID_BY_NONE) - }, - None => { - // message lane verifier has accepted the message before, so this message - // is unpaid **by design** - // => let's just do nothing - return Ok(()) - }, - }; - - if !frame_system::Pallet::::account_exists(relayer_fund_account) { - return Err("The relayer fund account must exist for the message lanes pallet to work correctly."); - } - - Currency::transfer( - &submitter_account, - relayer_fund_account, - *fee, - // it's fine for the submitter to go below Existential Deposit and die. - ExistenceRequirement::AllowDeath, - ) - .map_err(Into::into) - } - - fn pay_relayers_rewards( - lane_id: LaneId, - messages_relayers: VecDeque>, - confirmation_relayer: &T::AccountId, - received_range: &RangeInclusive, - relayer_fund_account: &T::AccountId, - ) { - let relayers_rewards = - cal_relayers_rewards::(lane_id, messages_relayers, received_range); - if !relayers_rewards.is_empty() { - pay_relayers_rewards::( - confirmation_relayer, - relayers_rewards, - relayer_fund_account, - GetConfirmationFee::get(), - ); - } - } -} - -/// Calculate the relayers rewards -pub(crate) fn cal_relayers_rewards( - lane_id: LaneId, - messages_relayers: VecDeque>, - received_range: &RangeInclusive, -) -> RelayersRewards -where - T: frame_system::Config + crate::Config, - I: 'static, -{ - // remember to reward relayers that have delivered messages - // this loop is bounded by `T::MaxUnrewardedRelayerEntriesAtInboundLane` on the bridged chain - let mut relayers_rewards: RelayersRewards<_, T::OutboundMessageFee> = RelayersRewards::new(); - for entry in messages_relayers { - let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start()); - let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end()); - - // loop won't proceed if current entry is ahead of received range (begin > end). - // this loop is bound by `T::MaxUnconfirmedMessagesAtInboundLane` on the bridged chain - let mut relayer_reward = relayers_rewards.entry(entry.relayer).or_default(); - for nonce in nonce_begin..nonce_end + 1 { - let message_data = OutboundMessages::::get(MessageKey { lane_id, nonce }) - .expect("message was just confirmed; we never prune unconfirmed messages; qed"); - relayer_reward.reward = relayer_reward.reward.saturating_add(&message_data.fee); - relayer_reward.messages += 1; - } - } - relayers_rewards -} - -/// Pay rewards to given relayers, optionally rewarding confirmation relayer. -fn pay_relayers_rewards( - confirmation_relayer: &AccountId, - relayers_rewards: RelayersRewards, - relayer_fund_account: &AccountId, - confirmation_fee: Currency::Balance, -) where - AccountId: Debug + Encode + PartialEq, - Currency: CurrencyT, - Currency::Balance: From, -{ - // reward every relayer except `confirmation_relayer` - let mut confirmation_relayer_reward = Currency::Balance::zero(); - for (relayer, reward) in relayers_rewards { - let mut relayer_reward = reward.reward; - - if relayer != *confirmation_relayer { - // If delivery confirmation is submitted by other relayer, let's deduct confirmation fee - // from relayer reward. - // - // If confirmation fee has been increased (or if it was the only component of message - // fee), then messages relayer may receive zero reward. - let mut confirmation_reward = confirmation_fee.saturating_mul(reward.messages.into()); - if confirmation_reward > relayer_reward { - confirmation_reward = relayer_reward; - } - relayer_reward = relayer_reward.saturating_sub(confirmation_reward); - confirmation_relayer_reward = - confirmation_relayer_reward.saturating_add(confirmation_reward); - } else { - // If delivery confirmation is submitted by this relayer, let's add confirmation fee - // from other relayers to this relayer reward. - confirmation_relayer_reward = confirmation_relayer_reward.saturating_add(reward.reward); - continue - } - - pay_relayer_reward::(relayer_fund_account, &relayer, relayer_reward); - } - - // finally - pay reward to confirmation relayer - pay_relayer_reward::( - relayer_fund_account, - confirmation_relayer, - confirmation_relayer_reward, - ); -} - -/// Transfer funds from relayers fund account to given relayer. -fn pay_relayer_reward( - relayer_fund_account: &AccountId, - relayer_account: &AccountId, - reward: Currency::Balance, -) where - AccountId: Debug, - Currency: CurrencyT, -{ - if reward.is_zero() { - return - } - - let pay_result = Currency::transfer( - relayer_fund_account, - relayer_account, - reward, - // the relayer fund account must stay above ED (needs to be pre-funded) - ExistenceRequirement::KeepAlive, - ); - - match pay_result { - Ok(_) => log::trace!( - target: "runtime::bridge-messages", - "Rewarded relayer {:?} with {:?}", - relayer_account, - reward, - ), - Err(error) => log::trace!( - target: "runtime::bridge-messages", - "Failed to pay relayer {:?} reward {:?}: {:?}", - relayer_account, - reward, - error, - ), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mock::{ - run_test, AccountId as TestAccountId, Balance as TestBalance, Origin, TestRuntime, - }; - use bp_messages::source_chain::RelayerRewards; - - type Balances = pallet_balances::Pallet; - - const RELAYER_1: TestAccountId = 1; - const RELAYER_2: TestAccountId = 2; - const RELAYER_3: TestAccountId = 3; - const RELAYERS_FUND_ACCOUNT: TestAccountId = crate::mock::ENDOWED_ACCOUNT; - - fn relayers_rewards() -> RelayersRewards { - vec![ - (RELAYER_1, RelayerRewards { reward: 100, messages: 2 }), - (RELAYER_2, RelayerRewards { reward: 100, messages: 3 }), - ] - .into_iter() - .collect() - } - - #[test] - fn pay_delivery_and_dispatch_fee_fails_on_non_zero_fee_and_unknown_payer() { - frame_support::parameter_types! { - const GetConfirmationFee: TestBalance = 0; - }; - - run_test(|| { - let result = InstantCurrencyPayments::< - TestRuntime, - (), - Balances, - GetConfirmationFee, - >::pay_delivery_and_dispatch_fee( - &Origin::root(), - &100, - &RELAYERS_FUND_ACCOUNT, - ); - assert_eq!(result, Err(NON_ZERO_MESSAGE_FEE_CANT_BE_PAID_BY_NONE)); - }); - } - - #[test] - fn pay_delivery_and_dispatch_succeeds_on_zero_fee_and_unknown_payer() { - frame_support::parameter_types! { - const GetConfirmationFee: TestBalance = 0; - }; - - run_test(|| { - let result = InstantCurrencyPayments::< - TestRuntime, - (), - Balances, - GetConfirmationFee, - >::pay_delivery_and_dispatch_fee( - &Origin::root(), - &0, - &RELAYERS_FUND_ACCOUNT, - ); - assert!(result.is_ok()); - }); - } - - #[test] - fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() { - run_test(|| { - pay_relayers_rewards::( - &RELAYER_2, - relayers_rewards(), - &RELAYERS_FUND_ACCOUNT, - 10, - ); - - assert_eq!(Balances::free_balance(&RELAYER_1), 80); - assert_eq!(Balances::free_balance(&RELAYER_2), 120); - }); - } - - #[test] - fn confirmation_relayer_is_rewarded_if_it_has_not_delivered_any_delivered_messages() { - run_test(|| { - pay_relayers_rewards::( - &RELAYER_3, - relayers_rewards(), - &RELAYERS_FUND_ACCOUNT, - 10, - ); - - assert_eq!(Balances::free_balance(&RELAYER_1), 80); - assert_eq!(Balances::free_balance(&RELAYER_2), 70); - assert_eq!(Balances::free_balance(&RELAYER_3), 50); - }); - } - - #[test] - fn only_confirmation_relayer_is_rewarded_if_confirmation_fee_has_significantly_increased() { - run_test(|| { - pay_relayers_rewards::( - &RELAYER_3, - relayers_rewards(), - &RELAYERS_FUND_ACCOUNT, - 1000, - ); - - assert_eq!(Balances::free_balance(&RELAYER_1), 0); - assert_eq!(Balances::free_balance(&RELAYER_2), 0); - assert_eq!(Balances::free_balance(&RELAYER_3), 200); - }); - } -} diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index 10cdaae40712c..5c3398ff705f4 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -51,14 +51,14 @@ use crate::{ use bp_messages::{ source_chain::{ LaneMessageVerifier, MessageDeliveryAndDispatchPayment, OnDeliveryConfirmed, - OnMessageAccepted, SendMessageArtifacts, TargetHeaderChain, + OnMessageAccepted, RelayersRewards, SendMessageArtifacts, TargetHeaderChain, }, target_chain::{ DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, }, total_unrewarded_messages, DeliveredMessages, InboundLaneData, LaneId, MessageData, MessageKey, MessageNonce, OperatingMode, OutboundLaneData, Parameter as MessagesParameter, - UnrewardedRelayersState, + UnrewardedRelayer, UnrewardedRelayersState, }; use bp_runtime::{ChainId, Size}; use codec::{Decode, Encode}; @@ -71,13 +71,15 @@ use frame_system::RawOrigin; use num_traits::{SaturatingAdd, Zero}; use sp_core::H256; use sp_runtime::traits::{BadOrigin, Convert}; -use sp_std::{cell::RefCell, cmp::PartialOrd, marker::PhantomData, prelude::*}; +use sp_std::{ + cell::RefCell, cmp::PartialOrd, collections::vec_deque::VecDeque, marker::PhantomData, + ops::RangeInclusive, prelude::*, +}; mod inbound_lane; mod outbound_lane; mod weights_ext; -pub mod instant_payments; pub mod weights; #[cfg(feature = "runtime-benchmarks")] @@ -426,13 +428,13 @@ pub mod pallet { } } - for message in lane_data.messages { + for mut message in lane_data.messages { debug_assert_eq!(message.key.lane_id, lane_id); // ensure that relayer has declared enough weight for dispatching next message // on this lane. We can't dispatch lane messages out-of-order, so if declared // weight is not enough, let's move to next lane - let dispatch_weight = T::MessageDispatch::dispatch_weight(&message); + let dispatch_weight = T::MessageDispatch::dispatch_weight(&mut message); if dispatch_weight > dispatch_weight_left { log::trace!( target: "runtime::bridge-messages", @@ -767,7 +769,6 @@ pub mod pallet { /// /// This account is passed to `MessageDeliveryAndDispatchPayment` trait, and depending /// on the implementation it can be used to store relayers rewards. -/// See [`InstantCurrencyPayments`] for a concrete implementation. pub fn relayer_fund_account_id>( ) -> AccountId { let encoded_id = bp_runtime::derive_relayer_fund_account_id(bp_runtime::NO_INSTANCE_ID); @@ -921,6 +922,36 @@ fn send_message, I: 'static>( Ok(SendMessageArtifacts { nonce, weight: actual_weight }) } +/// Calculate the relayers rewards +pub fn calc_relayers_rewards( + lane_id: LaneId, + messages_relayers: VecDeque>, + received_range: &RangeInclusive, +) -> RelayersRewards +where + T: frame_system::Config + crate::Config, + I: 'static, +{ + // remember to reward relayers that have delivered messages + // this loop is bounded by `T::MaxUnrewardedRelayerEntriesAtInboundLane` on the bridged chain + let mut relayers_rewards: RelayersRewards<_, T::OutboundMessageFee> = RelayersRewards::new(); + for entry in messages_relayers { + let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start()); + let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end()); + + // loop won't proceed if current entry is ahead of received range (begin > end). + // this loop is bound by `T::MaxUnconfirmedMessagesAtInboundLane` on the bridged chain + let mut relayer_reward = relayers_rewards.entry(entry.relayer).or_default(); + for nonce in nonce_begin..nonce_end + 1 { + let message_data = OutboundMessages::::get(MessageKey { lane_id, nonce }) + .expect("message was just confirmed; we never prune unconfirmed messages; qed"); + relayer_reward.reward = relayer_reward.reward.saturating_add(&message_data.fee); + relayer_reward.messages += 1; + } + } + relayers_rewards +} + /// Ensure that the origin is either root, or `PalletOwner`. fn ensure_owner_or_root, I: 'static>(origin: T::Origin) -> Result<(), BadOrigin> { match origin.into() { diff --git a/bridges/modules/messages/src/mock.rs b/bridges/modules/messages/src/mock.rs index 5bf36f6809cc0..a4ecb0467b79a 100644 --- a/bridges/modules/messages/src/mock.rs +++ b/bridges/modules/messages/src/mock.rs @@ -17,7 +17,7 @@ // From construct_runtime macro #![allow(clippy::from_over_into)] -use crate::{instant_payments::cal_relayers_rewards, Config}; +use crate::{calc_relayers_rewards, Config}; use bitvec::prelude::*; use bp_messages::{ @@ -375,7 +375,7 @@ impl MessageDeliveryAndDispatchPayment _relayer_fund_account: &AccountId, ) { let relayers_rewards = - cal_relayers_rewards::(lane_id, message_relayers, received_range); + calc_relayers_rewards::(lane_id, message_relayers, received_range); for (relayer, reward) in &relayers_rewards { let key = (b":relayer-reward:", relayer, reward.reward).encode(); frame_support::storage::unhashed::put(&key, &true); @@ -489,7 +489,7 @@ pub struct TestMessageDispatch; impl MessageDispatch for TestMessageDispatch { type DispatchPayload = TestPayload; - fn dispatch_weight(message: &DispatchMessage) -> Weight { + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { match message.data.payload.as_ref() { Ok(payload) => payload.declared_weight, Err(_) => 0, diff --git a/bridges/primitives/messages/src/source_chain.rs b/bridges/primitives/messages/src/source_chain.rs index fa7b3bb85ed05..28ce49acc0d7b 100644 --- a/bridges/primitives/messages/src/source_chain.rs +++ b/bridges/primitives/messages/src/source_chain.rs @@ -149,6 +149,29 @@ pub trait MessageDeliveryAndDispatchPayment { ); } +impl + MessageDeliveryAndDispatchPayment for () +{ + type Error = &'static str; + + fn pay_delivery_and_dispatch_fee( + _submitter: &SenderOrigin, + _fee: &Balance, + _relayer_fund_account: &AccountId, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn pay_relayers_rewards( + _lane_id: LaneId, + _messages_relayers: VecDeque>, + _confirmation_relayer: &AccountId, + _received_range: &RangeInclusive, + _relayer_fund_account: &AccountId, + ) { + } +} + /// Send message artifacts. #[derive(RuntimeDebug, PartialEq)] pub struct SendMessageArtifacts { diff --git a/bridges/primitives/messages/src/target_chain.rs b/bridges/primitives/messages/src/target_chain.rs index a84ea7af907de..1ee0d7d631089 100644 --- a/bridges/primitives/messages/src/target_chain.rs +++ b/bridges/primitives/messages/src/target_chain.rs @@ -95,7 +95,7 @@ pub trait MessageDispatch { /// /// This function must: (1) be instant and (2) return correct upper bound /// of dispatch weight. - fn dispatch_weight(message: &DispatchMessage) -> Weight; + fn dispatch_weight(message: &mut DispatchMessage) -> Weight; /// Called when inbound message is received. /// @@ -156,7 +156,7 @@ impl SourceHeaderChain for ForbidInboundMessages { impl MessageDispatch for ForbidInboundMessages { type DispatchPayload = (); - fn dispatch_weight(_message: &DispatchMessage) -> Weight { + fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { Weight::MAX }