// Copyright (C) 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. #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] pub use inbound_lane::{InboundLane, InboundLaneStorage, StoredInboundLaneData}; pub use lanes_manager::{ LanesManager, LanesManagerError, RuntimeInboundLaneStorage, RuntimeOutboundLaneStorage, }; pub use outbound_lane::{ OutboundLane, OutboundLaneStorage, ReceptionConfirmationError, StoredMessagePayload, }; pub use weights::WeightInfo; pub use weights_ext::{ ensure_able_to_receive_confirmation, ensure_able_to_receive_message, ensure_maximal_message_dispatch, ensure_weights_are_correct, WeightInfoExt, EXPECTED_DEFAULT_MESSAGE_LENGTH, EXTRA_STORAGE_PROOF_SIZE, }; use bp_header_chain::HeaderChain; use bp_messages::{ source_chain::{ DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered, SendMessageArtifacts, }, target_chain::{ DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch, ProvedLaneMessages, ProvedMessages, }, ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, UnrewardedRelayersState, VerificationError, }; use bp_runtime::{ AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, Size, }; use codec::{Decode, Encode}; use pezframe_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound}; use pezsp_std::{marker::PhantomData, prelude::*}; mod call_ext; mod inbound_lane; mod lanes_manager; mod outbound_lane; mod proofs; mod tests; mod weights_ext; pub mod weights; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; pub mod migration; pub use call_ext::*; pub use pallet::*; #[cfg(feature = "test-helpers")] pub use tests::*; /// The target that will be used when publishing logs related to this pallet. pub const LOG_TARGET: &str = "runtime::bridge-messages"; #[pezframe_support::pallet] pub mod pallet { use super::*; use bp_messages::{LaneIdType, ReceivedMessages, ReceptionResult}; use bp_runtime::RangeInclusiveExt; use pezframe_support::pezpallet_prelude::*; use pezframe_system::pezpallet_prelude::*; #[pallet::config] pub trait Config: pezframe_system::Config { // General types /// The overarching event type. #[allow(deprecated)] type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Benchmarks results from runtime we're plugged into. type WeightInfo: WeightInfoExt; /// This chain type. type ThisChain: ChainWithMessages; /// Bridged chain type. type BridgedChain: ChainWithMessages; /// Bridged chain headers provider. type BridgedHeaderChain: HeaderChain; /// 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; /// Lane identifier type. type LaneId: LaneIdType; /// Handler for relayer payments that happen during message delivery transaction. type DeliveryPayments: DeliveryPayments; /// Handler for relayer payments that happen during message delivery confirmation /// transaction. type DeliveryConfirmationPayments: DeliveryConfirmationPayments< Self::AccountId, Self::LaneId, >; /// Delivery confirmation callback. type OnMessagesDelivered: OnMessagesDelivered; /// Message dispatch handler. type MessageDispatch: MessageDispatch< DispatchPayload = Self::InboundPayload, LaneId = Self::LaneId, >; } /// Shortcut to this chain type for Config. pub type ThisChainOf = >::ThisChain; /// Shortcut to bridged chain type for Config. pub type BridgedChainOf = >::BridgedChain; /// Shortcut to bridged header chain type for Config. pub type BridgedHeaderChainOf = >::BridgedHeaderChain; /// Shortcut to lane identifier type for Config. pub type LaneIdOf = >::LaneId; #[pallet::pallet] #[pallet::storage_version(migration::STORAGE_VERSION)] 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::call_index(0)] #[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::call_index(1)] #[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) } /// 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. /// /// The call fails if: /// /// - the pallet is halted; /// /// - the call origin is not `Signed(_)`; /// /// - there are too many messages in the proof; /// /// - the proof verification procedure returns an error - e.g. because header used to craft /// proof is not imported by the associated finality pallet; /// /// - the `dispatch_weight` argument is not sufficient to dispatch all bundled messages. /// /// The call may succeed, but some messages may not be delivered e.g. if they are not fit /// into the unrewarded relayers vector. #[pallet::call_index(2)] #[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: AccountIdOf>, proof: Box>, T::LaneId>>, 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) <= BridgedChainOf::::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, 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 message. 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 (lane_id, lane_data) = verify_and_decode_messages_proof::(*proof, messages_count).map_err( |err| { tracing::trace!(target: LOG_TARGET, error=?err, "Rejecting invalid messages proof"); 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; let mut lane = active_inbound_lane::(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 { tracing::trace!( target: LOG_TARGET, ?lane_id, latest_confirmed_nonce=%updated_latest_confirmed_nonce, unrewarded_relayers=?UnrewardedRelayersState::from(&lane.storage().data()), "Received state update" ); } } let mut messages_received_status = ReceivedMessages::new(lane_id, Vec::with_capacity(lane_data.messages.len())); for mut message in lane_data.messages { debug_assert_eq!(message.key.lane_id, lane_id); total_messages += 1; // 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) { tracing::trace!( target: LOG_TARGET, ?lane_id, declared=%message_dispatch_weight, left=%dispatch_weight_left, "Cannot dispatch any more messages" ); fail!(Error::::InsufficientDispatchWeight); } let receival_result = lane.receive_message::( &relayer_id_at_bridged_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 { ReceptionResult::Dispatched(dispatch_result) => { valid_messages += 1; dispatch_result.unspent_weight }, ReceptionResult::InvalidNonce | ReceptionResult::TooManyUnrewardedRelayers | ReceptionResult::TooManyUnconfirmedMessages => message_dispatch_weight, }; 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); } // let's now deal with relayer payments T::DeliveryPayments::pay_reward( relayer_id_at_this_chain, total_messages, valid_messages, actual_weight, ); tracing::debug!( target: LOG_TARGET, total=%total_messages, valid=%valid_messages, %actual_weight, %declared_weight, "Received messages." ); 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( proof, relayers_state, ))] pub fn receive_messages_delivery_proof( origin: OriginFor, proof: FromBridgedChainMessagesDeliveryProof>, T::LaneId>, mut relayers_state: UnrewardedRelayersState, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; let proof_size = proof.size(); let confirmation_relayer = ensure_signed(origin)?; let (lane_id, lane_data) = proofs::verify_messages_delivery_proof::(proof) .map_err(|err| { tracing::trace!( target: LOG_TARGET, error=?err, "Rejecting invalid messages delivery proof" ); Error::::InvalidMessagesDeliveryProof })?; ensure!( relayers_state.is_valid(&lane_data), Error::::InvalidUnrewardedRelayersState ); // mark messages as delivered let mut lane = any_state_outbound_lane::(lane_id)?; let last_delivered_nonce = lane_data.last_delivered_nonce(); let confirmed_messages = lane .confirm_delivery( relayers_state.total_messages, last_delivered_nonce, &lane_data.relayers, ) .map_err(Error::::ReceptionConfirmation)?; 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: lane_id.into(), messages: confirmed_messages, }); // if some new messages have been confirmed, reward relayers let actually_rewarded_relayers = T::DeliveryConfirmationPayments::pay_reward( lane_id, lane_data.relayers, &confirmation_relayer, &received_range, ); // update relayers state with actual numbers to compute actual weight below relayers_state.unrewarded_relayer_entries = pezsp_std::cmp::min( relayers_state.unrewarded_relayer_entries, actually_rewarded_relayers, ); relayers_state.total_messages = pezsp_std::cmp::min( relayers_state.total_messages, received_range.checked_len().unwrap_or(MessageNonce::MAX), ); }; tracing::trace!( target: LOG_TARGET, ?lane_id, %last_delivered_nonce, "Received messages delivery proof up to (and including)" ); // notify others about messages delivery T::OnMessagesDelivered::on_messages_delivered( lane_id, lane.data().queued_messages().saturating_len(), ); // because of lags, the inbound lane state (`lane_data`) may have entries for // already rewarded relayers and messages (if all entries are duplicated, then // this transaction must be filtered out by our signed extension) let actual_weight = T::WeightInfo::receive_messages_delivery_proof_weight( &PreComputedSize(proof_size as usize), &relayers_state, ); 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 = ()> { /// Message has been accepted and is waiting to be delivered. MessageAccepted { /// Lane, which has accepted the message. lane_id: T::LaneId, /// Nonce of accepted message. nonce: MessageNonce, }, /// Messages have been received from the bridged chain. MessagesReceived( /// Result of received messages dispatch. ReceivedMessages< ::DispatchLevelResult, T::LaneId, >, ), /// Messages in the inclusive range have been delivered to the bridged chain. MessagesDelivered { /// Lane for which the delivery has been confirmed. lane_id: T::LaneId, /// Delivered messages. messages: DeliveredMessages, }, } #[pallet::error] #[derive(PartialEq, Eq)] pub enum Error { /// Pallet is not in Normal operating mode. NotOperatingNormally, /// Error that is reported by the lanes manager. LanesManager(LanesManagerError), /// Message has been treated as invalid by the pallet logic. MessageRejectedByPallet(VerificationError), /// The transaction brings too many messages. TooManyMessagesInTheProof, /// Invalid messages has been submitted. InvalidMessagesProof, /// Invalid messages delivery proof has been submitted. InvalidMessagesDeliveryProof, /// The relayer has declared invalid unrewarded relayers state in the /// `receive_messages_delivery_proof` call. InvalidUnrewardedRelayersState, /// The cumulative dispatch weight, passed by relayer is not enough to cover dispatch /// of all bundled messages. InsufficientDispatchWeight, /// Error confirming messages receival. ReceptionConfirmation(ReceptionConfirmationError), /// 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 `set_operating_mode`). #[pallet::storage] 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] pub type PalletOperatingMode, I: 'static = ()> = StorageValue<_, MessagesOperatingMode, ValueQuery>; // TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/89: let's limit number of // possible opened lanes && use it to constraint maps below /// Map of lane id => inbound lane data. #[pallet::storage] pub type InboundLanes, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::LaneId, StoredInboundLaneData, OptionQuery>; /// Map of lane id => outbound lane data. #[pallet::storage] pub type OutboundLanes, I: 'static = ()> = StorageMap< Hasher = Blake2_128Concat, Key = T::LaneId, Value = OutboundLaneData, QueryKind = OptionQuery, >; /// All queued outbound messages. #[pallet::storage] pub type OutboundMessages, I: 'static = ()> = StorageMap<_, Blake2_128Concat, MessageKey, StoredMessagePayload>; #[pallet::genesis_config] #[derive(DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { /// Initial pallet operating mode. pub operating_mode: MessagesOperatingMode, /// Initial pallet owner. pub owner: Option, /// Opened lanes. pub opened_lanes: Vec, /// Dummy marker. #[serde(skip)] pub _phantom: pezsp_std::marker::PhantomData, } #[pallet::genesis_build] impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { PalletOperatingMode::::put(self.operating_mode); if let Some(ref owner) = self.owner { PalletOwner::::put(owner); } for lane_id in &self.opened_lanes { InboundLanes::::insert(lane_id, InboundLaneData::opened()); OutboundLanes::::insert(lane_id, OutboundLaneData::opened()); } } } #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { #[cfg(feature = "try-runtime")] fn try_state(_n: BlockNumberFor) -> Result<(), pezsp_runtime::TryRuntimeError> { Self::do_try_state() } } impl, I: 'static> Pallet { /// Get stored data of the outbound message with given nonce. pub fn outbound_message_data( lane: T::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: T::LaneId, payload: MessagePayload, outbound_details: OutboundMessageDetails, ) -> InboundMessageDetails { let mut dispatch_message = DispatchMessage { key: MessageKey { lane_id: lane, nonce: outbound_details.nonce }, data: payload.into(), }; InboundMessageDetails { dispatch_weight: T::MessageDispatch::dispatch_weight(&mut dispatch_message), } } /// Return outbound lane data. pub fn outbound_lane_data(lane: T::LaneId) -> Option { OutboundLanes::::get(lane) } /// Return inbound lane data. pub fn inbound_lane_data( lane: T::LaneId, ) -> Option>>> { InboundLanes::::get(lane).map(|lane| lane.0) } } #[cfg(any(feature = "try-runtime", test))] impl, I: 'static> Pallet { /// Ensure the correctness of the state of this pallet. pub fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> { Self::do_try_state_for_outbound_lanes() } /// Ensure the correctness of the state of outbound lanes. pub fn do_try_state_for_outbound_lanes() -> Result<(), pezsp_runtime::TryRuntimeError> { use pezsp_runtime::traits::One; use pezsp_std::vec::Vec; // collect unpruned lanes let mut unpruned_lanes = Vec::new(); for (lane_id, lane_data) in OutboundLanes::::iter() { let Some(expected_last_prunned_nonce) = lane_data.oldest_unpruned_nonce.checked_sub(One::one()) else { continue; }; // collect message_nonces that were supposed to be pruned let mut unpruned_message_nonces = Vec::new(); const MAX_MESSAGES_ITERATION: u64 = 16; let start_nonce = expected_last_prunned_nonce.checked_sub(MAX_MESSAGES_ITERATION).unwrap_or(0); for current_nonce in start_nonce..=expected_last_prunned_nonce { // check a message for current_nonce if OutboundMessages::::contains_key(MessageKey { lane_id, nonce: current_nonce, }) { unpruned_message_nonces.push(current_nonce); } } if !unpruned_message_nonces.is_empty() { tracing::warn!( target: LOG_TARGET, ?lane_id, ?lane_data, ?unpruned_message_nonces, "do_try_state_for_outbound_lanes found", ); unpruned_lanes.push((lane_id, lane_data, unpruned_message_nonces)); } } // ensure messages before `oldest_unpruned_nonce` are really pruned. ensure!(unpruned_lanes.is_empty(), "Found unpruned lanes!"); Ok(()) } } } /// Structure, containing a validated message payload and all the info required /// to send it on the bridge. #[derive(Debug, PartialEq, Eq)] pub struct SendMessageArgs, I: 'static> { lane_id: T::LaneId, lane: OutboundLane>, payload: StoredMessagePayload, } impl bp_messages::source_chain::MessagesBridge for Pallet where T: Config, I: 'static, { type Error = Error; type SendMessageArgs = SendMessageArgs; fn validate_message( lane_id: T::LaneId, message: &T::OutboundPayload, ) -> Result, Self::Error> { // we can't accept any messages if the pallet is halted ensure_normal_operating_mode::()?; // check lane let lane = active_outbound_lane::(lane_id)?; Ok(SendMessageArgs { lane_id, lane, payload: StoredMessagePayload::::try_from(message.encode()).map_err(|_| { Error::::MessageRejectedByPallet(VerificationError::MessageTooLarge) })?, }) } fn send_message(args: SendMessageArgs) -> SendMessageArtifacts { // save message in outbound storage and emit event let mut lane = args.lane; let message_len = args.payload.len(); let nonce = lane.send_message(args.payload); // return number of messages in the queue to let sender know about its state let enqueued_messages = lane.data().queued_messages().saturating_len(); tracing::trace!( target: LOG_TARGET, lane_id=?args.lane_id, %nonce, message_size=?message_len, "Accepted message" ); Pallet::::deposit_event(Event::MessageAccepted { lane_id: args.lane_id.into(), nonce, }); SendMessageArtifacts { nonce, enqueued_messages } } } /// 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. Lane must be active. fn active_inbound_lane, I: 'static>( lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .active_inbound_lane(lane_id) .map_err(Error::LanesManager) } /// Creates new outbound lane object, backed by runtime storage. Lane must be active. fn active_outbound_lane, I: 'static>( lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .active_outbound_lane(lane_id) .map_err(Error::LanesManager) } /// Creates new outbound lane object, backed by runtime storage. fn any_state_outbound_lane, I: 'static>( lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .any_state_outbound_lane(lane_id) .map_err(Error::LanesManager) } /// Verify messages proof and return proved messages with decoded payload. fn verify_and_decode_messages_proof, I: 'static>( proof: FromBridgedChainMessagesProof>, T::LaneId>, messages_count: u32, ) -> Result< ProvedMessages>, VerificationError, > { // `receive_messages_proof` weight formula and `MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` // 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) proofs::verify_messages_proof::(proof, messages_count).map(|(lane, lane_data)| { ( lane, ProvedLaneMessages { lane_state: lane_data.lane_state, messages: lane_data.messages.into_iter().map(Into::into).collect(), }, ) }) }