// 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 . //! Runtime module that allows sending and receiving messages using lane concept: //! //! 1) the message is sent using `send_message()` call; //! 2) every outbound message is assigned nonce; //! 3) the messages are stored in the storage; //! 4) external component (relay) delivers messages to bridged chain; //! 5) messages are processed in order (ordered by assigned nonce); //! 6) relay may send proof-of-delivery back to this chain. //! //! Once message is sent, its progress can be tracked by looking at module events. //! The assigned nonce is reported using `MessageAccepted` event. When message is //! delivered to the the bridged chain, it is reported using `MessagesDelivered` event. //! //! **IMPORTANT NOTE**: after generating weights (custom `WeighInfo` implementation) for //! your runtime (where this module is plugged to), please add test for these weights. //! The test should call the `ensure_weights_are_correct` function from this module. //! If this test fails with your weights, then either weights are computed incorrectly, //! or some benchmarks assumptions are broken for your runtime. #![cfg_attr(not(feature = "std"), no_std)] // Generated by `decl_event!` #![allow(clippy::unused_unit)] pub use inbound_lane::StoredInboundLaneData; pub use outbound_lane::StoredMessageData; pub use weights::WeightInfo; pub use weights_ext::{ ensure_able_to_receive_confirmation, ensure_able_to_receive_message, ensure_weights_are_correct, WeightInfoExt, EXPECTED_DEFAULT_MESSAGE_LENGTH, }; use crate::{ inbound_lane::{InboundLane, InboundLaneStorage, ReceivalResult}, outbound_lane::{OutboundLane, OutboundLaneStorage, ReceivalConfirmationResult}, }; use bp_messages::{ source_chain::{ LaneMessageVerifier, MessageDeliveryAndDispatchPayment, OnDeliveryConfirmed, OnMessageAccepted, RelayersRewards, SendMessageArtifacts, TargetHeaderChain, }, target_chain::{ DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, }, total_unrewarded_messages, DeliveredMessages, InboundLaneData, InboundMessageDetails, LaneId, MessageData, MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, Parameter as MessagesParameter, UnrewardedRelayer, UnrewardedRelayersState, }; use bp_runtime::{BasicOperatingMode, ChainId, OwnedBridgeModule, Size}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ ensure, fail, traits::Get, weights::{Pays, PostDispatchInfo}, }; use num_traits::{SaturatingAdd, Zero}; 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 weights; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; #[cfg(test)] mod mock; pub use pallet::*; /// The target that will be used when publishing logs related to this pallet. const LOG_TARGET: &str = "runtime::bridge-messages"; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { // General types /// The overarching event type. type Event: From> + IsType<::Event>; /// Benchmarks results from runtime we're plugged into. type WeightInfo: WeightInfoExt; /// Gets the chain id value from the instance. #[pallet::constant] type BridgedChainId: Get; /// Pallet parameter that is opaque to the pallet itself, but may be used by the runtime /// for integrating the pallet. /// /// All pallet parameters may only be updated either by the root, or by the pallet owner. type Parameter: MessagesParameter; /// Maximal number of messages that may be pruned during maintenance. Maintenance occurs /// whenever new message is sent. The reason is that if you want to use lane, you should /// be ready to pay for its maintenance. type MaxMessagesToPruneAtOnce: Get; /// Maximal number of unrewarded relayer entries at inbound lane. Unrewarded means that the /// relayer has delivered messages, but either confirmations haven't been delivered back to /// the source chain, or we haven't received reward confirmations yet. /// /// This constant limits maximal number of entries in the `InboundLaneData::relayers`. Keep /// in mind that the same relayer account may take several (non-consecutive) entries in this /// set. type MaxUnrewardedRelayerEntriesAtInboundLane: Get; /// Maximal number of unconfirmed messages at inbound lane. Unconfirmed means that the /// message has been delivered, but either confirmations haven't been delivered back to the /// source chain, or we haven't received reward confirmations for these messages yet. /// /// This constant limits difference between last message from last entry of the /// `InboundLaneData::relayers` and first message at the first entry. /// /// There is no point of making this parameter lesser than /// MaxUnrewardedRelayerEntriesAtInboundLane, because then maximal number of relayer entries /// will be limited by maximal number of messages. /// /// This value also represents maximal number of messages in single delivery transaction. /// Transaction that is declaring more messages than this value, will be rejected. Even if /// these messages are from different lanes. type MaxUnconfirmedMessagesAtInboundLane: Get; /// Maximal size of the outbound payload. #[pallet::constant] type MaximalOutboundPayloadSize: Get; /// Payload type of outbound messages. This payload is dispatched on the bridged chain. type OutboundPayload: Parameter + Size; /// Message fee type of outbound messages. This fee is paid on this chain. type OutboundMessageFee: Default + From + PartialOrd + Parameter + SaturatingAdd + Zero + Copy + MaxEncodedLen; /// Payload type of inbound messages. This payload is dispatched on this chain. type InboundPayload: Decode; /// Message fee type of inbound messages. This fee is paid on the bridged chain. type InboundMessageFee: Decode + Zero; /// Identifier of relayer that deliver messages to this chain. Relayer reward is paid on the /// bridged chain. type InboundRelayer: Parameter + MaxEncodedLen; // Types that are used by outbound_lane (on source chain). /// Target header chain. type TargetHeaderChain: TargetHeaderChain; /// Message payload verifier. type LaneMessageVerifier: LaneMessageVerifier< Self::Origin, Self::AccountId, Self::OutboundPayload, Self::OutboundMessageFee, >; /// Message delivery payment. type MessageDeliveryAndDispatchPayment: MessageDeliveryAndDispatchPayment< Self::Origin, Self::AccountId, Self::OutboundMessageFee, >; /// Handler for accepted messages. type OnMessageAccepted: OnMessageAccepted; /// Handler for delivered messages. type OnDeliveryConfirmed: OnDeliveryConfirmed; // Types that are used by inbound_lane (on target chain). /// Source header chain, as it is represented on target chain. type SourceHeaderChain: SourceHeaderChain; /// Message dispatch. type MessageDispatch: MessageDispatch< Self::AccountId, Self::InboundMessageFee, DispatchPayload = Self::InboundPayload, >; } /// Shortcut to messages proof type for Config. type MessagesProofOf = <>::SourceHeaderChain as SourceHeaderChain< >::InboundMessageFee, >>::MessagesProof; /// Shortcut to messages delivery proof type for Config. type MessagesDeliveryProofOf = <>::TargetHeaderChain as TargetHeaderChain< >::OutboundPayload, ::AccountId, >>::MessagesDeliveryProof; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData<(T, I)>); impl, I: 'static> OwnedBridgeModule for Pallet { const LOG_TARGET: &'static str = LOG_TARGET; type OwnerStorage = PalletOwner; type OperatingMode = MessagesOperatingMode; type OperatingModeStorage = PalletOperatingMode; } #[pallet::call] impl, I: 'static> Pallet { /// Change `PalletOwner`. /// /// May only be called either by root, or by `PalletOwner`. #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { >::set_owner(origin, new_owner) } /// Halt or resume all/some pallet operations. /// /// May only be called either by root, or by `PalletOwner`. #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] pub fn set_operating_mode( origin: OriginFor, operating_mode: MessagesOperatingMode, ) -> DispatchResult { >::set_operating_mode(origin, operating_mode) } /// Update pallet parameter. /// /// May only be called either by root, or by `PalletOwner`. /// /// The weight is: single read for permissions check + 2 writes for parameter value and /// event. #[pallet::weight((T::DbWeight::get().reads_writes(1, 2), DispatchClass::Operational))] pub fn update_pallet_parameter( origin: OriginFor, parameter: T::Parameter, ) -> DispatchResult { Self::ensure_owner_or_root(origin)?; parameter.save(); Self::deposit_event(Event::ParameterUpdated { parameter }); Ok(()) } /// Send message over lane. #[pallet::weight(T::WeightInfo::send_message_weight(payload, T::DbWeight::get()))] pub fn send_message( origin: OriginFor, lane_id: LaneId, payload: T::OutboundPayload, delivery_and_dispatch_fee: T::OutboundMessageFee, ) -> DispatchResultWithPostInfo { crate::send_message::(origin, lane_id, payload, delivery_and_dispatch_fee).map( |sent_message| PostDispatchInfo { actual_weight: Some(sent_message.weight), pays_fee: Pays::Yes, }, ) } /// Pay additional fee for the message. #[pallet::weight(T::WeightInfo::maximal_increase_message_fee())] pub fn increase_message_fee( origin: OriginFor, lane_id: LaneId, nonce: MessageNonce, additional_fee: T::OutboundMessageFee, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; // if someone tries to pay for already-delivered message, we're rejecting this intention // (otherwise this additional fee will be locked forever in relayers fund) // // if someone tries to pay for not-yet-sent message, we're rejecting this intention, or // we're risking to have mess in the storage let lane = outbound_lane::(lane_id); ensure!( nonce > lane.data().latest_received_nonce, Error::::MessageIsAlreadyDelivered ); ensure!( nonce <= lane.data().latest_generated_nonce, Error::::MessageIsNotYetSent ); // withdraw additional fee from submitter T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee( &origin, &additional_fee, ) .map_err(|err| { log::trace!( target: LOG_TARGET, "Submitter can't pay additional fee {:?} for the message {:?}/{:?}: {:?}", additional_fee, lane_id, nonce, err, ); Error::::FailedToWithdrawMessageFee })?; // and finally update fee in the storage let message_key = MessageKey { lane_id, nonce }; let message_size = OutboundMessages::::mutate(message_key, |message_data| { // saturating_add is fine here - overflow here means that someone controls all // chain funds, which shouldn't ever happen + `pay_delivery_and_dispatch_fee` // above will fail before we reach here let message_data = message_data.as_mut().expect( "the message is sent and not yet delivered; so it is in the storage; qed", ); message_data.fee = message_data.fee.saturating_add(&additional_fee); message_data.payload.len() }); // compute actual dispatch weight that depends on the stored message size let actual_weight = sp_std::cmp::min( T::WeightInfo::maximal_increase_message_fee(), T::WeightInfo::increase_message_fee(message_size as _), ); Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) } /// Receive messages proof from bridged chain. /// /// The weight of the call assumes that the transaction always brings outbound lane /// state update. Because of that, the submitter (relayer) has no benefit of not including /// this data in the transaction, so reward confirmations lags should be minimal. #[pallet::weight(T::WeightInfo::receive_messages_proof_weight(proof, *messages_count, *dispatch_weight))] pub fn receive_messages_proof( origin: OriginFor, relayer_id_at_bridged_chain: T::InboundRelayer, proof: MessagesProofOf, messages_count: u32, dispatch_weight: Weight, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; let relayer_id_at_this_chain = ensure_signed(origin)?; // reject transactions that are declaring too many messages ensure!( MessageNonce::from(messages_count) <= T::MaxUnconfirmedMessagesAtInboundLane::get(), Error::::TooManyMessagesInTheProof ); // why do we need to know the weight of this (`receive_messages_proof`) call? Because // we may want to return some funds for not-dispatching (or partially dispatching) some // messages to the call origin (relayer). And this is done by returning actual weight // from the call. But we only know dispatch weight of every messages. So to refund // relayer because we have not dispatched Message, we need to: // // ActualWeight = DeclaredWeight - Message.DispatchWeight // // The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible // to get pre-computed value (and it has been already computed by the executive). let declared_weight = T::WeightInfo::receive_messages_proof_weight( &proof, messages_count, dispatch_weight, ); let mut actual_weight = declared_weight; // verify messages proof && convert proof into messages let messages = verify_and_decode_messages_proof::< T::SourceHeaderChain, T::InboundMessageFee, T::InboundPayload, >(proof, messages_count) .map_err(|err| { log::trace!(target: LOG_TARGET, "Rejecting invalid messages proof: {:?}", err,); Error::::InvalidMessagesProof })?; // dispatch messages and (optionally) update lane(s) state(s) let mut total_messages = 0; let mut valid_messages = 0; let mut dispatch_weight_left = dispatch_weight; for (lane_id, lane_data) in messages { let mut lane = inbound_lane::(lane_id); if let Some(lane_state) = lane_data.lane_state { let updated_latest_confirmed_nonce = lane.receive_state_update(lane_state); if let Some(updated_latest_confirmed_nonce) = updated_latest_confirmed_nonce { log::trace!( target: LOG_TARGET, "Received lane {:?} state update: latest_confirmed_nonce={}", lane_id, updated_latest_confirmed_nonce, ); } } 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(&mut message); if dispatch_weight > dispatch_weight_left { log::trace!( target: LOG_TARGET, "Cannot dispatch any more messages on lane {:?}. Weight: declared={}, left={}", lane_id, dispatch_weight, dispatch_weight_left, ); break } total_messages += 1; let receival_result = lane.receive_message::( &relayer_id_at_bridged_chain, &relayer_id_at_this_chain, message.key.nonce, message.data, ); // note that we're returning unspent weight to relayer even if message has been // rejected by the lane. This allows relayers to submit spam transactions with // e.g. the same set of already delivered messages over and over again, without // losing funds for messages dispatch. But keep in mind that relayer pays base // delivery transaction cost anyway. And base cost covers everything except // dispatch, so we have a balance here. let (unspent_weight, refund_pay_dispatch_fee) = match receival_result { ReceivalResult::Dispatched(dispatch_result) => { valid_messages += 1; ( dispatch_result.unspent_weight, !dispatch_result.dispatch_fee_paid_during_dispatch, ) }, ReceivalResult::InvalidNonce | ReceivalResult::TooManyUnrewardedRelayers | ReceivalResult::TooManyUnconfirmedMessages => (dispatch_weight, true), }; let unspent_weight = sp_std::cmp::min(unspent_weight, dispatch_weight); dispatch_weight_left -= dispatch_weight - unspent_weight; actual_weight = actual_weight.saturating_sub(unspent_weight).saturating_sub( // delivery call weight formula assumes that the fee is paid at // this (target) chain. If the message is prepaid at the source // chain, let's refund relayer with this extra cost. if refund_pay_dispatch_fee { T::WeightInfo::pay_inbound_dispatch_fee_overhead() } else { 0 }, ); } } log::trace!( target: LOG_TARGET, "Received messages: total={}, valid={}. Weight used: {}/{}", total_messages, valid_messages, actual_weight, declared_weight, ); Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) } /// Receive messages delivery proof from bridged chain. #[pallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight( proof, relayers_state, T::DbWeight::get(), ))] pub fn receive_messages_delivery_proof( origin: OriginFor, proof: MessagesDeliveryProofOf, relayers_state: UnrewardedRelayersState, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; // why do we need to know the weight of this (`receive_messages_delivery_proof`) call? // Because we may want to return some funds for messages that are not processed by the // delivery callback, or if their actual processing weight is less than accounted by // weight formula. So to refund relayer, we need to: // // ActualWeight = DeclaredWeight - UnspentCallbackWeight // // The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible // to get pre-computed value (and it has been already computed by the executive). let single_message_callback_overhead = T::WeightInfo::single_message_callback_overhead(T::DbWeight::get()); let declared_weight = T::WeightInfo::receive_messages_delivery_proof_weight( &proof, &relayers_state, T::DbWeight::get(), ); let mut actual_weight = declared_weight; let confirmation_relayer = ensure_signed(origin)?; let (lane_id, lane_data) = T::TargetHeaderChain::verify_messages_delivery_proof(proof) .map_err(|err| { log::trace!( target: LOG_TARGET, "Rejecting invalid messages delivery proof: {:?}", err, ); Error::::InvalidMessagesDeliveryProof })?; // verify that the relayer has declared correct `lane_data::relayers` state // (we only care about total number of entries and messages, because this affects call // weight) ensure!( total_unrewarded_messages(&lane_data.relayers).unwrap_or(MessageNonce::MAX) == relayers_state.total_messages && lane_data.relayers.len() as MessageNonce == relayers_state.unrewarded_relayer_entries, Error::::InvalidUnrewardedRelayersState ); // the `last_delivered_nonce` field may also be used by the signed extension. Even // though providing wrong value isn't critical, let's also check it here. ensure!( lane_data.last_delivered_nonce() == relayers_state.last_delivered_nonce, Error::::InvalidUnrewardedRelayersState ); // mark messages as delivered let mut lane = outbound_lane::(lane_id); let last_delivered_nonce = lane_data.last_delivered_nonce(); let confirmed_messages = match lane.confirm_delivery( relayers_state.total_messages, last_delivered_nonce, &lane_data.relayers, ) { ReceivalConfirmationResult::ConfirmedMessages(confirmed_messages) => Some(confirmed_messages), ReceivalConfirmationResult::NoNewConfirmations => None, ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected( to_confirm_messages_count, ) => { log::trace!( target: LOG_TARGET, "Messages delivery proof contains too many messages to confirm: {} vs declared {}", to_confirm_messages_count, relayers_state.total_messages, ); fail!(Error::::TryingToConfirmMoreMessagesThanExpected); }, error => { log::trace!( target: LOG_TARGET, "Messages delivery proof contains invalid unrewarded relayers vec: {:?}", error, ); fail!(Error::::InvalidUnrewardedRelayers); }, }; if let Some(confirmed_messages) = confirmed_messages { // handle messages delivery confirmation let preliminary_callback_overhead = relayers_state.total_messages.saturating_mul(single_message_callback_overhead); let actual_callback_weight = T::OnDeliveryConfirmed::on_messages_delivered(&lane_id, &confirmed_messages); match preliminary_callback_overhead.checked_sub(actual_callback_weight) { Some(difference) if difference == 0 => (), Some(difference) => { log::trace!( target: LOG_TARGET, "T::OnDeliveryConfirmed callback has spent less weight than expected. Refunding: \ {} - {} = {}", preliminary_callback_overhead, actual_callback_weight, difference, ); actual_weight = actual_weight.saturating_sub(difference); }, None => { debug_assert!( false, "T::OnDeliveryConfirmed callback consumed too much weight." ); log::error!( target: LOG_TARGET, "T::OnDeliveryConfirmed callback has spent more weight that it is allowed to: \ {} vs {}", preliminary_callback_overhead, actual_callback_weight, ); }, } // emit 'delivered' event let received_range = confirmed_messages.begin..=confirmed_messages.end; Self::deposit_event(Event::MessagesDelivered { lane_id, messages: confirmed_messages, }); // if some new messages have been confirmed, reward relayers >::MessageDeliveryAndDispatchPayment::pay_relayers_rewards( lane_id, lane_data.relayers, &confirmation_relayer, &received_range, ); } log::trace!( target: LOG_TARGET, "Received messages delivery proof up to (and including) {} at lane {:?}", last_delivered_nonce, lane_id, ); Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) } } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { /// Pallet parameter has been updated. ParameterUpdated { parameter: T::Parameter }, /// Message has been accepted and is waiting to be delivered. MessageAccepted { lane_id: LaneId, nonce: MessageNonce }, /// Messages in the inclusive range have been delivered to the bridged chain. MessagesDelivered { lane_id: LaneId, messages: DeliveredMessages }, } #[pallet::error] pub enum Error { /// Pallet is not in Normal operating mode. NotOperatingNormally, /// The message is too large to be sent over the bridge. MessageIsTooLarge, /// Message has been treated as invalid by chain verifier. MessageRejectedByChainVerifier, /// Message has been treated as invalid by lane verifier. MessageRejectedByLaneVerifier, /// Submitter has failed to pay fee for delivering and dispatching messages. FailedToWithdrawMessageFee, /// The transaction brings too many messages. TooManyMessagesInTheProof, /// Invalid messages has been submitted. InvalidMessagesProof, /// Invalid messages delivery proof has been submitted. InvalidMessagesDeliveryProof, /// The bridged chain has invalid `UnrewardedRelayers` in its storage (fatal for the lane). InvalidUnrewardedRelayers, /// The relayer has declared invalid unrewarded relayers state in the /// `receive_messages_delivery_proof` call. InvalidUnrewardedRelayersState, /// The message someone is trying to work with (i.e. increase fee) is already-delivered. MessageIsAlreadyDelivered, /// The message someone is trying to work with (i.e. increase fee) is not yet sent. MessageIsNotYetSent, /// The number of actually confirmed messages is going to be larger than the number of /// messages in the proof. This may mean that this or bridged chain storage is corrupted. TryingToConfirmMoreMessagesThanExpected, /// Error generated by the `OwnedBridgeModule` trait. BridgeModule(bp_runtime::OwnedBridgeModuleError), } /// Optional pallet owner. /// /// Pallet owner has a right to halt all pallet operations and then resume it. If it is /// `None`, then there are no direct ways to halt/resume pallet operations, but other /// runtime methods may still be used to do that (i.e. democracy::referendum to update halt /// flag directly or call the `halt_operations`). #[pallet::storage] #[pallet::getter(fn module_owner)] pub type PalletOwner, I: 'static = ()> = StorageValue<_, T::AccountId>; /// The current operating mode of the pallet. /// /// Depending on the mode either all, some, or no transactions will be allowed. #[pallet::storage] #[pallet::getter(fn operating_mode)] pub type PalletOperatingMode, I: 'static = ()> = StorageValue<_, MessagesOperatingMode, ValueQuery>; /// Map of lane id => inbound lane data. #[pallet::storage] pub type InboundLanes, I: 'static = ()> = StorageMap<_, Blake2_128Concat, LaneId, StoredInboundLaneData, ValueQuery>; /// Map of lane id => outbound lane data. #[pallet::storage] pub type OutboundLanes, I: 'static = ()> = StorageMap<_, Blake2_128Concat, LaneId, OutboundLaneData, ValueQuery>; /// All queued outbound messages. #[pallet::storage] pub type OutboundMessages, I: 'static = ()> = StorageMap<_, Blake2_128Concat, MessageKey, StoredMessageData>; #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { /// Initial pallet operating mode. pub operating_mode: MessagesOperatingMode, /// Initial pallet owner. pub owner: Option, /// Dummy marker. pub phantom: sp_std::marker::PhantomData, } #[cfg(feature = "std")] impl, I: 'static> Default for GenesisConfig { fn default() -> Self { Self { operating_mode: Default::default(), owner: Default::default(), phantom: Default::default(), } } } #[pallet::genesis_build] impl, I: 'static> GenesisBuild for GenesisConfig { fn build(&self) { PalletOperatingMode::::put(&self.operating_mode); if let Some(ref owner) = self.owner { PalletOwner::::put(owner); } } } impl, I: 'static> Pallet { /// Get stored data of the outbound message with given nonce. pub fn outbound_message_data( lane: LaneId, nonce: MessageNonce, ) -> Option> { OutboundMessages::::get(MessageKey { lane_id: lane, nonce }).map(Into::into) } /// Prepare data, related to given inbound message. pub fn inbound_message_data( lane: LaneId, payload: MessagePayload, outbound_details: OutboundMessageDetails, ) -> InboundMessageDetails { let mut dispatch_message = DispatchMessage { key: MessageKey { lane_id: lane, nonce: outbound_details.nonce }, data: MessageData { payload, fee: outbound_details.delivery_and_dispatch_fee } .into(), }; InboundMessageDetails { dispatch_weight: T::MessageDispatch::dispatch_weight(&mut dispatch_message), } } } } impl bp_messages::source_chain::MessagesBridge< T::Origin, T::AccountId, T::OutboundMessageFee, T::OutboundPayload, > for Pallet where T: Config, I: 'static, { type Error = sp_runtime::DispatchErrorWithPostInfo; fn send_message( sender: T::Origin, lane: LaneId, message: T::OutboundPayload, delivery_and_dispatch_fee: T::OutboundMessageFee, ) -> Result { crate::send_message::(sender, lane, message, delivery_and_dispatch_fee) } } /// Function that actually sends message. fn send_message, I: 'static>( submitter: T::Origin, lane_id: LaneId, payload: T::OutboundPayload, delivery_and_dispatch_fee: T::OutboundMessageFee, ) -> sp_std::result::Result< SendMessageArtifacts, sp_runtime::DispatchErrorWithPostInfo, > { ensure_normal_operating_mode::()?; // the most lightweigh check is the message size check ensure!( payload.size() < T::MaximalOutboundPayloadSize::get(), Error::::MessageIsTooLarge, ); // initially, actual (post-dispatch) weight is equal to pre-dispatch weight let mut actual_weight = T::WeightInfo::send_message_weight(&payload, T::DbWeight::get()); // let's first check if message can be delivered to target chain T::TargetHeaderChain::verify_message(&payload).map_err(|err| { log::trace!( target: LOG_TARGET, "Message to lane {:?} is rejected by target chain: {:?}", lane_id, err, ); Error::::MessageRejectedByChainVerifier })?; // now let's enforce any additional lane rules let mut lane = outbound_lane::(lane_id); T::LaneMessageVerifier::verify_message( &submitter, &delivery_and_dispatch_fee, &lane_id, &lane.data(), &payload, ) .map_err(|err| { log::trace!( target: LOG_TARGET, "Message to lane {:?} is rejected by lane verifier: {:?}", lane_id, err, ); Error::::MessageRejectedByLaneVerifier })?; // let's withdraw delivery and dispatch fee from submitter T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee( &submitter, &delivery_and_dispatch_fee, ) .map_err(|err| { log::trace!( target: LOG_TARGET, "Message to lane {:?} is rejected because submitter is unable to pay fee {:?}: {:?}", lane_id, delivery_and_dispatch_fee, err, ); Error::::FailedToWithdrawMessageFee })?; // finally, save message in outbound storage and emit event let encoded_payload = payload.encode(); let encoded_payload_len = encoded_payload.len(); let nonce = lane.send_message(MessageData { payload: encoded_payload, fee: delivery_and_dispatch_fee }); // Guaranteed to be called outside only when the message is accepted. // We assume that the maximum weight call back used is `single_message_callback_overhead`, so do // not perform complex db operation in callback. If you want to, put these magic logic in // outside pallet and control the weight there. let single_message_callback_overhead = T::WeightInfo::single_message_callback_overhead(T::DbWeight::get()); let actual_callback_weight = T::OnMessageAccepted::on_messages_accepted(&lane_id, &nonce); match single_message_callback_overhead.checked_sub(actual_callback_weight) { Some(difference) if difference == 0 => (), Some(difference) => { log::trace!( target: LOG_TARGET, "T::OnMessageAccepted callback has spent less weight than expected. Refunding: \ {} - {} = {}", single_message_callback_overhead, actual_callback_weight, difference, ); actual_weight = actual_weight.saturating_sub(difference); }, None => { debug_assert!(false, "T::OnMessageAccepted callback consumed too much weight."); log::error!( target: LOG_TARGET, "T::OnMessageAccepted callback has spent more weight that it is allowed to: \ {} vs {}", single_message_callback_overhead, actual_callback_weight, ); }, } // message sender pays for pruning at most `MaxMessagesToPruneAtOnce` messages // the cost of pruning every message is roughly single db write // => lets refund sender if less than `MaxMessagesToPruneAtOnce` messages pruned let max_messages_to_prune = T::MaxMessagesToPruneAtOnce::get(); let pruned_messages = lane.prune_messages(max_messages_to_prune); if let Some(extra_messages) = max_messages_to_prune.checked_sub(pruned_messages) { actual_weight = actual_weight.saturating_sub(T::DbWeight::get().writes(extra_messages)); } log::trace!( target: LOG_TARGET, "Accepted message {} to lane {:?}. Message size: {:?}", nonce, lane_id, encoded_payload_len, ); Pallet::::deposit_event(Event::MessageAccepted { lane_id, nonce }); 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 key = MessageKey { lane_id, nonce }; let message_data = OutboundMessages::::get(key) .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 pallet is in normal operational mode. fn ensure_normal_operating_mode, I: 'static>() -> Result<(), Error> { if PalletOperatingMode::::get() == MessagesOperatingMode::Basic(BasicOperatingMode::Normal) { return Ok(()) } Err(Error::::NotOperatingNormally) } /// Creates new inbound lane object, backed by runtime storage. fn inbound_lane, I: 'static>( lane_id: LaneId, ) -> InboundLane> { InboundLane::new(inbound_lane_storage::(lane_id)) } /// Creates new runtime inbound lane storage. fn inbound_lane_storage, I: 'static>( lane_id: LaneId, ) -> RuntimeInboundLaneStorage { RuntimeInboundLaneStorage { lane_id, cached_data: RefCell::new(None), _phantom: Default::default(), } } /// Creates new outbound lane object, backed by runtime storage. fn outbound_lane, I: 'static>( lane_id: LaneId, ) -> OutboundLane> { OutboundLane::new(RuntimeOutboundLaneStorage { lane_id, _phantom: Default::default() }) } /// Runtime inbound lane storage. struct RuntimeInboundLaneStorage, I: 'static = ()> { lane_id: LaneId, cached_data: RefCell>>, _phantom: PhantomData, } impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage { type MessageFee = T::InboundMessageFee; type Relayer = T::InboundRelayer; fn id(&self) -> LaneId { self.lane_id } fn max_unrewarded_relayer_entries(&self) -> MessageNonce { T::MaxUnrewardedRelayerEntriesAtInboundLane::get() } fn max_unconfirmed_messages(&self) -> MessageNonce { T::MaxUnconfirmedMessagesAtInboundLane::get() } fn data(&self) -> InboundLaneData { match self.cached_data.clone().into_inner() { Some(data) => data, None => { let data: InboundLaneData = InboundLanes::::get(&self.lane_id).into(); *self.cached_data.try_borrow_mut().expect( "we're in the single-threaded environment;\ we have no recursive borrows; qed", ) = Some(data.clone()); data }, } } fn set_data(&mut self, data: InboundLaneData) { *self.cached_data.try_borrow_mut().expect( "we're in the single-threaded environment;\ we have no recursive borrows; qed", ) = Some(data.clone()); InboundLanes::::insert(&self.lane_id, StoredInboundLaneData::(data)) } } /// Runtime outbound lane storage. struct RuntimeOutboundLaneStorage { lane_id: LaneId, _phantom: PhantomData<(T, I)>, } impl, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage { type MessageFee = T::OutboundMessageFee; fn id(&self) -> LaneId { self.lane_id } fn data(&self) -> OutboundLaneData { OutboundLanes::::get(&self.lane_id) } fn set_data(&mut self, data: OutboundLaneData) { OutboundLanes::::insert(&self.lane_id, data) } #[cfg(test)] fn message(&self, nonce: &MessageNonce) -> Option> { OutboundMessages::::get(MessageKey { lane_id: self.lane_id, nonce: *nonce }) .map(Into::into) } fn save_message( &mut self, nonce: MessageNonce, mesage_data: MessageData, ) { OutboundMessages::::insert(MessageKey { lane_id: self.lane_id, nonce }, mesage_data); } fn remove_message(&mut self, nonce: &MessageNonce) { OutboundMessages::::remove(MessageKey { lane_id: self.lane_id, nonce: *nonce }); } } /// Verify messages proof and return proved messages with decoded payload. fn verify_and_decode_messages_proof, Fee, DispatchPayload: Decode>( proof: Chain::MessagesProof, messages_count: u32, ) -> Result>, Chain::Error> { // `receive_messages_proof` weight formula and `MaxUnconfirmedMessagesAtInboundLane` check // guarantees that the `message_count` is sane and Vec may be allocated. // (tx with too many messages will either be rejected from the pool, or will fail earlier) Chain::verify_messages_proof(proof, messages_count).map(|messages_by_lane| { messages_by_lane .into_iter() .map(|(lane, lane_data)| { ( lane, ProvedLaneMessages { lane_state: lane_data.lane_state, messages: lane_data.messages.into_iter().map(Into::into).collect(), }, ) }) .collect() }) } #[cfg(test)] 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, }; use bp_messages::{UnrewardedRelayer, UnrewardedRelayersState}; use bp_test_utils::generate_owned_bridge_module_tests; use frame_support::{ assert_noop, assert_ok, storage::generator::{StorageMap, StorageValue}, weights::Weight, }; use frame_system::{EventRecord, Pallet as System, Phase}; use sp_runtime::DispatchError; fn get_ready_for_events() { System::::set_block_number(1); System::::reset_events(); } fn inbound_unrewarded_relayers_state( lane: bp_messages::LaneId, ) -> bp_messages::UnrewardedRelayersState { let inbound_lane_data = InboundLanes::::get(&lane).0; let last_delivered_nonce = inbound_lane_data.last_delivered_nonce(); let relayers = inbound_lane_data.relayers; bp_messages::UnrewardedRelayersState { unrewarded_relayer_entries: relayers.len() as _, messages_in_oldest_entry: relayers .front() .map(|entry| 1 + entry.messages.end - entry.messages.begin) .unwrap_or(0), total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX), last_delivered_nonce, } } fn send_regular_message() -> Weight { get_ready_for_events(); let message_nonce = outbound_lane::(TEST_LANE_ID).data().latest_generated_nonce + 1; let weight = Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, REGULAR_PAYLOAD.declared_weight, ) .expect("send_message has failed") .actual_weight .expect("send_message always returns Some"); // check event with assigned nonce assert_eq!( System::::events(), vec![EventRecord { phase: Phase::Initialization, event: TestEvent::Messages(Event::MessageAccepted { lane_id: TEST_LANE_ID, nonce: message_nonce }), topics: vec![], }], ); // check that fee has been withdrawn from submitter assert!(TestMessageDeliveryAndDispatchPayment::is_fee_paid( 1, REGULAR_PAYLOAD.declared_weight )); weight } fn receive_messages_delivery_proof() { System::::set_block_number(1); System::::reset_events(); assert_ok!(Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 1, relayers: vec![UnrewardedRelayer { relayer: 0, messages: DeliveredMessages::new(1, true), }] .into_iter() .collect(), }, ))), UnrewardedRelayersState { unrewarded_relayer_entries: 1, total_messages: 1, last_delivered_nonce: 1, ..Default::default() }, )); assert_eq!( System::::events(), vec![EventRecord { phase: Phase::Initialization, event: TestEvent::Messages(Event::MessagesDelivered { lane_id: TEST_LANE_ID, messages: DeliveredMessages::new(1, true), }), topics: vec![], }], ); } #[test] fn pallet_parameter_may_be_updated_by_root() { run_test(|| { get_ready_for_events(); let parameter = TestMessagesParameter::TokenConversionRate(10.into()); assert_ok!(Pallet::::update_pallet_parameter( Origin::root(), parameter.clone(), )); assert_eq!(TokenConversionRate::get(), 10.into()); assert_eq!( System::::events(), vec![EventRecord { phase: Phase::Initialization, event: TestEvent::Messages(Event::ParameterUpdated { parameter }), topics: vec![], }], ); }); } #[test] fn pallet_parameter_may_be_updated_by_owner() { run_test(|| { PalletOwner::::put(2); get_ready_for_events(); let parameter = TestMessagesParameter::TokenConversionRate(10.into()); assert_ok!(Pallet::::update_pallet_parameter( Origin::signed(2), parameter.clone(), )); assert_eq!(TokenConversionRate::get(), 10.into()); assert_eq!( System::::events(), vec![EventRecord { phase: Phase::Initialization, event: TestEvent::Messages(Event::ParameterUpdated { parameter }), topics: vec![], }], ); }); } #[test] fn pallet_parameter_cant_be_updated_by_arbitrary_submitter() { run_test(|| { assert_noop!( Pallet::::update_pallet_parameter( Origin::signed(2), TestMessagesParameter::TokenConversionRate(10.into()), ), DispatchError::BadOrigin, ); PalletOwner::::put(2); assert_noop!( Pallet::::update_pallet_parameter( Origin::signed(1), TestMessagesParameter::TokenConversionRate(10.into()), ), DispatchError::BadOrigin, ); }); } #[test] fn fixed_u128_works_as_i_think() { // this test is here just to be sure that conversion rate may be represented with FixedU128 run_test(|| { use sp_runtime::{FixedPointNumber, FixedU128}; // 1:1 conversion that we use by default for testnets let rialto_token = 1u64; let rialto_token_in_millau_tokens = TokenConversionRate::get().saturating_mul_int(rialto_token); assert_eq!(rialto_token_in_millau_tokens, 1); // let's say conversion rate is 1:1.7 let conversion_rate = FixedU128::saturating_from_rational(170, 100); let rialto_tokens = 100u64; let rialto_tokens_in_millau_tokens = conversion_rate.saturating_mul_int(rialto_tokens); assert_eq!(rialto_tokens_in_millau_tokens, 170); // let's say conversion rate is 1:0.25 let conversion_rate = FixedU128::saturating_from_rational(25, 100); let rialto_tokens = 100u64; let rialto_tokens_in_millau_tokens = conversion_rate.saturating_mul_int(rialto_tokens); assert_eq!(rialto_tokens_in_millau_tokens, 25); }); } #[test] fn pallet_rejects_transactions_if_halted() { run_test(|| { // send message first to be able to check that delivery_proof fails later send_regular_message(); PalletOperatingMode::::put(MessagesOperatingMode::Basic( BasicOperatingMode::Halted, )); assert_noop!( Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, REGULAR_PAYLOAD.declared_weight, ), Error::::NotOperatingNormally, ); assert_noop!( Pallet::::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 1, 1,), Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), ); assert_noop!( Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, Ok(vec![message(2, REGULAR_PAYLOAD)]).into(), 1, REGULAR_PAYLOAD.declared_weight, ), Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), ); assert_noop!( Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 1, relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] .into_iter() .collect(), }, ))), UnrewardedRelayersState { unrewarded_relayer_entries: 1, messages_in_oldest_entry: 1, total_messages: 1, last_delivered_nonce: 1, }, ), Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), ); }); } #[test] fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() { run_test(|| { // send message first to be able to check that delivery_proof fails later send_regular_message(); PalletOperatingMode::::put( MessagesOperatingMode::RejectingOutboundMessages, ); assert_noop!( Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, REGULAR_PAYLOAD.declared_weight, ), Error::::NotOperatingNormally, ); assert_ok!(Pallet::::increase_message_fee( Origin::signed(1), TEST_LANE_ID, 1, 1, )); assert_ok!(Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), 1, REGULAR_PAYLOAD.declared_weight, ),); assert_ok!(Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 1, relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] .into_iter() .collect(), }, ))), UnrewardedRelayersState { unrewarded_relayer_entries: 1, messages_in_oldest_entry: 1, total_messages: 1, last_delivered_nonce: 1, }, )); }); } #[test] fn send_message_works() { run_test(|| { send_regular_message(); }); } #[test] fn send_message_rejects_too_large_message() { run_test(|| { let mut message_payload = message_payload(1, 0); // the payload isn't simply extra, so it'll definitely overflow // `MAX_OUTBOUND_PAYLOAD_SIZE` if we add `MAX_OUTBOUND_PAYLOAD_SIZE` bytes to extra message_payload .extra .extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]); assert_noop!( Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, message_payload, 0, ), Error::::MessageIsTooLarge, ); }) } #[test] fn chain_verifier_rejects_invalid_message_in_send_message() { run_test(|| { // messages with this payload are rejected by target chain verifier assert_noop!( Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, PAYLOAD_REJECTED_BY_TARGET_CHAIN, PAYLOAD_REJECTED_BY_TARGET_CHAIN.declared_weight ), Error::::MessageRejectedByChainVerifier, ); }); } #[test] fn lane_verifier_rejects_invalid_message_in_send_message() { run_test(|| { // messages with zero fee are rejected by lane verifier assert_noop!( Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, 0 ), Error::::MessageRejectedByLaneVerifier, ); }); } #[test] fn message_send_fails_if_submitter_cant_pay_message_fee() { run_test(|| { TestMessageDeliveryAndDispatchPayment::reject_payments(); assert_noop!( Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, REGULAR_PAYLOAD.declared_weight ), Error::::FailedToWithdrawMessageFee, ); }); } #[test] fn receive_messages_proof_works() { run_test(|| { assert_ok!(Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), 1, REGULAR_PAYLOAD.declared_weight, )); assert_eq!(InboundLanes::::get(TEST_LANE_ID).0.last_delivered_nonce(), 1); }); } #[test] fn receive_messages_proof_updates_confirmed_message_nonce() { run_test(|| { // say we have received 10 messages && last confirmed message is 8 InboundLanes::::insert( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 8, relayers: vec![ unrewarded_relayer(9, 9, TEST_RELAYER_A), unrewarded_relayer(10, 10, TEST_RELAYER_B), ] .into_iter() .collect(), }, ); assert_eq!( inbound_unrewarded_relayers_state(TEST_LANE_ID), UnrewardedRelayersState { unrewarded_relayer_entries: 2, messages_in_oldest_entry: 1, total_messages: 2, last_delivered_nonce: 10, }, ); // message proof includes outbound lane state with latest confirmed message updated to 9 let mut message_proof: TestMessagesProof = Ok(vec![message(11, REGULAR_PAYLOAD)]).into(); message_proof.result.as_mut().unwrap()[0].1.lane_state = Some(OutboundLaneData { latest_received_nonce: 9, ..Default::default() }); assert_ok!(Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, message_proof, 1, REGULAR_PAYLOAD.declared_weight, )); assert_eq!( InboundLanes::::get(TEST_LANE_ID).0, InboundLaneData { last_confirmed_nonce: 9, relayers: vec![ unrewarded_relayer(10, 10, TEST_RELAYER_B), unrewarded_relayer(11, 11, TEST_RELAYER_A) ] .into_iter() .collect(), }, ); assert_eq!( inbound_unrewarded_relayers_state(TEST_LANE_ID), UnrewardedRelayersState { unrewarded_relayer_entries: 2, messages_in_oldest_entry: 1, total_messages: 2, last_delivered_nonce: 11, }, ); }); } #[test] fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() { run_test(|| { assert_ok!(Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), 1, REGULAR_PAYLOAD.declared_weight - 1, )); assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 0); }); } #[test] fn receive_messages_proof_rejects_invalid_proof() { run_test(|| { assert_noop!( Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, Err(()).into(), 1, 0, ), Error::::InvalidMessagesProof, ); }); } #[test] fn receive_messages_proof_rejects_proof_with_too_many_messages() { run_test(|| { assert_noop!( Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), u32::MAX, 0, ), Error::::TooManyMessagesInTheProof, ); }); } #[test] fn receive_messages_delivery_proof_works() { run_test(|| { send_regular_message(); receive_messages_delivery_proof(); assert_eq!( OutboundLanes::::get(&TEST_LANE_ID).latest_received_nonce, 1, ); }); } #[test] fn receive_messages_delivery_proof_rewards_relayers() { run_test(|| { assert_ok!(Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, 1000, )); assert_ok!(Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, 2000, )); // this reports delivery of message 1 => reward is paid to TEST_RELAYER_A assert_ok!(Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] .into_iter() .collect(), ..Default::default() } ))), UnrewardedRelayersState { unrewarded_relayer_entries: 1, total_messages: 1, last_delivered_nonce: 1, ..Default::default() }, )); assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_A, 1000)); assert!(!TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_B, 2000)); // this reports delivery of both message 1 and message 2 => reward is paid only to // TEST_RELAYER_B assert_ok!(Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { relayers: vec![ unrewarded_relayer(1, 1, TEST_RELAYER_A), unrewarded_relayer(2, 2, TEST_RELAYER_B) ] .into_iter() .collect(), ..Default::default() } ))), UnrewardedRelayersState { unrewarded_relayer_entries: 2, total_messages: 2, last_delivered_nonce: 2, ..Default::default() }, )); assert!(!TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_A, 1000)); assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_B, 2000)); }); } #[test] fn receive_messages_delivery_proof_rejects_invalid_proof() { run_test(|| { assert_noop!( Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Err(())), Default::default(), ), Error::::InvalidMessagesDeliveryProof, ); }); } #[test] fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_invalid() { run_test(|| { // when number of relayers entries is invalid assert_noop!( Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { relayers: vec![ unrewarded_relayer(1, 1, TEST_RELAYER_A), unrewarded_relayer(2, 2, TEST_RELAYER_B) ] .into_iter() .collect(), ..Default::default() } ))), UnrewardedRelayersState { unrewarded_relayer_entries: 1, total_messages: 2, last_delivered_nonce: 2, ..Default::default() }, ), Error::::InvalidUnrewardedRelayersState, ); // when number of messages is invalid assert_noop!( Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { relayers: vec![ unrewarded_relayer(1, 1, TEST_RELAYER_A), unrewarded_relayer(2, 2, TEST_RELAYER_B) ] .into_iter() .collect(), ..Default::default() } ))), UnrewardedRelayersState { unrewarded_relayer_entries: 2, total_messages: 1, last_delivered_nonce: 2, ..Default::default() }, ), Error::::InvalidUnrewardedRelayersState, ); // when last delivered nonce is invalid assert_noop!( Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { relayers: vec![ unrewarded_relayer(1, 1, TEST_RELAYER_A), unrewarded_relayer(2, 2, TEST_RELAYER_B) ] .into_iter() .collect(), ..Default::default() } ))), UnrewardedRelayersState { unrewarded_relayer_entries: 2, total_messages: 2, last_delivered_nonce: 8, ..Default::default() }, ), Error::::InvalidUnrewardedRelayersState, ); }); } #[test] fn receive_messages_accepts_single_message_with_invalid_payload() { run_test(|| { let mut invalid_message = message(1, REGULAR_PAYLOAD); invalid_message.data.payload = Vec::new(); assert_ok!(Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, Ok(vec![invalid_message]).into(), 1, 0, // weight may be zero in this case (all messages are improperly encoded) ),); assert_eq!(InboundLanes::::get(&TEST_LANE_ID).last_delivered_nonce(), 1,); }); } #[test] fn receive_messages_accepts_batch_with_message_with_invalid_payload() { run_test(|| { let mut invalid_message = message(2, REGULAR_PAYLOAD); invalid_message.data.payload = Vec::new(); assert_ok!(Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, Ok( vec![message(1, REGULAR_PAYLOAD), invalid_message, message(3, REGULAR_PAYLOAD),] ) .into(), 3, REGULAR_PAYLOAD.declared_weight + REGULAR_PAYLOAD.declared_weight, ),); assert_eq!(InboundLanes::::get(&TEST_LANE_ID).last_delivered_nonce(), 3,); }); } #[test] fn actual_dispatch_weight_does_not_overlow() { run_test(|| { let message1 = message(1, message_payload(0, Weight::MAX / 2)); let message2 = message(2, message_payload(0, Weight::MAX / 2)); let message3 = message(3, message_payload(0, Weight::MAX / 2)); assert_ok!(Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, // this may cause overflow if source chain storage is invalid Ok(vec![message1, message2, message3]).into(), 3, Weight::MAX, )); assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 2); }); } #[test] fn increase_message_fee_fails_if_message_is_already_delivered() { run_test(|| { send_regular_message(); receive_messages_delivery_proof(); assert_noop!( Pallet::::increase_message_fee( Origin::signed(1), TEST_LANE_ID, 1, 100, ), Error::::MessageIsAlreadyDelivered, ); }); } #[test] fn increase_message_fee_fails_if_message_is_not_yet_sent() { run_test(|| { assert_noop!( Pallet::::increase_message_fee( Origin::signed(1), TEST_LANE_ID, 1, 100, ), Error::::MessageIsNotYetSent, ); }); } #[test] fn increase_message_fee_fails_if_submitter_cant_pay_additional_fee() { run_test(|| { send_regular_message(); TestMessageDeliveryAndDispatchPayment::reject_payments(); assert_noop!( Pallet::::increase_message_fee( Origin::signed(1), TEST_LANE_ID, 1, 100, ), Error::::FailedToWithdrawMessageFee, ); }); } #[test] fn increase_message_fee_succeeds() { run_test(|| { send_regular_message(); assert_ok!(Pallet::::increase_message_fee( Origin::signed(1), TEST_LANE_ID, 1, 100, ),); assert!(TestMessageDeliveryAndDispatchPayment::is_fee_paid(1, 100)); }); } #[test] fn weight_refund_from_receive_messages_proof_works() { run_test(|| { fn submit_with_unspent_weight( nonce: MessageNonce, unspent_weight: Weight, is_prepaid: bool, ) -> (Weight, Weight) { let mut payload = REGULAR_PAYLOAD; payload.dispatch_result.unspent_weight = unspent_weight; payload.dispatch_result.dispatch_fee_paid_during_dispatch = !is_prepaid; let proof = Ok(vec![message(nonce, payload)]).into(); let messages_count = 1; let pre_dispatch_weight = ::WeightInfo::receive_messages_proof_weight( &proof, messages_count, REGULAR_PAYLOAD.declared_weight, ); let post_dispatch_weight = Pallet::::receive_messages_proof( Origin::signed(1), TEST_RELAYER_A, proof, messages_count, REGULAR_PAYLOAD.declared_weight, ) .expect("delivery has failed") .actual_weight .expect("receive_messages_proof always returns Some"); (pre_dispatch_weight, post_dispatch_weight) } // when dispatch is returning `unspent_weight < declared_weight` let (pre, post) = submit_with_unspent_weight(1, 1, false); assert_eq!(post, pre - 1); // when dispatch is returning `unspent_weight = declared_weight` let (pre, post) = submit_with_unspent_weight(2, REGULAR_PAYLOAD.declared_weight, false); assert_eq!(post, pre - REGULAR_PAYLOAD.declared_weight); // when dispatch is returning `unspent_weight > declared_weight` let (pre, post) = submit_with_unspent_weight(3, REGULAR_PAYLOAD.declared_weight + 1, false); assert_eq!(post, pre - REGULAR_PAYLOAD.declared_weight); // when there's no unspent weight let (pre, post) = submit_with_unspent_weight(4, 0, false); assert_eq!(post, pre); // when dispatch is returning `unspent_weight < declared_weight` AND message is prepaid let (pre, post) = submit_with_unspent_weight(5, 1, true); assert_eq!( post, pre - 1 - ::WeightInfo::pay_inbound_dispatch_fee_overhead() ); }); } #[test] fn messages_delivered_callbacks_are_called() { run_test(|| { send_regular_message(); send_regular_message(); send_regular_message(); // messages 1+2 are confirmed in 1 tx, message 3 in a separate tx // dispatch of message 2 has failed let mut delivered_messages_1_and_2 = DeliveredMessages::new(1, true); delivered_messages_1_and_2.note_dispatched_message(false); let messages_1_and_2_proof = Ok(( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 0, relayers: vec![UnrewardedRelayer { relayer: 0, messages: delivered_messages_1_and_2.clone(), }] .into_iter() .collect(), }, )); let delivered_message_3 = DeliveredMessages::new(3, true); let messages_3_proof = Ok(( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 0, relayers: vec![UnrewardedRelayer { relayer: 0, messages: delivered_message_3.clone(), }] .into_iter() .collect(), }, )); // first tx with messages 1+2 assert_ok!(Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(messages_1_and_2_proof), UnrewardedRelayersState { unrewarded_relayer_entries: 1, total_messages: 2, last_delivered_nonce: 2, ..Default::default() }, )); // second tx with message 3 assert_ok!(Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(messages_3_proof), UnrewardedRelayersState { unrewarded_relayer_entries: 1, total_messages: 1, last_delivered_nonce: 3, ..Default::default() }, )); // ensure that both callbacks have been called twice: for 1+2, then for 3 TestOnDeliveryConfirmed1::ensure_called(&TEST_LANE_ID, &delivered_messages_1_and_2); TestOnDeliveryConfirmed1::ensure_called(&TEST_LANE_ID, &delivered_message_3); TestOnDeliveryConfirmed2::ensure_called(&TEST_LANE_ID, &delivered_messages_1_and_2); TestOnDeliveryConfirmed2::ensure_called(&TEST_LANE_ID, &delivered_message_3); }); } fn confirm_3_messages_delivery() -> (Weight, Weight) { send_regular_message(); send_regular_message(); send_regular_message(); let proof = TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 0, relayers: vec![unrewarded_relayer(1, 3, TEST_RELAYER_A)].into_iter().collect(), }, ))); let relayers_state = UnrewardedRelayersState { unrewarded_relayer_entries: 1, total_messages: 3, last_delivered_nonce: 3, ..Default::default() }; let pre_dispatch_weight = ::WeightInfo::receive_messages_delivery_proof_weight( &proof, &relayers_state, crate::mock::DbWeight::get(), ); let post_dispatch_weight = Pallet::::receive_messages_delivery_proof( Origin::signed(1), proof, relayers_state, ) .expect("confirmation has failed") .actual_weight .expect("receive_messages_delivery_proof always returns Some"); (pre_dispatch_weight, post_dispatch_weight) } #[test] fn receive_messages_delivery_proof_refunds_zero_weight() { run_test(|| { let (pre_dispatch_weight, post_dispatch_weight) = confirm_3_messages_delivery(); assert_eq!(pre_dispatch_weight, post_dispatch_weight); }); } #[test] fn receive_messages_delivery_proof_refunds_non_zero_weight() { run_test(|| { TestOnDeliveryConfirmed1::set_consumed_weight_per_message( crate::mock::DbWeight::get().writes(1), ); let (pre_dispatch_weight, post_dispatch_weight) = confirm_3_messages_delivery(); assert_eq!( pre_dispatch_weight.saturating_sub(post_dispatch_weight), crate::mock::DbWeight::get().reads(1) * 3 ); }); } #[test] #[should_panic] #[cfg(debug_assertions)] fn receive_messages_panics_in_debug_mode_if_callback_is_wrong() { run_test(|| { TestOnDeliveryConfirmed1::set_consumed_weight_per_message( crate::mock::DbWeight::get().reads_writes(2, 2), ); confirm_3_messages_delivery() }); } #[test] fn receive_messages_delivery_proof_rejects_proof_if_trying_to_confirm_more_messages_than_expected( ) { run_test(|| { // send message first to be able to check that delivery_proof fails later send_regular_message(); // 1) InboundLaneData declares that the `last_confirmed_nonce` is 1; // 2) InboundLaneData has no entries => `InboundLaneData::last_delivered_nonce()` // returns `last_confirmed_nonce`; // 3) it means that we're going to confirm delivery of messages 1..=1; // 4) so the number of declared messages (see `UnrewardedRelayersState`) is `0` and // numer of actually confirmed messages is `1`. assert_noop!( Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 1, relayers: Default::default() }, ))), UnrewardedRelayersState { last_delivered_nonce: 1, ..Default::default() }, ), Error::::TryingToConfirmMoreMessagesThanExpected, ); }); } #[test] fn increase_message_fee_weight_depends_on_message_size() { run_test(|| { let mut small_payload = message_payload(0, 100); let mut large_payload = message_payload(1, 100); small_payload.extra = vec![1; MAX_OUTBOUND_PAYLOAD_SIZE as usize / 10]; large_payload.extra = vec![2; MAX_OUTBOUND_PAYLOAD_SIZE as usize / 5]; assert_ok!(Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, small_payload, 100, )); assert_ok!(Pallet::::send_message( Origin::signed(1), TEST_LANE_ID, large_payload, 100, )); let small_weight = Pallet::::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 1, 1) .expect("increase_message_fee has failed") .actual_weight .expect("increase_message_fee always returns Some"); let large_weight = Pallet::::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 2, 1) .expect("increase_message_fee has failed") .actual_weight .expect("increase_message_fee always returns Some"); assert!( large_weight > small_weight, "Actual post-dispatch weigth for larger message {} must be larger than {} for small message", large_weight, small_weight, ); }); } #[test] fn weight_is_refunded_for_messages_that_are_not_pruned() { run_test(|| { // send first MAX messages - no messages are pruned let max_messages_to_prune = crate::mock::MaxMessagesToPruneAtOnce::get(); let when_zero_messages_are_pruned = send_regular_message(); let mut delivered_messages = DeliveredMessages::new(1, true); for _ in 1..max_messages_to_prune { assert_eq!(send_regular_message(), when_zero_messages_are_pruned); delivered_messages.note_dispatched_message(true); } // confirm delivery of all sent messages assert_ok!(Pallet::::receive_messages_delivery_proof( Origin::signed(1), TestMessagesDeliveryProof(Ok(( TEST_LANE_ID, InboundLaneData { last_confirmed_nonce: 1, relayers: vec![UnrewardedRelayer { relayer: 0, messages: delivered_messages, }] .into_iter() .collect(), }, ))), UnrewardedRelayersState { unrewarded_relayer_entries: 1, total_messages: max_messages_to_prune, last_delivered_nonce: max_messages_to_prune, ..Default::default() }, )); // when next message is sent, MAX messages are pruned let weight_when_max_messages_are_pruned = send_regular_message(); assert_eq!( weight_when_max_messages_are_pruned, when_zero_messages_are_pruned + crate::mock::DbWeight::get().writes(max_messages_to_prune), ); }); } #[test] fn message_accepted_callbacks_are_called() { run_test(|| { send_regular_message(); TestOnMessageAccepted::ensure_called(&TEST_LANE_ID, &1); }); } #[test] #[should_panic] #[cfg(debug_assertions)] fn message_accepted_panics_in_debug_mode_if_callback_is_wrong() { run_test(|| { TestOnMessageAccepted::set_consumed_weight_per_message( crate::mock::DbWeight::get().reads_writes(2, 2), ); send_regular_message(); }); } #[test] fn message_accepted_refunds_non_zero_weight() { run_test(|| { TestOnMessageAccepted::set_consumed_weight_per_message( crate::mock::DbWeight::get().writes(1), ); let actual_callback_weight = send_regular_message(); let pre_dispatch_weight = ::WeightInfo::send_message_weight( ®ULAR_PAYLOAD, crate::mock::DbWeight::get(), ); let prune_weight = crate::mock::DbWeight::get() .writes(::MaxMessagesToPruneAtOnce::get()); assert_eq!( pre_dispatch_weight.saturating_sub(actual_callback_weight), crate::mock::DbWeight::get().reads(1).saturating_add(prune_weight) ); }); } #[test] fn storage_keys_computed_properly() { assert_eq!( PalletOperatingMode::::storage_value_final_key().to_vec(), bp_messages::storage_keys::operating_mode_key("Messages").0, ); assert_eq!( OutboundMessages::::storage_map_final_key(MessageKey { lane_id: TEST_LANE_ID, nonce: 42 }), bp_messages::storage_keys::message_key("Messages", &TEST_LANE_ID, 42).0, ); assert_eq!( OutboundLanes::::storage_map_final_key(TEST_LANE_ID), bp_messages::storage_keys::outbound_lane_data_key("Messages", &TEST_LANE_ID).0, ); assert_eq!( InboundLanes::::storage_map_final_key(TEST_LANE_ID), bp_messages::storage_keys::inbound_lane_data_key("Messages", &TEST_LANE_ID).0, ); } #[test] fn inbound_message_details_works() { run_test(|| { assert_eq!( Pallet::::inbound_message_data( TEST_LANE_ID, REGULAR_PAYLOAD.encode(), OutboundMessageDetails { nonce: 0, dispatch_weight: 0, size: 0, delivery_and_dispatch_fee: 0, dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtTargetChain, }, ), InboundMessageDetails { dispatch_weight: REGULAR_PAYLOAD.declared_weight }, ); }); } generate_owned_bridge_module_tests!( MessagesOperatingMode::Basic(BasicOperatingMode::Normal), MessagesOperatingMode::Basic(BasicOperatingMode::Halted) ); }