Skip to content
lib.rs 66 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 inbound_lane::StoredInboundLaneData;
pub use outbound_lane::StoredMessagePayload;
pub use weights::WeightInfo;
pub use 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},
hacpy's avatar
hacpy committed
	outbound_lane::{OutboundLane, OutboundLaneStorage, ReceivalConfirmationResult},
};
		DeliveryConfirmationPayments, LaneMessageVerifier, SendMessageArtifacts, TargetHeaderChain,
hacpy's avatar
hacpy committed
	},
	target_chain::{
		DeliveryPayments, DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages,
		SourceHeaderChain,
	total_unrewarded_messages, DeliveredMessages, InboundLaneData, InboundMessageDetails, LaneId,
	MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData,
use bp_runtime::{BasicOperatingMode, ChainId, OwnedBridgeModule, Size};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get};
use sp_runtime::traits::UniqueSaturatedFrom;
use sp_std::{cell::RefCell, marker::PhantomData, prelude::*};

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

/// The target that will be used when publishing logs related to this pallet.
pub const LOG_TARGET: &str = "runtime::bridge-messages";
#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use bp_messages::{ReceivalResult, ReceivedMessages};
	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 RuntimeEvent: From<Event<Self, I>>
			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
		/// 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>;

		/// Get all active outbound lanes that the message pallet is serving.
		type ActiveOutboundLanes: Get<&'static [LaneId]>;
		/// 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>;

		/// Maximal encoded size of the outbound payload.
		#[pallet::constant]
		type MaximalOutboundPayloadSize: Get<u32>;
		/// Payload type of outbound messages. This payload is dispatched on the bridged chain.
		type OutboundPayload: Parameter + Size;

		/// Payload type of inbound messages. This payload is dispatched on this chain.
		type InboundPayload: 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 + MaxEncodedLen;
		/// Delivery payments.
		type DeliveryPayments: DeliveryPayments<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.
		type LaneMessageVerifier: LaneMessageVerifier<Self::RuntimeOrigin, Self::OutboundPayload>;
		/// Delivery confirmation payments.
		type DeliveryConfirmationPayments: DeliveryConfirmationPayments<Self::AccountId>;

		// 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,
			DispatchPayload = Self::InboundPayload,
		>;
	}

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

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

	impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pallet<T, I> {
		const LOG_TARGET: &'static str = LOG_TARGET;
		type OwnerStorage = PalletOwner<T, I>;
		type OperatingMode = MessagesOperatingMode;
		type OperatingModeStorage = PalletOperatingMode<T, I>;
	}

	#[pallet::hooks]
	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I>
	where
		u32: TryFrom<<T as frame_system::Config>::BlockNumber>,
	{
		fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight {
			// we'll need at least to read outbound lane state, kill a message and update lane state
			let db_weight = T::DbWeight::get();
			if !remaining_weight.all_gte(db_weight.reads_writes(1, 2)) {
				return Weight::zero()
			}

			// messages from lane with index `i` in `ActiveOutboundLanes` are pruned when
			// `System::block_number() % lanes.len() == i`. Otherwise we need to read lane states on
			// every block, wasting the whole `remaining_weight` for nothing and causing starvation
			// of the last lane pruning
			let active_lanes = T::ActiveOutboundLanes::get();
			let active_lanes_len = (active_lanes.len() as u32).into();
			let active_lane_index = u32::unique_saturated_from(
				frame_system::Pallet::<T>::block_number() % active_lanes_len,
			);
			let active_lane_id = active_lanes[active_lane_index as usize];

			// first db read - outbound lane state
			let mut active_lane = outbound_lane::<T, I>(active_lane_id);
			let mut used_weight = db_weight.reads(1);
			// and here we'll have writes
			used_weight += active_lane.prune_messages(db_weight, remaining_weight - used_weight);

			// we already checked we have enough `remaining_weight` to cover this `used_weight`
			used_weight
		}
	}

	#[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::call_index(0)]
		#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
		pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
			<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
		/// 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::call_index(1)]
		#[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: MessagesOperatingMode,
