Skip to content
lib.rs 73.5 KiB
Newer Older
// 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 <http://www.gnu.org/licenses/>.

//! 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 crate::weights_ext::{
hacpy's avatar
hacpy committed
	ensure_able_to_receive_confirmation, ensure_able_to_receive_message,
	ensure_weights_are_correct, WeightInfoExt, EXPECTED_DEFAULT_MESSAGE_LENGTH,
hacpy's avatar
hacpy committed
use crate::{
	inbound_lane::{InboundLane, InboundLaneStorage, ReceivalResult},
	outbound_lane::{OutboundLane, OutboundLaneStorage, ReceivalConfirmationResult},
	weights::WeightInfo,
};
hacpy's avatar
hacpy committed
		LaneMessageVerifier, MessageDeliveryAndDispatchPayment, OnDeliveryConfirmed,
		OnMessageAccepted, SendMessageArtifacts, TargetHeaderChain,
hacpy's avatar
hacpy committed
	},
	target_chain::{
		DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain,
hacpy's avatar
hacpy committed
	total_unrewarded_messages, DeliveredMessages, InboundLaneData, LaneId, MessageData, MessageKey,
	MessageNonce, OperatingMode, OutboundLaneData, Parameter as MessagesParameter,
	UnrewardedRelayersState,
use codec::{Decode, Encode};
use frame_support::{
	fail,
	traits::Get,
	weights::{Pays, PostDispatchInfo},
};
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::*};

mod inbound_lane;
mod outbound_lane;
mod weights_ext;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;

#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use frame_support::pallet_prelude::*;
	use frame_system::pallet_prelude::*;
	#[pallet::config]
	pub trait Config<I: 'static = ()>: frame_system::Config {
		// General types
		/// The overarching event type.
		type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::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<ChainId>;
		/// 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<MessageNonce>;
		/// Maximal number of unrewarded relayer entries at inbound lane. Unrewarded means that the
hacpy's avatar
hacpy committed
		/// 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<MessageNonce>;
		/// 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.
		///
hacpy's avatar
hacpy committed
		/// 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.
hacpy's avatar
hacpy committed
		/// 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<MessageNonce>;

		/// 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<u64>
			+ PartialOrd
			+ Parameter
			+ SaturatingAdd
			+ Zero
			+ Copy;

		/// 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;
hacpy's avatar
hacpy committed
		/// Identifier of relayer that deliver messages to this chain. Relayer reward is paid on the
		/// bridged chain.
		type InboundRelayer: Parameter;

		/// A type which can be turned into an AccountId from a 256-bit hash.
		///
		/// Used when deriving the shared relayer fund account.
		type AccountIdConverter: sp_runtime::traits::Convert<sp_core::hash::H256, Self::AccountId>;

		// Types that are used by outbound_lane (on source chain).

		/// Target header chain.
		type TargetHeaderChain: TargetHeaderChain<Self::OutboundPayload, Self::AccountId>;
		/// Message payload verifier.
hacpy's avatar
hacpy committed
		type LaneMessageVerifier: LaneMessageVerifier<
			Self::AccountId,
			Self::OutboundPayload,
			Self::OutboundMessageFee,
		>;
		/// Message delivery payment.
		type MessageDeliveryAndDispatchPayment: MessageDeliveryAndDispatchPayment<
			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<Self::InboundMessageFee>;
		/// Message dispatch.
		type MessageDispatch: MessageDispatch<
			Self::AccountId,
			Self::InboundMessageFee,
			DispatchPayload = Self::InboundPayload,
		>;
	}

	/// Shortcut to messages proof type for Config.
hacpy's avatar
hacpy committed
	type MessagesProofOf<T, I> = <<T as Config<I>>::SourceHeaderChain as SourceHeaderChain<
		<T as Config<I>>::InboundMessageFee,
	>>::MessagesProof;
	/// Shortcut to messages delivery proof type for Config.
hacpy's avatar
hacpy committed
	type MessagesDeliveryProofOf<T, I> =
		<<T as Config<I>>::TargetHeaderChain as TargetHeaderChain<
			<T as Config<I>>::OutboundPayload,
			<T as frame_system::Config>::AccountId,
		>>::MessagesDeliveryProof;

	#[pallet::pallet]
	#[pallet::generate_store(pub(super) trait Store)]
	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);

	#[pallet::call]
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
Hernando Castano's avatar
Hernando Castano committed
		/// Change `PalletOwner`.
Hernando Castano's avatar
Hernando Castano committed
		/// 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<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
			ensure_owner_or_root::<T, I>(origin)?;
			match new_owner {
				Some(new_owner) => {
Hernando Castano's avatar
Hernando Castano committed
					PalletOwner::<T, I>::put(&new_owner);
					log::info!(target: "runtime::bridge-messages", "Setting pallet Owner to: {:?}", new_owner);
hacpy's avatar
hacpy committed
				},
Hernando Castano's avatar
Hernando Castano committed
					PalletOwner::<T, I>::kill();
					log::info!(target: "runtime::bridge-messages", "Removed Owner of pallet.");
hacpy's avatar
hacpy committed
				},
		/// Halt or resume all/some pallet operations.
Hernando Castano's avatar
Hernando Castano committed
		/// May only be called either by root, or by `PalletOwner`.
		#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
hacpy's avatar
hacpy committed
		pub fn set_operating_mode(
			origin: OriginFor<T>,
			operating_mode: OperatingMode,
		) -> DispatchResult {
			ensure_owner_or_root::<T, I>(origin)?;
			PalletOperatingMode::<T, I>::put(operating_mode);
			log::info!(
				target: "runtime::bridge-messages",
				"Setting messages pallet operating mode to {:?}.",
				operating_mode,
			);
Hernando Castano's avatar
Hernando Castano committed
		/// May only be called either by root, or by `PalletOwner`.
hacpy's avatar
hacpy committed
		/// 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))]
hacpy's avatar
hacpy committed
		pub fn update_pallet_parameter(
			origin: OriginFor<T>,
			parameter: T::Parameter,
		) -> DispatchResult {
			ensure_owner_or_root::<T, I>(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(
			payload: T::OutboundPayload,
			delivery_and_dispatch_fee: T::OutboundMessageFee,
		) -> DispatchResultWithPostInfo {
			crate::send_message::<T, I>(
				origin.into().map_err(|_| BadOrigin)?,
				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(
			lane_id: LaneId,
			nonce: MessageNonce,
			additional_fee: T::OutboundMessageFee,
		) -> DispatchResultWithPostInfo {
			ensure_not_halted::<T, I>()?;
			// 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::<T, I>(lane_id);
			ensure!(
				nonce > lane.data().latest_received_nonce,
				Error::<T, I>::MessageIsAlreadyDelivered
			);
			ensure!(
				nonce <= lane.data().latest_generated_nonce,
				Error::<T, I>::MessageIsNotYetSent
			);

			// withdraw additional fee from submitter
			let submitter = origin.into().map_err(|_| BadOrigin)?;
			T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee(
				&submitter,
				&additional_fee,
				&relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
Hernando Castano's avatar
Hernando Castano committed
				log::trace!(
					target: "runtime::bridge-messages",
					"Submitter {:?} can't pay additional fee {:?} for the message {:?}/{:?} to {:?}: {:?}",
					submitter,
					additional_fee,
					lane_id,
					nonce,
					relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
					err,
				);

				Error::<T, I>::FailedToWithdrawMessageFee
			})?;

			// and finally update fee in the storage
			let message_key = MessageKey { lane_id, nonce };
			let message_size = OutboundMessages::<T, I>::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
hacpy's avatar
hacpy committed
				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);
			// 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 _),
			);

hacpy's avatar
hacpy committed
			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(
			relayer_id_at_bridged_chain: T::InboundRelayer,
			proof: MessagesProofOf<T, I>,
			messages_count: u32,
		) -> DispatchResultWithPostInfo {
			ensure_not_halted::<T, I>()?;
			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::<T, I>::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
hacpy's avatar
hacpy committed
			// 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).
hacpy's avatar
hacpy committed
			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,
			.map_err(|err| {
				log::trace!(
					target: "runtime::bridge-messages",
					"Rejecting invalid messages proof: {:?}",
					err,
				);
				Error::<T, I>::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::<T, I>(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 {
Hernando Castano's avatar
Hernando Castano committed
						log::trace!(
							target: "runtime::bridge-messages",
							"Received lane {:?} state update: latest_confirmed_nonce={}",
							lane_id,
							updated_latest_confirmed_nonce,
						);
					}
				}

				for message in lane_data.messages {
					debug_assert_eq!(message.key.lane_id, lane_id);

hacpy's avatar
hacpy committed
					// 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);
					if dispatch_weight > dispatch_weight_left {
						log::trace!(
							target: "runtime::bridge-messages",
							"Cannot dispatch any more messages on lane {:?}. Weight: declared={}, left={}",
							lane_id,
							dispatch_weight,
							dispatch_weight_left,
						);
hacpy's avatar
hacpy committed
						break
					let receival_result = lane.receive_message::<T::MessageDispatch, T::AccountId>(
						&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,
							)
hacpy's avatar
hacpy committed
						},
						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
						},
					);
Hernando Castano's avatar
Hernando Castano committed
			log::trace!(
				target: "runtime::bridge-messages",
				"Received messages: total={}, valid={}. Weight used: {}/{}",
				total_messages,
				valid_messages,
				actual_weight,
				declared_weight,
hacpy's avatar
hacpy committed
			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(
		pub fn receive_messages_delivery_proof(
			proof: MessagesDeliveryProofOf<T, I>,
			relayers_state: UnrewardedRelayersState,
			ensure_not_halted::<T, I>()?;
hacpy's avatar
hacpy committed
			// 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).
hacpy's avatar
hacpy committed
			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 confirmation_relayer = ensure_signed(origin)?;
hacpy's avatar
hacpy committed
			let (lane_id, lane_data) = T::TargetHeaderChain::verify_messages_delivery_proof(proof)
				.map_err(|err| {
					log::trace!(
						target: "runtime::bridge-messages",
						"Rejecting invalid messages delivery proof: {:?}",
						err,
					);
hacpy's avatar
hacpy committed
					Error::<T, I>::InvalidMessagesDeliveryProof
				})?;
			// verify that the relayer has declared correct `lane_data::relayers` state
hacpy's avatar
hacpy committed
			// (we only care about total number of entries and messages, because this affects call
			// weight)
hacpy's avatar
hacpy committed
				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::<T, I>::InvalidUnrewardedRelayersState
			);

			// mark messages as delivered
			let mut lane = outbound_lane::<T, I>(lane_id);
			let last_delivered_nonce = lane_data.last_delivered_nonce();
hacpy's avatar
hacpy committed
			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: "runtime::bridge-messages",
						"Messages delivery proof contains too many messages to confirm: {} vs declared {}",
						to_confirm_messages_count,
						relayers_state.total_messages,
					);
hacpy's avatar
hacpy committed
					fail!(Error::<T, I>::TryingToConfirmMoreMessagesThanExpected);
				},
				error => {
					log::trace!(
						target: "runtime::bridge-messages",
						"Messages delivery proof contains invalid unrewarded relayers vec: {:?}",
						error,
					);
hacpy's avatar
hacpy committed
					fail!(Error::<T, I>::InvalidUnrewardedRelayers);
				},
			};
			if let Some(confirmed_messages) = confirmed_messages {
				// handle messages delivery confirmation
hacpy's avatar
hacpy committed
				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: "runtime::bridge-messages",
							"T::OnDeliveryConfirmed callback has spent less weight than expected. Refunding: \
							{} - {} = {}",
							preliminary_callback_overhead,
							actual_callback_weight,
							difference,
						);
						actual_weight = actual_weight.saturating_sub(difference);
hacpy's avatar
hacpy committed
					},
hacpy's avatar
hacpy committed
						debug_assert!(
							false,
							"T::OnDeliveryConfirmed callback consumed too much weight."
						);
						log::error!(
							"T::OnDeliveryConfirmed callback has spent more weight that it is allowed to: \
							{} vs {}",
							preliminary_callback_overhead,
							actual_callback_weight,
						);
hacpy's avatar
hacpy committed
					},

				// emit 'delivered' event
				let received_range = confirmed_messages.begin..=confirmed_messages.end;
				Self::deposit_event(Event::MessagesDelivered(lane_id, confirmed_messages));
				// if some new messages have been confirmed, reward relayers
hacpy's avatar
hacpy committed
				let relayer_fund_account =
					relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
				<T as Config<I>>::MessageDeliveryAndDispatchPayment::pay_relayers_rewards(
Hernando Castano's avatar
Hernando Castano committed
			log::trace!(
				target: "runtime::bridge-messages",
				"Received messages delivery proof up to (and including) {} at lane {:?}",
				last_delivered_nonce,
hacpy's avatar
hacpy committed
			Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	#[pallet::metadata(T::Parameter = "Parameter")]
	pub enum Event<T: Config<I>, I: 'static = ()> {
		/// Pallet parameter has been updated.
		ParameterUpdated(T::Parameter),
		/// Message has been accepted and is waiting to be delivered.
		MessageAccepted(LaneId, MessageNonce),
		/// Messages in the inclusive range have been delivered to the bridged chain.
		MessagesDelivered(LaneId, DeliveredMessages),
	#[pallet::error]
	pub enum Error<T, I = ()> {
		/// All pallet operations are halted.
		Halted,
		/// 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,
hacpy's avatar
hacpy committed
		/// 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,
hacpy's avatar
hacpy committed
		/// 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,
	/// 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<T: Config<I>, 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)]
hacpy's avatar
hacpy committed
	pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
		StorageValue<_, OperatingMode, ValueQuery>;

	/// Map of lane id => inbound lane data.
	#[pallet::storage]
	pub type InboundLanes<T: Config<I>, I: 'static = ()> =
		StorageMap<_, Blake2_128Concat, LaneId, InboundLaneData<T::InboundRelayer>, ValueQuery>;

	/// Map of lane id => outbound lane data.
	#[pallet::storage]
	pub type OutboundLanes<T: Config<I>, I: 'static = ()> =
		StorageMap<_, Blake2_128Concat, LaneId, OutboundLaneData, ValueQuery>;

	/// All queued outbound messages.
	#[pallet::storage]
	pub type OutboundMessages<T: Config<I>, I: 'static = ()> =
		StorageMap<_, Blake2_128Concat, MessageKey, MessageData<T::OutboundMessageFee>>;

	#[pallet::genesis_config]
	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
		/// Initial pallet operating mode.
		pub operating_mode: OperatingMode,
		/// Initial pallet owner.
		pub owner: Option<T::AccountId>,
		/// Dummy marker.
		pub phantom: sp_std::marker::PhantomData<I>,
	}

	#[cfg(feature = "std")]
	impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
		fn default() -> Self {
			Self {
				operating_mode: Default::default(),
				owner: Default::default(),
				phantom: Default::default(),
			}
		}
	#[pallet::genesis_build]
	impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig<T, I> {
		fn build(&self) {
			PalletOperatingMode::<T, I>::put(&self.operating_mode);
			if let Some(ref owner) = self.owner {
				PalletOwner::<T, I>::put(owner);
			}
		}
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
		/// Get stored data of the outbound message with given nonce.
hacpy's avatar
hacpy committed
		pub fn outbound_message_data(
			lane: LaneId,
			nonce: MessageNonce,
		) -> Option<MessageData<T::OutboundMessageFee>> {
			OutboundMessages::<T, I>::get(MessageKey { lane_id: lane, nonce })
		}
		/// Get nonce of the latest generated message at given outbound lane.
		pub fn outbound_latest_generated_nonce(lane: LaneId) -> MessageNonce {
			OutboundLanes::<T, I>::get(&lane).latest_generated_nonce
		/// Get nonce of the latest confirmed message at given outbound lane.
		pub fn outbound_latest_received_nonce(lane: LaneId) -> MessageNonce {
			OutboundLanes::<T, I>::get(&lane).latest_received_nonce
		}

		/// Get nonce of the latest received message at given inbound lane.
		pub fn inbound_latest_received_nonce(lane: LaneId) -> MessageNonce {
			InboundLanes::<T, I>::get(&lane).last_delivered_nonce()
		}

		/// Get nonce of the latest confirmed message at given inbound lane.
		pub fn inbound_latest_confirmed_nonce(lane: LaneId) -> MessageNonce {
			InboundLanes::<T, I>::get(&lane).last_confirmed_nonce
		}

		/// Get state of unrewarded relayers set.
hacpy's avatar
hacpy committed
		pub fn inbound_unrewarded_relayers_state(
			lane: bp_messages::LaneId,
		) -> bp_messages::UnrewardedRelayersState {
			let relayers = InboundLanes::<T, I>::get(&lane).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),
			}
		}
/// Getting storage keys for messages and lanes states. These keys are normally used when building
/// messages and lanes states proofs.
pub mod storage_keys {
	use super::*;
	use sp_core::storage::StorageKey;

	/// Storage key of the outbound message in the runtime storage.
	pub fn message_key(pallet_prefix: &str, lane: &LaneId, nonce: MessageNonce) -> StorageKey {
		bp_runtime::storage_map_final_key_blake2_128concat(
			pallet_prefix,
			"OutboundMessages",
			&MessageKey { lane_id: *lane, nonce }.encode(),
		)
	}

	/// Storage key of the outbound message lane state in the runtime storage.
	pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey {
		bp_runtime::storage_map_final_key_blake2_128concat(pallet_prefix, "OutboundLanes", lane)
	}

	/// Storage key of the inbound message lane state in the runtime storage.
	pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey {
		bp_runtime::storage_map_final_key_blake2_128concat(pallet_prefix, "InboundLanes", lane)
/// AccountId of the shared relayer fund account.
///
/// 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.
hacpy's avatar
hacpy committed
pub fn relayer_fund_account_id<AccountId, AccountIdConverter: Convert<H256, AccountId>>(
) -> AccountId {
	let encoded_id = bp_runtime::derive_relayer_fund_account_id(bp_runtime::NO_INSTANCE_ID);
	AccountIdConverter::convert(encoded_id)
}

hacpy's avatar
hacpy committed
impl<T, I>
	bp_messages::source_chain::MessagesBridge<
		T::AccountId,
		T::OutboundMessageFee,
		T::OutboundPayload,
	> for Pallet<T, I>
where
	T: Config<I>,
	I: 'static,
{
	type Error = sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>;

	fn send_message(
		sender: bp_messages::source_chain::Sender<T::AccountId>,
		lane: LaneId,
		message: T::OutboundPayload,
		delivery_and_dispatch_fee: T::OutboundMessageFee,
	) -> Result<SendMessageArtifacts, Self::Error> {
		crate::send_message::<T, I>(sender, lane, message, delivery_and_dispatch_fee)
	}
}

/// Function that actually sends message.
fn send_message<T: Config<I>, I: 'static>(
	submitter: bp_messages::source_chain::Sender<T::AccountId>,
	lane_id: LaneId,
	payload: T::OutboundPayload,
	delivery_and_dispatch_fee: T::OutboundMessageFee,
) -> sp_std::result::Result<
	SendMessageArtifacts,
	sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>,
> {
	ensure_normal_operating_mode::<T, I>()?;

	// 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: "runtime::bridge-messages",
			"Message to lane {:?} is rejected by target chain: {:?}",
			lane_id,
			err,
		);

		Error::<T, I>::MessageRejectedByChainVerifier
	})?;

	// now let's enforce any additional lane rules
	let mut lane = outbound_lane::<T, I>(lane_id);
hacpy's avatar
hacpy committed
	T::LaneMessageVerifier::verify_message(
		&submitter,
		&delivery_and_dispatch_fee,
		&lane_id,
		&lane.data(),
		&payload,
	)
	.map_err(|err| {
		log::trace!(
			target: "runtime::bridge-messages",
			"Message to lane {:?} is rejected by lane verifier: {:?}",
			lane_id,
			err,
		);
hacpy's avatar
hacpy committed
		Error::<T, I>::MessageRejectedByLaneVerifier
	})?;

	// let's withdraw delivery and dispatch fee from submitter
	T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee(
		&submitter,
		&delivery_and_dispatch_fee,
		&relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
	)
	.map_err(|err| {
		log::trace!(
			target: "runtime::bridge-messages",
			"Message to lane {:?} is rejected because submitter {:?} is unable to pay fee {:?}: {:?}",
			lane_id,
			submitter,
			delivery_and_dispatch_fee,
			err,
		);

		Error::<T, I>::FailedToWithdrawMessageFee
	})?;

	// finally, save message in outbound storage and emit event
	let encoded_payload = payload.encode();
	let encoded_payload_len = encoded_payload.len();
hacpy's avatar
hacpy committed
	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.
hacpy's avatar
hacpy committed
	// 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: "runtime::bridge-messages",
				"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);
hacpy's avatar
hacpy committed
		},
		None => {
			debug_assert!(false, "T::OnMessageAccepted callback consumed too much weight.");
			log::error!(
				target: "runtime::bridge-messages",
				"T::OnMessageAccepted callback has spent more weight that it is allowed to: \
				{} vs {}",
				single_message_callback_overhead,
				actual_callback_weight,
			);
hacpy's avatar
hacpy committed
		},

	// 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: "runtime::bridge-messages",
		"Accepted message {} to lane {:?}. Message size: {:?}",
		nonce,
		lane_id,
		encoded_payload_len,
	);

	Pallet::<T, I>::deposit_event(Event::MessageAccepted(lane_id, nonce));

	Ok(SendMessageArtifacts { nonce, weight: actual_weight })
Hernando Castano's avatar
Hernando Castano committed
/// Ensure that the origin is either root, or `PalletOwner`.
fn ensure_owner_or_root<T: Config<I>, I: 'static>(origin: T::Origin) -> Result<(), BadOrigin> {
	match origin.into() {
		Ok(RawOrigin::Root) => Ok(()),
hacpy's avatar
hacpy committed
		Ok(RawOrigin::Signed(ref signer))
			if Some(signer) == Pallet::<T, I>::module_owner().as_ref() =>
			Ok(()),
/// Ensure that the pallet is in normal operational mode.