hacpy's avatar
hacpy committed
		) -> DispatchResult {
			<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
		/// 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::call_index(2)]
		#[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 {
			Self::ensure_not_halted().map_err(Error::<T, I>::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::<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::InboundPayload,
				log::trace!(target: LOG_TARGET, "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 messages_received_status = Vec::with_capacity(messages.len());
			let mut dispatch_weight_left = dispatch_weight;
			for (lane_id, lane_data) in messages {
				let mut lane = inbound_lane::<T, I>(lane_id);

				// subtract extra storage proof bytes from the actual PoV size - there may be
				// less unrewarded relayers than the maximal configured value
				let lane_extra_proof_size_bytes = lane.storage().extra_proof_size_bytes();
				actual_weight = actual_weight.set_proof_size(
					actual_weight.proof_size().saturating_sub(lane_extra_proof_size_bytes),
				);

				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: LOG_TARGET,
							"Received lane {:?} state update: latest_confirmed_nonce={}",
							lane_id,
							updated_latest_confirmed_nonce,
						);
					}
				}

				let mut lane_messages_received_status =
					ReceivedMessages::new(lane_id, Vec::with_capacity(lane_data.messages.len()));
				let mut is_lane_processing_stopped_no_weight_left = false;

				for mut message in lane_data.messages {
					debug_assert_eq!(message.key.lane_id, lane_id);
					total_messages += 1;

					if is_lane_processing_stopped_no_weight_left {
						lane_messages_received_status
							.push_skipped_for_not_enough_weight(message.key.nonce);
						continue
					}
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 message_dispatch_weight = T::MessageDispatch::dispatch_weight(&mut message);
					if message_dispatch_weight.any_gt(dispatch_weight_left) {
							target: LOG_TARGET,
							"Cannot dispatch any more messages on lane {:?}. Weight: declared={}, left={}",
							lane_id,
						lane_messages_received_status
							.push_skipped_for_not_enough_weight(message.key.nonce);
						is_lane_processing_stopped_no_weight_left = true;
						continue
					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 = match &receival_result {
						ReceivalResult::Dispatched(dispatch_result) => {
							valid_messages += 1;
							dispatch_result.unspent_weight
hacpy's avatar
hacpy committed
						},
						ReceivalResult::InvalidNonce |
						ReceivalResult::TooManyUnrewardedRelayers |
						ReceivalResult::TooManyUnconfirmedMessages => message_dispatch_weight,
					lane_messages_received_status.push(message.key.nonce, receival_result);
					let unspent_weight = unspent_weight.min(message_dispatch_weight);
					dispatch_weight_left -= message_dispatch_weight - unspent_weight;
					actual_weight = actual_weight.saturating_sub(unspent_weight);

				messages_received_status.push(lane_messages_received_status);
			// let's now deal with relayer payments
			T::DeliveryPayments::pay_reward(
				relayer_id_at_this_chain,
				total_messages,
				valid_messages,
				actual_weight,
			);

				target: LOG_TARGET,
				"Received messages: total={}, valid={}. Weight used: {}/{}.",
				total_messages,
				valid_messages,
				actual_weight,
				declared_weight,
			Self::deposit_event(Event::MessagesReceived(messages_received_status));

			Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
		/// Receive messages delivery proof from bridged chain.
		#[pallet::call_index(3)]
		#[pallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight(
		pub fn receive_messages_delivery_proof(
			proof: MessagesDeliveryProofOf<T, I>,
			relayers_state: UnrewardedRelayersState,
			Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
			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: LOG_TARGET,
hacpy's avatar
hacpy committed
						"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
			);
			// 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::<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: LOG_TARGET,
hacpy's avatar
hacpy committed
						"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: LOG_TARGET,
hacpy's avatar
hacpy committed
						"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 {
				// 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
				T::DeliveryConfirmationPayments::pay_reward(
Hernando Castano's avatar
Hernando Castano committed
			log::trace!(
				target: LOG_TARGET,
				"Received messages delivery proof up to (and including) {} at lane {:?}",
				last_delivered_nonce,
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config<I>, I: 'static = ()> {
		/// Message has been accepted and is waiting to be delivered.
		MessageAccepted { lane_id: LaneId, nonce: MessageNonce },
		/// Messages have been received from the bridged chain.
		MessagesReceived(
			Vec<
				ReceivedMessages<
					<T::MessageDispatch as MessageDispatch<T::AccountId>>::DispatchLevelResult,
				>,
			>,
		),
		/// Messages in the inclusive range have been delivered to the bridged chain.
		MessagesDelivered { lane_id: LaneId, messages: DeliveredMessages },
	#[pallet::error]
	pub enum Error<T, I = ()> {
		/// Pallet is not in Normal operating mode.
		NotOperatingNormally,
		/// The outbound lane is inactive.
		InactiveOutboundLane,
		/// 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,
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,
		/// 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<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<_, MessagesOperatingMode, ValueQuery>;

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

	/// Map of lane id => outbound lane data.
	#[pallet::storage]
	pub type OutboundLanes<T: Config<I>, I: 'static = ()> = StorageMap<
		Hasher = Blake2_128Concat,
		Key = LaneId,
		Value = OutboundLaneData,
		QueryKind = ValueQuery,
		OnEmpty = GetDefault,
		MaxValues = MaybeOutboundLanesCount<T, I>,
	>;

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

	#[pallet::genesis_config]
	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
		/// Initial pallet operating mode.
		pub operating_mode: MessagesOperatingMode,
		/// 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.
		pub fn outbound_message_data(lane: LaneId, nonce: MessageNonce) -> Option<MessagePayload> {
			OutboundMessages::<T, I>::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 },
			};
			InboundMessageDetails {
				dispatch_weight: T::MessageDispatch::dispatch_weight(&mut dispatch_message),
			}
		}

		/// Return inbound lane data.
		pub fn inbound_lane_data(lane: LaneId) -> InboundLaneData<T::InboundRelayer> {
			InboundLanes::<T, I>::get(lane).0
		}

	/// Get-parameter that returns number of active outbound lanes that the pallet maintains.
	pub struct MaybeOutboundLanesCount<T, I>(PhantomData<(T, I)>);

	impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeOutboundLanesCount<T, I> {
		fn get() -> Option<u32> {
			Some(T::ActiveOutboundLanes::get().len() as u32)
		}
	}
impl<T, I> bp_messages::source_chain::MessagesBridge<T::RuntimeOrigin, T::OutboundPayload>
	for Pallet<T, I>
where
	T: Config<I>,
	I: 'static,
{
	type Error = sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>;

	fn send_message(
		lane: LaneId,
		message: T::OutboundPayload,
	) -> Result<SendMessageArtifacts, Self::Error> {
		crate::send_message::<T, I>(sender, lane, message)
	}
}

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

	// let's check if outbound lane is active
	ensure!(T::ActiveOutboundLanes::get().contains(&lane_id), Error::<T, I>::InactiveOutboundLane,);

	// 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::<T, I>::MessageRejectedByChainVerifier
	})?;

	// now let's enforce any additional lane rules
	let mut lane = outbound_lane::<T, I>(lane_id);
	T::LaneMessageVerifier::verify_message(&submitter, &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::<T, I>::MessageRejectedByLaneVerifier
		},
	)?;

	// finally, save message in outbound storage and emit event
	let encoded_payload = payload.encode();
	let encoded_payload_len = encoded_payload.len();
	ensure!(
		encoded_payload_len <= T::MaximalOutboundPayloadSize::get() as usize,
		Error::<T, I>::MessageIsTooLarge
	);
	let nonce = lane.send_message(encoded_payload);

		target: LOG_TARGET,
		"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 })
/// Ensure that the pallet is in normal operational mode.
fn ensure_normal_operating_mode<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
	if PalletOperatingMode::<T, I>::get() ==
		MessagesOperatingMode::Basic(BasicOperatingMode::Normal)
	{
		return Ok(())

	Err(Error::<T, I>::NotOperatingNormally)
/// Creates new inbound lane object, backed by runtime storage.
hacpy's avatar
hacpy committed
fn inbound_lane<T: Config<I>, I: 'static>(
	lane_id: LaneId,
) -> InboundLane<RuntimeInboundLaneStorage<T, I>> {
	InboundLane::new(inbound_lane_storage::<T, I>(lane_id))
}

/// Creates new runtime inbound lane storage.
hacpy's avatar
hacpy committed
fn inbound_lane_storage<T: Config<I>, I: 'static>(
	lane_id: LaneId,
) -> RuntimeInboundLaneStorage<T, I> {
	RuntimeInboundLaneStorage {
		cached_data: RefCell::new(None),
		_phantom: Default::default(),
}

/// Creates new outbound lane object, backed by runtime storage.
hacpy's avatar
hacpy committed
fn outbound_lane<T: Config<I>, I: 'static>(
	lane_id: LaneId,
) -> OutboundLane<RuntimeOutboundLaneStorage<T, I>> {
	OutboundLane::new(RuntimeOutboundLaneStorage { lane_id, _phantom: Default::default() })
}

/// Runtime inbound lane storage.
struct RuntimeInboundLaneStorage<T: Config<I>, I: 'static = ()> {
	cached_data: RefCell<Option<InboundLaneData<T::InboundRelayer>>>,
	_phantom: PhantomData<I>,
impl<T: Config<I>, I: 'static> RuntimeInboundLaneStorage<T, I> {
	/// Returns number of bytes that may be subtracted from the PoV component of
	/// `receive_messages_proof` call, because the actual inbound lane state is smaller than the
	/// maximal configured.
	///
	/// Maximal inbound lane state set size is configured by the
	/// `MaxUnrewardedRelayerEntriesAtInboundLane` constant from the pallet configuration. The PoV
	/// of the call includes the maximal size of inbound lane state. If the actual size is smaller,
	/// we may subtract extra bytes from this component.
	pub fn extra_proof_size_bytes(&self) -> u64 {
		let max_encoded_len = StoredInboundLaneData::<T, I>::max_encoded_len();
		let relayers_count = self.data().relayers.len();
		let actual_encoded_len =
			InboundLaneData::<T::InboundRelayer>::encoded_size_hint(relayers_count)
				.unwrap_or(usize::MAX);
		max_encoded_len.saturating_sub(actual_encoded_len) as _
	}
}

impl<T: Config<I>, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage<T, I> {
	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<T::InboundRelayer> {
		match self.cached_data.clone().into_inner() {
			Some(data) => data,
			None => {
				let data: InboundLaneData<T::InboundRelayer> =
					InboundLanes::<T, I>::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
hacpy's avatar
hacpy committed
			},
	fn set_data(&mut self, data: InboundLaneData<T::InboundRelayer>) {
		*self.cached_data.try_borrow_mut().expect(
			"we're in the single-threaded environment;\
				we have no recursive borrows; qed",
		) = Some(data.clone());
		InboundLanes::<T, I>::insert(self.lane_id, StoredInboundLaneData::<T, I>(data))
	}
}

/// Runtime outbound lane storage.
struct RuntimeOutboundLaneStorage<T, I = ()> {
	lane_id: LaneId,
	_phantom: PhantomData<(T, I)>,
}

impl<T: Config<I>, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage<T, I> {
	fn id(&self) -> LaneId {
		self.lane_id
	}

	fn data(&self) -> OutboundLaneData {
		OutboundLanes::<T, I>::get(self.lane_id)
	}

	fn set_data(&mut self, data: OutboundLaneData) {
		OutboundLanes::<T, I>::insert(self.lane_id, data)
	fn message(&self, nonce: &MessageNonce) -> Option<MessagePayload> {
hacpy's avatar
hacpy committed
		OutboundMessages::<T, I>::get(MessageKey { lane_id: self.lane_id, nonce: *nonce })
	fn save_message(&mut self, nonce: MessageNonce, message_payload: MessagePayload) {
		OutboundMessages::<T, I>::insert(
			MessageKey { lane_id: self.lane_id, nonce },
			StoredMessagePayload::<T, I>::try_from(message_payload).expect(
				"save_message is called after all checks in send_message; \
					send_message checks message size; \
					qed",
			),
		);
	}

	fn remove_message(&mut self, nonce: &MessageNonce) {
hacpy's avatar
hacpy committed
		OutboundMessages::<T, I>::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<Chain: SourceHeaderChain, DispatchPayload: Decode>(
	proof: Chain::MessagesProof,
	messages_count: u32,
) -> Result<ProvedMessages<DispatchMessage<DispatchPayload>>, Chain::Error> {
	// `receive_messages_proof` weight formula and `MaxUnconfirmedMessagesAtInboundLane` check
	// guarantees that the `message_count` is sane and Vec<Message> 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, AccountId, DbWeight,
		RuntimeEvent as TestEvent, RuntimeOrigin, TestDeliveryConfirmationPayments,
		TestDeliveryPayments, TestMessagesDeliveryProof, TestMessagesProof, TestRelayer,
		TestRuntime, MAX_OUTBOUND_PAYLOAD_SIZE, PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD,
		TEST_LANE_ID, TEST_LANE_ID_2, TEST_LANE_ID_3, TEST_RELAYER_A, TEST_RELAYER_B,
	use bp_messages::{BridgeMessagesCall, UnrewardedRelayer, UnrewardedRelayersState};
	use bp_test_utils::generate_owned_bridge_module_tests;
	use frame_support::{
		assert_noop, assert_ok,
		storage::generator::{StorageMap, StorageValue},
Hernando Castano's avatar
Hernando Castano committed
	use frame_system::{EventRecord, Pallet as System, Phase};
	use sp_runtime::DispatchError;
		System::<TestRuntime>::set_block_number(1);
		System::<TestRuntime>::reset_events();
	fn inbound_unrewarded_relayers_state(
		lane: bp_messages::LaneId,
	) -> bp_messages::UnrewardedRelayersState {
		let inbound_lane_data = InboundLanes::<TestRuntime, ()>::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),
	fn send_regular_message() {
hacpy's avatar
hacpy committed
		let message_nonce =
			outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().latest_generated_nonce + 1;
		send_message::<TestRuntime, ()>(RuntimeOrigin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD)
			.expect("send_message has failed");

		// check event with assigned nonce
		assert_eq!(
			System::<TestRuntime>::events(),
			vec![EventRecord {
				phase: Phase::Initialization,
				event: TestEvent::Messages(Event::MessageAccepted {
					lane_id: TEST_LANE_ID,
					nonce: message_nonce
				}),
				topics: vec![],
			}],
		);
	}

	fn receive_messages_delivery_proof() {
		System::<TestRuntime>::set_block_number(1);
		System::<TestRuntime>::reset_events();

Hernando Castano's avatar
Hernando Castano committed
		assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
			TestMessagesDeliveryProof(Ok((
				TEST_LANE_ID,
				InboundLaneData {
					last_confirmed_nonce: 1,
						messages: DeliveredMessages::new(1),