pallet_messenger/
lib.rs

1// Copyright (C) 2021 Subspace Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Pallet messenger used to communicate between domains and other blockchains.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![forbid(unsafe_code)]
20#![warn(rust_2018_idioms)]
21#![feature(variant_count, if_let_guard)]
22
23#[cfg(feature = "runtime-benchmarks")]
24mod benchmarking;
25pub mod extensions;
26mod fees;
27mod messages;
28#[cfg(test)]
29mod mock;
30#[cfg(test)]
31mod tests;
32pub mod weights;
33
34#[cfg(not(feature = "std"))]
35extern crate alloc;
36
37use frame_support::__private::RuntimeDebug;
38use frame_support::pallet_prelude::{EnsureOrigin, MaxEncodedLen, StorageVersion};
39use frame_support::traits::fungible::{Inspect, InspectHold};
40use frame_system::pallet_prelude::BlockNumberFor;
41pub use pallet::*;
42use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
43use scale_info::TypeInfo;
44use sp_core::U256;
45use sp_domains::{DomainAllowlistUpdates, DomainId};
46use sp_messenger::MAX_FUTURE_ALLOWED_NONCES;
47use sp_messenger::messages::{
48    ChainId, Channel, ChannelId, ChannelState, CrossDomainMessage, Message, Nonce,
49};
50use sp_runtime::DispatchError;
51use sp_runtime::traits::Hash;
52use subspace_runtime_primitives::CreateUnsigned;
53pub use weights::WeightInfo;
54
55/// Transaction validity for a given validated XDM extrinsic.
56/// If the extrinsic is not included in the bundle, extrinsic is removed from the TxPool.
57const XDM_TRANSACTION_LONGEVITY: u64 = 10;
58
59/// XDM verification errors.
60pub(crate) mod verification_errors {
61    // When updating these error codes, check for clashes between:
62    // <https://github.com/autonomys/subspace/blob/main/domains/primitives/runtime/src/lib.rs#L85-L88>
63    // <https://github.com/autonomys/subspace/blob/main/crates/sp-domains-fraud-proof/src/lib.rs#L49-L64>
64    pub(crate) const INVALID_NONCE: u8 = 201;
65    // Custom error code when a messenger nonce overflows.
66    pub(crate) const XDM_NONCE_OVERFLOW: u8 = 202;
67    // This error code was previously 200, but that clashed with ERR_BALANCE_OVERFLOW.
68    pub(crate) const INVALID_CHANNEL: u8 = 203;
69    pub(crate) const IN_FUTURE_NONCE: u8 = 204;
70    // Failed to update next nonce during the pre_dispatch
71    pub(crate) const NEXT_NONCE_UPDATE: u8 = 205;
72}
73
74#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy, DecodeWithMemTracking)]
75pub enum OutboxMessageResult {
76    /// Message response handler returned Ok.
77    Ok,
78    /// Message response handler failed with Err.
79    Err(DispatchError),
80}
81
82/// Custom origin for validated unsigned extrinsics.
83#[derive(
84    PartialEq,
85    Eq,
86    Clone,
87    Encode,
88    Decode,
89    RuntimeDebug,
90    TypeInfo,
91    MaxEncodedLen,
92    DecodeWithMemTracking,
93)]
94pub enum RawOrigin {
95    ValidatedUnsigned,
96}
97
98/// Ensure the messenger origin.
99pub struct EnsureMessengerOrigin;
100impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureMessengerOrigin {
101    type Success = ();
102
103    fn try_origin(o: O) -> Result<Self::Success, O> {
104        o.into().map(|o| match o {
105            RawOrigin::ValidatedUnsigned => (),
106        })
107    }
108
109    #[cfg(feature = "runtime-benchmarks")]
110    fn try_successful_origin() -> Result<O, ()> {
111        Ok(O::from(RawOrigin::ValidatedUnsigned))
112    }
113}
114
115pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>::Output;
116pub(crate) type BalanceOf<T> =
117    <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
118pub(crate) type FungibleHoldId<T> =
119    <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
120
121/// Parameter to update chain allow list.
122#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy, DecodeWithMemTracking)]
123pub enum ChainAllowlistUpdate {
124    Add(ChainId),
125    Remove(ChainId),
126}
127
128impl ChainAllowlistUpdate {
129    fn chain_id(&self) -> ChainId {
130        match self {
131            ChainAllowlistUpdate::Add(chain_id) => *chain_id,
132            ChainAllowlistUpdate::Remove(chain_id) => *chain_id,
133        }
134    }
135}
136
137#[derive(Debug, Encode, Decode, TypeInfo)]
138pub struct ValidatedRelayMessage<T: Config> {
139    pub message: Message<BalanceOf<T>>,
140    pub should_init_channel: bool,
141    pub next_nonce: Nonce,
142}
143
144/// Channel can be closed either by Channel owner or Sudo
145#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
146pub(crate) enum CloseChannelBy<AccountId> {
147    Owner(AccountId),
148    Sudo,
149}
150
151/// Hold identifier trait for messenger specific balance holds
152pub trait HoldIdentifier<T: Config> {
153    fn messenger_channel() -> FungibleHoldId<T>;
154}
155
156/// The current storage version.
157const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
158
159#[frame_support::pallet]
160mod pallet {
161    pub use crate::extensions::weights::WeightInfo as ExtensionWeightInfo;
162    use crate::weights::WeightInfo;
163    use crate::{
164        BalanceOf, ChainAllowlistUpdate, Channel, ChannelId, ChannelState, CloseChannelBy,
165        HoldIdentifier, Nonce, OutboxMessageResult, RawOrigin, STORAGE_VERSION, StateRootOf, U256,
166        ValidatedRelayMessage,
167    };
168    #[cfg(not(feature = "std"))]
169    use alloc::boxed::Box;
170    #[cfg(not(feature = "std"))]
171    use alloc::collections::BTreeSet;
172    #[cfg(not(feature = "std"))]
173    use alloc::vec::Vec;
174    use core::cmp::Ordering;
175    use frame_support::ensure;
176    use frame_support::pallet_prelude::*;
177    use frame_support::storage::with_storage_layer;
178    use frame_support::traits::fungible::{Balanced, Inspect, InspectHold, Mutate, MutateHold};
179    use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
180    use frame_support::weights::WeightToFee;
181    use frame_system::pallet_prelude::*;
182    use sp_core::storage::StorageKey;
183    use sp_domains::proof_provider_and_verifier::{StorageProofVerifier, VerificationError};
184    use sp_domains::{DomainAllowlistUpdates, DomainId, DomainOwner};
185    use sp_messenger::endpoint::{
186        Endpoint, EndpointHandler, EndpointRequest, EndpointRequestWithCollectedFee, Sender,
187    };
188    use sp_messenger::messages::{
189        ChainId, ChannelOpenParamsV1, ChannelStateWithNonce, CrossDomainMessage, Message,
190        MessageId, MessageKey, MessageWeightTag, PayloadV1, ProtocolMessageRequest,
191        RequestResponse, VersionedPayload,
192    };
193    use sp_messenger::{
194        ChannelNonce, DomainRegistration, INHERENT_IDENTIFIER, InherentError, InherentType,
195        NoteChainTransfer, OnXDMRewards, StorageKeys,
196    };
197    use sp_runtime::traits::Zero;
198    use sp_runtime::{ArithmeticError, Perbill, Saturating};
199    use sp_subspace_mmr::MmrProofVerifier;
200    #[cfg(feature = "std")]
201    use std::collections::BTreeSet;
202
203    #[pallet::config]
204    pub trait Config: frame_system::Config {
205        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
206        /// Gets the chain_id that is treated as src_chain_id for outgoing messages.
207        type SelfChainId: Get<ChainId>;
208        /// function to fetch endpoint response handler by Endpoint.
209        fn get_endpoint_handler(endpoint: &Endpoint)
210        -> Option<Box<dyn EndpointHandler<MessageId>>>;
211        /// Currency type pallet uses for fees and deposits.
212        type Currency: Mutate<Self::AccountId>
213            + InspectHold<Self::AccountId>
214            + MutateHold<Self::AccountId>
215            + Balanced<Self::AccountId>;
216        /// Weight information for extrinsics in this pallet.
217        type WeightInfo: WeightInfo;
218        /// Weight to fee conversion.
219        type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
220        /// Adjusted Weight to fee conversion.
221        /// This includes the TransactionPayment Multiper at the time of fee deduction.
222        type AdjustedWeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
223        /// Fee Multiper for XDM
224        /// Final fee calculated will fee_multiplier * adjusted_weight_to_fee.
225        #[pallet::constant]
226        type FeeMultiplier: Get<u32>;
227        /// Handle XDM rewards.
228        type OnXDMRewards: OnXDMRewards<BalanceOf<Self>>;
229        /// Hash type of MMR
230        type MmrHash: Parameter + Member + Default + Clone;
231        /// MMR proof verifier
232        type MmrProofVerifier: MmrProofVerifier<Self::MmrHash, BlockNumberFor<Self>, StateRootOf<Self>>;
233        /// Storage key provider.
234        type StorageKeys: StorageKeys;
235        /// Domain owner provider.
236        type DomainOwner: DomainOwner<Self::AccountId>;
237        /// A variation of the Identifier used for holding the funds used for Messenger
238        type HoldIdentifier: HoldIdentifier<Self>;
239        /// Channel reserve fee to open a channel.
240        #[pallet::constant]
241        type ChannelReserveFee: Get<BalanceOf<Self>>;
242        /// Portion of Channel reserve taken by the protocol
243        /// if the channel is in init state and is requested to be closed.
244        #[pallet::constant]
245        type ChannelInitReservePortion: Get<Perbill>;
246        /// Type to check if a given domain is registered on Consensus chain.
247        type DomainRegistration: DomainRegistration;
248        /// Maximum outgoing messages from a given channel
249        #[pallet::constant]
250        type MaxOutgoingMessages: Get<u32>;
251        /// Origin for messenger call.
252        type MessengerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
253        /// Helper to note cross chain XDM fee transfer
254        type NoteChainTransfer: NoteChainTransfer<BalanceOf<Self>>;
255        /// Weight info for extensions
256        type ExtensionWeightInfo: ExtensionWeightInfo;
257    }
258
259    /// Pallet messenger used to communicate between chains and other blockchains.
260    #[pallet::pallet]
261    #[pallet::without_storage_info]
262    #[pallet::storage_version(STORAGE_VERSION)]
263    pub struct Pallet<T>(_);
264
265    /// Stores the next channel id for a foreign chain.
266    #[pallet::storage]
267    #[pallet::getter(fn next_channel_id)]
268    pub(super) type NextChannelId<T: Config> =
269        StorageMap<_, Identity, ChainId, ChannelId, ValueQuery>;
270
271    /// Stores channel config between two chains.
272    /// Key points to the foreign chain wrt own chain's storage name space
273    #[pallet::storage]
274    #[pallet::getter(fn channels)]
275    pub(super) type Channels<T: Config> = StorageDoubleMap<
276        _,
277        Identity,
278        ChainId,
279        Identity,
280        ChannelId,
281        Channel<BalanceOf<T>, T::AccountId>,
282        OptionQuery,
283    >;
284
285    /// A temporary storage for storing decoded inbox message between `pre_dispatch_relay_message`
286    /// and `relay_message`.
287    #[pallet::storage]
288    #[pallet::getter(fn inbox)]
289    pub(super) type Inbox<T: Config> = StorageValue<_, Message<BalanceOf<T>>, OptionQuery>;
290
291    /// A temporary storage of fees for executing an inbox message.
292    /// The storage is cleared when the acknowledgement of inbox response is received
293    /// from the src_chain.
294    #[pallet::storage]
295    #[pallet::getter(fn inbox_fees)]
296    pub(super) type InboxFee<T: Config> =
297        StorageMap<_, Identity, (ChainId, MessageId), BalanceOf<T>, OptionQuery>;
298
299    /// A temporary storage of fees for executing an outbox message and its response from dst_chain.
300    /// The storage is cleared when src_chain receives the response from dst_chain.
301    #[pallet::storage]
302    #[pallet::getter(fn outbox_fees)]
303    pub(super) type OutboxFee<T: Config> =
304        StorageMap<_, Identity, (ChainId, MessageId), BalanceOf<T>, OptionQuery>;
305
306    /// Stores the message responses of the incoming processed responses.
307    /// Used by the dst_chains to verify the message response.
308    #[pallet::storage]
309    #[pallet::getter(fn inbox_responses)]
310    pub(super) type InboxResponses<T: Config> =
311        StorageMap<_, Identity, (ChainId, ChannelId, Nonce), Message<BalanceOf<T>>, OptionQuery>;
312
313    /// Stores the outgoing messages that are awaiting message responses from the dst_chain.
314    /// Messages are processed in the outbox nonce order of chain's channel.
315    #[pallet::storage]
316    #[pallet::getter(fn outbox)]
317    pub(super) type Outbox<T: Config> =
318        StorageMap<_, Identity, (ChainId, ChannelId, Nonce), Message<BalanceOf<T>>, OptionQuery>;
319
320    /// Stores the outgoing messages count that are awaiting message responses from the dst_chain.
321    #[pallet::storage]
322    #[pallet::getter(fn outbox_message_count)]
323    pub(super) type OutboxMessageCount<T: Config> =
324        StorageMap<_, Identity, (ChainId, ChannelId), u32, ValueQuery>;
325
326    /// A temporary storage for storing decoded outbox response message between `pre_dispatch_relay_message_response`
327    /// and `relay_message_response`.
328    #[pallet::storage]
329    #[pallet::getter(fn outbox_responses)]
330    pub(super) type OutboxResponses<T: Config> =
331        StorageValue<_, Message<BalanceOf<T>>, OptionQuery>;
332
333    /// Storage to store the weight tags for all the outbox messages.
334    #[pallet::storage]
335    #[pallet::getter(fn outbox_message_weight_tags)]
336    pub(super) type OutboxMessageWeightTags<T: Config> =
337        StorageMap<_, Identity, (ChainId, MessageId), MessageWeightTag>;
338
339    /// Storage to store the weight tags for all the inbox responses messages.
340    #[pallet::storage]
341    #[pallet::getter(fn inbox_response_message_weight_tags)]
342    pub(super) type InboxResponseMessageWeightTags<T: Config> =
343        StorageMap<_, Identity, (ChainId, MessageId), MessageWeightTag>;
344
345    /// An allowlist of chains that can open channel with this chain.
346    #[pallet::storage]
347    #[pallet::getter(fn chain_allowlist)]
348    pub(super) type ChainAllowlist<T: Config> = StorageValue<_, BTreeSet<ChainId>, ValueQuery>;
349
350    /// A storage to store any allowlist updates to domain. The updates will be cleared in the next block
351    /// once the previous block has a domain bundle, but a empty value should be left because in the invalid
352    /// extrinsic root fraud proof the prover need to generate a proof-of-empty-value for the domain.
353    #[pallet::storage]
354    #[pallet::getter(fn domain_chain_allowlist_updates)]
355    pub(super) type DomainChainAllowlistUpdate<T: Config> =
356        StorageMap<_, Identity, DomainId, DomainAllowlistUpdates, OptionQuery>;
357
358    /// Temporary storage to store the updated channels between this chain and other chain.
359    /// Storage is cleared on block initialization.
360    #[pallet::storage]
361    pub(super) type UpdatedChannels<T: Config> =
362        StorageValue<_, BTreeSet<(ChainId, ChannelId)>, ValueQuery>;
363
364    /// Storage to track the inbox fees that is hold on the chain before distributing.
365    ///
366    /// NOTE: The inbox fees is accounted to the chain's total issuance but not hold on any account
367    /// because an account with balance below ED will be reaped, in this way, we can manage small
368    /// inbox fee that less than ED easier. It also means whenever `InboxFeesOnHold` is increase/decrease
369    /// we need to increase/decrease the total issuance manually.
370    #[pallet::storage]
371    pub(super) type InboxFeesOnHold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
372
373    /// Storage to track the outbox fees that is hold on the chain before distributing.
374    ///
375    /// NOTE: The outbox fees is accounted to the chain's total issuance but not hold on any account
376    /// because an account with balance below ED will be reaped, in this way, we can manage small
377    /// outbox fee that less than ED easier. It also means whenever `OutboxFeesOnHold` is increase/decrease
378    /// we need to increase/decrease the total issuance manually.
379    #[pallet::storage]
380    pub(super) type OutboxFeesOnHold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
381
382    #[pallet::origin]
383    pub type Origin = RawOrigin;
384
385    /// `pallet-messenger` events
386    #[pallet::event]
387    #[pallet::generate_deposit(pub (super) fn deposit_event)]
388    pub enum Event<T: Config> {
389        /// Emits when a channel between two chains is initiated.
390        ChannelInitiated {
391            /// Foreign chain id this channel connects to.
392            chain_id: ChainId,
393            /// Channel ID of the said channel.
394            channel_id: ChannelId,
395        },
396
397        /// Emits when a channel between two chains is closed.
398        ChannelClosed {
399            /// Foreign chain id this channel connects to.
400            chain_id: ChainId,
401            /// Channel ID of the said channel.
402            channel_id: ChannelId,
403        },
404
405        /// Emits when a channel between two chain is open.
406        ChannelOpen {
407            /// Foreign chain id this channel connects to.
408            chain_id: ChainId,
409            /// Channel ID of the said channel.
410            channel_id: ChannelId,
411        },
412
413        /// Emits when a new message is added to the outbox.
414        OutboxMessage {
415            chain_id: ChainId,
416            channel_id: ChannelId,
417            nonce: Nonce,
418        },
419
420        /// Emits when a message response is available for Outbox message.
421        OutboxMessageResponse {
422            /// Destination chain ID.
423            chain_id: ChainId,
424            /// Channel Is
425            channel_id: ChannelId,
426            nonce: Nonce,
427        },
428
429        /// Emits outbox message result.
430        OutboxMessageResult {
431            chain_id: ChainId,
432            channel_id: ChannelId,
433            nonce: Nonce,
434            result: OutboxMessageResult,
435        },
436
437        /// Emits when a new inbox message is validated and added to Inbox.
438        InboxMessage {
439            chain_id: ChainId,
440            channel_id: ChannelId,
441            nonce: Nonce,
442        },
443
444        /// Emits when a message response is available for Inbox message.
445        InboxMessageResponse {
446            /// Destination chain ID.
447            chain_id: ChainId,
448            /// Channel Is
449            channel_id: ChannelId,
450            nonce: Nonce,
451        },
452    }
453
454    #[pallet::hooks]
455    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
456        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
457            UpdatedChannels::<T>::take();
458            T::DbWeight::get().reads_writes(0, 1)
459        }
460    }
461
462    #[pallet::validate_unsigned]
463    impl<T: Config> ValidateUnsigned for Pallet<T> {
464        type Call = Call<T>;
465
466        fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
467            match call {
468                // always accept inherent extrinsic
469                Call::update_domain_allowlist { .. } => Ok(()),
470                _ => Err(InvalidTransaction::Call.into()),
471            }
472        }
473
474        /// Validate unsigned call to this module.
475        fn validate_unsigned(
476            _source: TransactionSource,
477            _call: &Self::Call,
478        ) -> TransactionValidity {
479            InvalidTransaction::Call.into()
480        }
481    }
482
483    /// `pallet-messenger` errors
484    #[pallet::error]
485    pub enum Error<T> {
486        /// Emits when the chain is neither consensus not chain.
487        InvalidChain,
488
489        /// Emits when there is no channel for a given Channel ID.
490        MissingChannel,
491
492        /// Emits when the said channel is not in an open state.
493        InvalidChannelState,
494
495        /// Emits when there are no open channels for a chain
496        NoOpenChannel,
497
498        /// Emits when there are not message handler with given endpoint ID.
499        NoMessageHandler,
500
501        /// Emits when the outbox is full for a channel.
502        OutboxFull,
503
504        /// Emits when the message payload is invalid.
505        InvalidMessagePayload,
506
507        /// Emits when the message destination is not valid.
508        InvalidMessageDestination,
509
510        /// Emits when the message verification failed.
511        MessageVerification(VerificationError),
512
513        /// Emits when there is no message available for the given nonce.
514        MissingMessage,
515
516        /// Emits when there is mismatch between the message's weight tag and the message's
517        /// actual processing path
518        WeightTagNotMatch,
519
520        /// Emits when the there is balance overflow.
521        BalanceOverflow,
522
523        /// Emits when the there is balance underflow.
524        BalanceUnderflow,
525
526        /// Invalid allowed chain.
527        InvalidAllowedChain,
528
529        /// Operation not allowed.
530        OperationNotAllowed,
531
532        /// Account is not a Domain owner.
533        NotDomainOwner,
534
535        /// Chain not allowed to open channel
536        ChainNotAllowed,
537
538        /// Not enough balance to do the operation
539        InsufficientBalance,
540
541        /// Failed to hold balance
542        BalanceHold,
543
544        /// Not a channel owner
545        ChannelOwner,
546
547        /// Failed to unlock the balance
548        BalanceUnlock,
549
550        /// Invalid channel reserve fee
551        InvalidChannelReserveFee,
552
553        /// Invalid max outgoing messages
554        InvalidMaxOutgoingMessages,
555
556        /// Message count overflow
557        MessageCountOverflow,
558
559        /// Message count underflow
560        MessageCountUnderflow,
561
562        /// Failed to note transfer in
563        FailedToNoteTransferIn,
564
565        /// Failed to note transfer out
566        FailedToNoteTransferOut,
567    }
568
569    #[pallet::call]
570    impl<T: Config> Pallet<T> {
571        /// A new Channel is initiated with a foreign chain.
572        /// Next Channel ID is used to assign the new channel.
573        /// Channel is set to initiated and do not accept or receive any messages.
574        #[pallet::call_index(0)]
575        #[pallet::weight(T::WeightInfo::initiate_channel())]
576        pub fn initiate_channel(origin: OriginFor<T>, dst_chain_id: ChainId) -> DispatchResult {
577            let owner = ensure_signed(origin)?;
578
579            // reserve channel open fees
580            let hold_id = T::HoldIdentifier::messenger_channel();
581            let amount = T::ChannelReserveFee::get();
582
583            // ensure there is enough free balance to lock
584            ensure!(
585                T::Currency::reducible_balance(&owner, Preservation::Preserve, Fortitude::Polite)
586                    >= amount,
587                Error::<T>::InsufficientBalance
588            );
589            T::Currency::hold(&hold_id, &owner, amount).map_err(|_| Error::<T>::BalanceHold)?;
590
591            // initiate the channel config
592            let channel_open_params = ChannelOpenParamsV1 {
593                max_outgoing_messages: T::MaxOutgoingMessages::get(),
594            };
595            let channel_id = Self::do_init_channel(
596                dst_chain_id,
597                channel_open_params,
598                Some(owner.clone()),
599                true,
600                amount,
601            )?;
602
603            let payload = VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
604                ProtocolMessageRequest::ChannelOpen(ChannelOpenParamsV1 {
605                    max_outgoing_messages: channel_open_params.max_outgoing_messages,
606                }),
607            )));
608
609            // send message to dst_chain
610            Self::new_outbox_message(T::SelfChainId::get(), dst_chain_id, channel_id, payload)?;
611
612            Ok(())
613        }
614
615        /// An open channel is closed with a foreign chain.
616        /// Channel is set to Closed and do not accept or receive any messages.
617        #[pallet::call_index(1)]
618        #[pallet::weight(T::WeightInfo::close_channel())]
619        pub fn close_channel(
620            origin: OriginFor<T>,
621            chain_id: ChainId,
622            channel_id: ChannelId,
623        ) -> DispatchResult {
624            // either owner can close the channel
625            // or sudo can close the channel
626            let close_channel_by = match ensure_signed_or_root(origin)? {
627                Some(owner) => CloseChannelBy::Owner(owner),
628                None => CloseChannelBy::Sudo,
629            };
630            Self::do_close_channel(chain_id, channel_id, close_channel_by)?;
631
632            let payload = VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
633                ProtocolMessageRequest::ChannelClose,
634            )));
635
636            Self::new_outbox_message(T::SelfChainId::get(), chain_id, channel_id, payload)?;
637
638            Ok(())
639        }
640
641        /// Receives an Inbox message that needs to be validated and processed.
642        #[pallet::call_index(2)]
643        #[pallet::weight(T::WeightInfo::relay_message().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag))
644        )]
645        pub fn relay_message(
646            origin: OriginFor<T>,
647            msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
648        ) -> DispatchResult {
649            T::MessengerOrigin::ensure_origin(origin)?;
650            let inbox_msg = Inbox::<T>::take().ok_or(Error::<T>::MissingMessage)?;
651            Self::process_inbox_messages(inbox_msg, msg.weight_tag)?;
652            Ok(())
653        }
654
655        /// Receives a response from the dst_chain for a message in Outbox.
656        #[pallet::call_index(3)]
657        #[pallet::weight(T::WeightInfo::relay_message_response().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag))
658        )]
659        pub fn relay_message_response(
660            origin: OriginFor<T>,
661            msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
662        ) -> DispatchResult {
663            T::MessengerOrigin::ensure_origin(origin)?;
664            let outbox_resp_msg = OutboxResponses::<T>::take().ok_or(Error::<T>::MissingMessage)?;
665            Self::process_outbox_message_responses(outbox_resp_msg, msg.weight_tag)?;
666            Ok(())
667        }
668
669        /// A call to update consensus chain allow list.
670        #[pallet::call_index(4)]
671        #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1))]
672        pub fn update_consensus_chain_allowlist(
673            origin: OriginFor<T>,
674            update: ChainAllowlistUpdate,
675        ) -> DispatchResult {
676            ensure_root(origin)?;
677            ensure!(
678                T::SelfChainId::get().is_consensus_chain(),
679                Error::<T>::OperationNotAllowed
680            );
681
682            ensure!(
683                update.chain_id() != T::SelfChainId::get(),
684                Error::<T>::InvalidAllowedChain
685            );
686
687            if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update {
688                ensure!(
689                    T::DomainRegistration::is_domain_registered(domain_id),
690                    Error::<T>::InvalidChain
691                );
692            }
693
694            ChainAllowlist::<T>::mutate(|list| match update {
695                ChainAllowlistUpdate::Add(chain_id) => list.insert(chain_id),
696                ChainAllowlistUpdate::Remove(chain_id) => list.remove(&chain_id),
697            });
698            Ok(())
699        }
700
701        /// A call to initiate chain allowlist update on domains
702        #[pallet::call_index(5)]
703        #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1))]
704        pub fn initiate_domain_update_chain_allowlist(
705            origin: OriginFor<T>,
706            domain_id: DomainId,
707            update: ChainAllowlistUpdate,
708        ) -> DispatchResult {
709            let domain_owner = ensure_signed(origin)?;
710            ensure!(
711                T::DomainOwner::is_domain_owner(domain_id, domain_owner),
712                Error::<T>::NotDomainOwner
713            );
714
715            ensure!(
716                T::SelfChainId::get().is_consensus_chain(),
717                Error::<T>::OperationNotAllowed
718            );
719
720            if let Some(dst_domain_id) = update.chain_id().maybe_domain_chain() {
721                ensure!(dst_domain_id != domain_id, Error::<T>::InvalidAllowedChain);
722            }
723
724            if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update {
725                ensure!(
726                    T::DomainRegistration::is_domain_registered(domain_id),
727                    Error::<T>::InvalidChain
728                );
729            }
730
731            DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_domain_updates| {
732                let mut domain_updates = maybe_domain_updates.take().unwrap_or_default();
733                match update {
734                    ChainAllowlistUpdate::Add(chain_id) => {
735                        domain_updates.remove_chains.remove(&chain_id);
736                        domain_updates.allow_chains.insert(chain_id);
737                    }
738                    ChainAllowlistUpdate::Remove(chain_id) => {
739                        domain_updates.allow_chains.remove(&chain_id);
740                        domain_updates.remove_chains.insert(chain_id);
741                    }
742                }
743
744                *maybe_domain_updates = Some(domain_updates)
745            });
746            Ok(())
747        }
748
749        /// An inherent call to update allowlist for domain.
750        #[pallet::call_index(6)]
751        #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Mandatory))]
752        pub fn update_domain_allowlist(
753            origin: OriginFor<T>,
754            updates: DomainAllowlistUpdates,
755        ) -> DispatchResult {
756            ensure_none(origin)?;
757            ensure!(
758                !T::SelfChainId::get().is_consensus_chain(),
759                Error::<T>::OperationNotAllowed
760            );
761
762            let DomainAllowlistUpdates {
763                allow_chains,
764                remove_chains,
765            } = updates;
766
767            ChainAllowlist::<T>::mutate(|list| {
768                // remove chains from set
769                remove_chains.into_iter().for_each(|chain_id| {
770                    list.remove(&chain_id);
771                });
772
773                // add new chains
774                allow_chains.into_iter().for_each(|chain_id| {
775                    list.insert(chain_id);
776                });
777            });
778
779            Ok(())
780        }
781    }
782
783    #[pallet::inherent]
784    impl<T: Config> ProvideInherent for Pallet<T> {
785        type Call = Call<T>;
786        type Error = InherentError;
787        const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
788
789        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
790            let inherent_data = data
791                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
792                .expect("Messenger inherent data not correctly encoded")
793                .expect("Messenger inherent data must be provided");
794
795            inherent_data
796                .maybe_updates
797                .map(|updates| Call::update_domain_allowlist { updates })
798        }
799
800        fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
801            let inherent_data = data
802                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
803                .expect("Messenger inherent data not correctly encoded")
804                .expect("Messenger inherent data must be provided");
805
806            Ok(if inherent_data.maybe_updates.is_none() {
807                None
808            } else {
809                Some(InherentError::MissingAllowlistUpdates)
810            })
811        }
812
813        fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
814            let inherent_data = data
815                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
816                .expect("Messenger inherent data not correctly encoded")
817                .expect("Messenger inherent data must be provided");
818
819            if let Some(provided_updates) = inherent_data.maybe_updates {
820                if let Call::update_domain_allowlist { updates } = call
821                    && updates != &provided_updates
822                {
823                    return Err(InherentError::IncorrectAllowlistUpdates);
824                }
825            } else {
826                return Err(InherentError::MissingAllowlistUpdates);
827            }
828
829            Ok(())
830        }
831
832        fn is_inherent(call: &Self::Call) -> bool {
833            matches!(call, Call::update_domain_allowlist { .. })
834        }
835    }
836
837    impl<T: Config> Sender<T::AccountId> for Pallet<T> {
838        type MessageId = MessageId;
839
840        fn send_message(
841            sender: &T::AccountId,
842            dst_chain_id: ChainId,
843            req: EndpointRequest,
844        ) -> Result<Self::MessageId, DispatchError> {
845            let allowed_chains = ChainAllowlist::<T>::get();
846            ensure!(
847                allowed_chains.contains(&dst_chain_id),
848                Error::<T>::ChainNotAllowed
849            );
850
851            let channel_id =
852                Self::get_open_channel_for_chain(dst_chain_id).ok_or(Error::<T>::NoOpenChannel)?;
853
854            let src_endpoint = req.src_endpoint.clone();
855
856            let message_id = {
857                // collect the fees from the sender
858                let collected_fee = Self::collect_fees_for_message_v1(sender, &src_endpoint)?;
859                let src_chain_fee = collected_fee.src_chain_fee;
860                let dst_chain_fee = collected_fee.dst_chain_fee;
861                let nonce = Self::new_outbox_message(
862                    T::SelfChainId::get(),
863                    dst_chain_id,
864                    channel_id,
865                    VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(
866                        EndpointRequestWithCollectedFee { req, collected_fee },
867                    ))),
868                )?;
869
870                // store src_chain, this chain, fee to OutboxFee
871                let message_id = (channel_id, nonce);
872                Self::store_outbox_fee(dst_chain_id, message_id, src_chain_fee, dst_chain_fee)?;
873                message_id
874            };
875
876            Ok(message_id)
877        }
878
879        /// Only used in benchmark to prepare for a upcoming `send_message` call to
880        /// ensure it will succeed.
881        #[cfg(feature = "runtime-benchmarks")]
882        fn unchecked_open_channel(dst_chain_id: ChainId) -> Result<(), DispatchError> {
883            let init_params = ChannelOpenParamsV1 {
884                max_outgoing_messages: 100,
885            };
886            ChainAllowlist::<T>::mutate(|list| list.insert(dst_chain_id));
887            let channel_id =
888                Self::do_init_channel(dst_chain_id, init_params, None, true, Zero::zero())?;
889            Self::do_open_channel(dst_chain_id, channel_id)?;
890            Ok(())
891        }
892    }
893
894    impl<T: Config> Pallet<T> {
895        // Get the weight according the given weight tag
896        fn message_weight(weight_tag: &MessageWeightTag) -> Weight {
897            match weight_tag {
898                MessageWeightTag::ProtocolChannelOpen => T::WeightInfo::do_open_channel(),
899                MessageWeightTag::ProtocolChannelClose => T::WeightInfo::do_close_channel(),
900                MessageWeightTag::EndpointRequest(endpoint) => {
901                    T::get_endpoint_handler(endpoint)
902                        .map(|endpoint_handler| endpoint_handler.message_weight())
903                        // If there is no endpoint handler the request won't be handled thus return zero weight
904                        .unwrap_or(Weight::zero())
905                }
906                MessageWeightTag::EndpointResponse(endpoint) => {
907                    T::get_endpoint_handler(endpoint)
908                        .map(|endpoint_handler| endpoint_handler.message_response_weight())
909                        // If there is no endpoint handler the request won't be handled thus return zero weight
910                        .unwrap_or(Weight::zero())
911                }
912                MessageWeightTag::None => Weight::zero(),
913            }
914        }
915
916        /// Returns the last open channel for a given chain.
917        pub fn get_open_channel_for_chain(dst_chain_id: ChainId) -> Option<ChannelId> {
918            let mut next_channel_id = NextChannelId::<T>::get(dst_chain_id);
919
920            // loop through channels in descending order until open channel is found.
921            // we always prefer latest opened channel.
922            while let Some(channel_id) = next_channel_id.checked_sub(ChannelId::one()) {
923                let message_count = OutboxMessageCount::<T>::get((dst_chain_id, channel_id));
924                if let Some(channel) = Channels::<T>::get(dst_chain_id, channel_id)
925                    && channel.state == ChannelState::Open
926                    && message_count < channel.max_outgoing_messages
927                {
928                    return Some(channel_id);
929                }
930
931                next_channel_id = channel_id
932            }
933
934            None
935        }
936
937        /// Opens an initiated channel.
938        pub(crate) fn do_open_channel(chain_id: ChainId, channel_id: ChannelId) -> DispatchResult {
939            Channels::<T>::try_mutate(chain_id, channel_id, |maybe_channel| -> DispatchResult {
940                let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
941
942                ensure!(
943                    channel.state == ChannelState::Initiated,
944                    Error::<T>::InvalidChannelState
945                );
946
947                channel.state = ChannelState::Open;
948                Ok(())
949            })?;
950
951            Self::deposit_event(Event::ChannelOpen {
952                chain_id,
953                channel_id,
954            });
955
956            Ok(())
957        }
958
959        pub(crate) fn do_close_channel(
960            chain_id: ChainId,
961            channel_id: ChannelId,
962            close_channel_by: CloseChannelBy<T::AccountId>,
963        ) -> DispatchResult {
964            Channels::<T>::try_mutate(chain_id, channel_id, |maybe_channel| -> DispatchResult {
965                let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
966
967                ensure!(
968                    channel.state != ChannelState::Closed,
969                    Error::<T>::InvalidChannelState
970                );
971
972                if let CloseChannelBy::Owner(owner) = close_channel_by {
973                    ensure!(channel.maybe_owner == Some(owner), Error::<T>::ChannelOwner);
974                }
975
976                if let Some(owner) = &channel.maybe_owner {
977                    let hold_id = T::HoldIdentifier::messenger_channel();
978                    let locked_amount = channel.channel_reserve_fee;
979                    let (amount_to_release, maybe_amount_to_burn) = {
980                        if channel.state == ChannelState::Open {
981                            (locked_amount, None)
982                        } else {
983                            let protocol_fee = T::ChannelInitReservePortion::get() * locked_amount;
984                            let release_amount = locked_amount.saturating_sub(protocol_fee);
985                            (release_amount, Some(protocol_fee))
986                        }
987                    };
988
989                    with_storage_layer(|| {
990                        if let Some(protocol_fee) = maybe_amount_to_burn {
991                            T::Currency::burn_held(
992                                &hold_id,
993                                owner,
994                                protocol_fee,
995                                Precision::Exact,
996                                Fortitude::Force,
997                            )?;
998                            T::OnXDMRewards::on_chain_protocol_fees(chain_id, protocol_fee);
999                        }
1000
1001                        T::Currency::release(&hold_id, owner, amount_to_release, Precision::Exact)
1002                            .map_err(|_| Error::<T>::BalanceUnlock)?;
1003
1004                        Ok::<(), DispatchError>(())
1005                    })?;
1006                }
1007
1008                channel.state = ChannelState::Closed;
1009                Ok(())
1010            })?;
1011
1012            Self::deposit_event(Event::ChannelClosed {
1013                chain_id,
1014                channel_id,
1015            });
1016
1017            Ok(())
1018        }
1019
1020        pub(crate) fn do_init_channel(
1021            dst_chain_id: ChainId,
1022            init_params: ChannelOpenParamsV1,
1023            maybe_owner: Option<T::AccountId>,
1024            check_allowlist: bool,
1025            channel_reserve_fee: BalanceOf<T>,
1026        ) -> Result<ChannelId, DispatchError> {
1027            ensure!(
1028                T::SelfChainId::get() != dst_chain_id,
1029                Error::<T>::InvalidChain,
1030            );
1031
1032            // ensure max outgoing messages is at least 1
1033            ensure!(
1034                init_params.max_outgoing_messages >= 1u32,
1035                Error::<T>::InvalidMaxOutgoingMessages
1036            );
1037
1038            // If the channel owner is in this chain then the channel reserve fee
1039            // must not be empty
1040            ensure!(
1041                maybe_owner.is_none() || !channel_reserve_fee.is_zero(),
1042                Error::<T>::InvalidChannelReserveFee,
1043            );
1044
1045            if check_allowlist {
1046                let chain_allowlist = ChainAllowlist::<T>::get();
1047                ensure!(
1048                    chain_allowlist.contains(&dst_chain_id),
1049                    Error::<T>::ChainNotAllowed
1050                );
1051            }
1052
1053            let channel_id = NextChannelId::<T>::get(dst_chain_id);
1054            let next_channel_id = channel_id
1055                .checked_add(U256::one())
1056                .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?;
1057
1058            Channels::<T>::insert(
1059                dst_chain_id,
1060                channel_id,
1061                Channel {
1062                    channel_id,
1063                    state: ChannelState::Initiated,
1064                    next_inbox_nonce: Default::default(),
1065                    next_outbox_nonce: Default::default(),
1066                    latest_response_received_message_nonce: Default::default(),
1067                    max_outgoing_messages: init_params.max_outgoing_messages,
1068                    maybe_owner,
1069                    channel_reserve_fee,
1070                },
1071            );
1072
1073            NextChannelId::<T>::insert(dst_chain_id, next_channel_id);
1074            Self::deposit_event(Event::ChannelInitiated {
1075                chain_id: dst_chain_id,
1076                channel_id,
1077            });
1078            Ok(channel_id)
1079        }
1080
1081        pub fn validate_relay_message(
1082            xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1083            consensus_state_root: StateRootOf<T>,
1084        ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1085            let (next_nonce, maybe_channel) =
1086                match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1087                    None => {
1088                        // if there is no channel config, this must the Channel open request.
1089                        // so nonce is 0
1090                        log::debug!(
1091                            "Initiating new channel: {:?} to chain: {:?}",
1092                            xdm.channel_id,
1093                            xdm.src_chain_id
1094                        );
1095                        (Nonce::zero(), None)
1096                    }
1097                    Some(channel) => {
1098                        log::debug!(
1099                            "Message to channel: {:?} from chain: {:?}",
1100                            xdm.channel_id,
1101                            xdm.src_chain_id
1102                        );
1103                        (channel.next_inbox_nonce, Some(channel))
1104                    }
1105                };
1106
1107            // derive the key as stored on the src_chain.
1108            let key = StorageKey(
1109                T::StorageKeys::outbox_storage_key(
1110                    xdm.src_chain_id,
1111                    (T::SelfChainId::get(), xdm.channel_id, xdm.nonce),
1112                )
1113                .ok_or(UnknownTransaction::CannotLookup)?,
1114            );
1115
1116            // verify and decode message
1117            let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1118
1119            let is_valid_call = match &msg.payload {
1120                VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(req))) => {
1121                    match req {
1122                        // channel open should ensure there is no Channel present already
1123                        ProtocolMessageRequest::ChannelOpen(_) => maybe_channel.is_none(),
1124                        // we allow channel close only if it is init or open state
1125                        ProtocolMessageRequest::ChannelClose => {
1126                            if let Some(ref channel) = maybe_channel {
1127                                !(channel.state == ChannelState::Closed)
1128                            } else {
1129                                false
1130                            }
1131                        }
1132                    }
1133                }
1134                // endpoint request messages are only allowed when
1135                // channel is open, or
1136                // channel is closed. Channel can be closed by dst_chain simultaneously
1137                // while src_chain already sent a message. We allow the message but return an
1138                // error in the response so that src_chain can revert any necessary actions
1139                VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(_))) => {
1140                    if let Some(ref channel) = maybe_channel {
1141                        !(channel.state == ChannelState::Initiated)
1142                    } else {
1143                        false
1144                    }
1145                }
1146                // any other message variants are not allowed
1147                _ => false,
1148            };
1149
1150            if !is_valid_call {
1151                log::error!("Unexpected XDM message: {msg:?}",);
1152                return Err(InvalidTransaction::Call.into());
1153            }
1154
1155            // Reject stale message
1156            if msg.nonce.cmp(&next_nonce) == Ordering::Less {
1157                return Err(InvalidTransaction::Stale.into());
1158            }
1159
1160            let validated_relay_msg = ValidatedRelayMessage {
1161                message: msg,
1162                should_init_channel: maybe_channel.is_none(),
1163                next_nonce,
1164            };
1165
1166            Ok(validated_relay_msg)
1167        }
1168
1169        pub(crate) fn pre_dispatch_relay_message(
1170            msg: Message<BalanceOf<T>>,
1171            should_init_channel: bool,
1172        ) -> Result<(), TransactionValidityError> {
1173            if should_init_channel {
1174                if let VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
1175                    ProtocolMessageRequest::ChannelOpen(params),
1176                ))) = msg.payload
1177                {
1178                    // channel is being opened without an owner since this is a relay message
1179                    // from other chain
1180                    // we do not check the allowlist to finish the end to end flow
1181                    Self::do_init_channel(msg.src_chain_id, params, None, false, Zero::zero())
1182                        .map_err(|err| {
1183                            log::error!(
1184                                "Error initiating channel: {:?} with chain: {:?}: {:?}",
1185                                msg.channel_id,
1186                                msg.src_chain_id,
1187                                err
1188                            );
1189                            InvalidTransaction::Call
1190                        })?;
1191                } else {
1192                    log::error!("Unexpected call instead of channel open request: {msg:?}");
1193                    return Err(InvalidTransaction::Call.into());
1194                }
1195            }
1196
1197            let (dst_chain_id, channel_id, nonce) = (msg.src_chain_id, msg.channel_id, msg.nonce);
1198            // future nonce check is already validated by the extension
1199            // it is safe to increment the next nonce here before processing.
1200            Channels::<T>::mutate(
1201                dst_chain_id,
1202                channel_id,
1203                |maybe_channel| -> sp_runtime::DispatchResult {
1204                    let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1205                    channel.next_inbox_nonce = nonce
1206                        .checked_add(Nonce::one())
1207                        .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?;
1208                    Ok(())
1209                },
1210            )
1211                .map_err(|err| {
1212                    log::error!(
1213                        "Failed to increment the next relay message nonce for Chain[{dst_chain_id:?}] with Channel[{channel_id:?}]: {err:?}"
1214                    );
1215                    InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1216                })?;
1217
1218            Self::deposit_event(Event::InboxMessage {
1219                chain_id: msg.src_chain_id,
1220                channel_id: msg.channel_id,
1221                nonce: msg.nonce,
1222            });
1223            Inbox::<T>::put(msg);
1224            Ok(())
1225        }
1226
1227        pub fn validate_relay_message_response(
1228            xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1229            consensus_state_root: StateRootOf<T>,
1230        ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1231            // channel should be open and message should be present in outbox
1232            let next_nonce =
1233                match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1234                    // unknown channel. return
1235                    None => {
1236                        log::error!("Unexpected inbox message response: {xdm:?}",);
1237                        return Err(InvalidTransaction::Call.into());
1238                    }
1239                    Some(channel) => match channel.latest_response_received_message_nonce {
1240                        None => Nonce::zero(),
1241                        Some(last_nonce) => last_nonce.checked_add(Nonce::one()).ok_or(
1242                            InvalidTransaction::Custom(
1243                                crate::verification_errors::XDM_NONCE_OVERFLOW,
1244                            ),
1245                        )?,
1246                    },
1247                };
1248
1249            // derive the key as stored on the src_chain.
1250            let key = StorageKey(
1251                T::StorageKeys::inbox_responses_storage_key(
1252                    xdm.src_chain_id,
1253                    (T::SelfChainId::get(), xdm.channel_id, xdm.nonce),
1254                )
1255                .ok_or(UnknownTransaction::CannotLookup)?,
1256            );
1257
1258            // verify, decode, and store the message
1259            let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1260
1261            // Reject stale message
1262            if msg.nonce.cmp(&next_nonce) == Ordering::Less {
1263                return Err(InvalidTransaction::Stale.into());
1264            }
1265
1266            let validated_relay_msg = ValidatedRelayMessage {
1267                message: msg,
1268                next_nonce,
1269                // not applicable in relay message response, default should be fine here
1270                should_init_channel: false,
1271            };
1272
1273            Ok(validated_relay_msg)
1274        }
1275
1276        pub(crate) fn pre_dispatch_relay_message_response(
1277            msg: Message<BalanceOf<T>>,
1278        ) -> Result<(), TransactionValidityError> {
1279            // future nonce check is already validated by the extension
1280            // it is safe to increment the next nonce here before processing.
1281            let (dst_chain_id, channel_id, nonce) = (msg.src_chain_id, msg.channel_id, msg.nonce);
1282            Channels::<T>::mutate(
1283                dst_chain_id,
1284                channel_id,
1285                |maybe_channel| -> sp_runtime::DispatchResult {
1286                    let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1287                    channel.latest_response_received_message_nonce = Some(nonce);
1288                    Ok(())
1289                },
1290            )
1291                .map_err(|err| { log::error!(
1292                    "Failed to increment the next relay message response nonce for Chain[{dst_chain_id:?}] with Channel[{channel_id:?}]: {err:?}",
1293                );
1294                    InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1295                })?;
1296
1297            Self::deposit_event(Event::OutboxMessageResponse {
1298                chain_id: msg.src_chain_id,
1299                channel_id: msg.channel_id,
1300                nonce: msg.nonce,
1301            });
1302
1303            OutboxResponses::<T>::put(msg);
1304            Ok(())
1305        }
1306
1307        pub(crate) fn do_verify_xdm(
1308            next_nonce: Nonce,
1309            storage_key: StorageKey,
1310            consensus_state_root: StateRootOf<T>,
1311            xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1312        ) -> Result<Message<BalanceOf<T>>, TransactionValidityError> {
1313            // channel should be either already be created or match the next channelId for chain.
1314            let next_channel_id = NextChannelId::<T>::get(xdm.src_chain_id);
1315            ensure!(
1316                xdm.channel_id <= next_channel_id,
1317                InvalidTransaction::Custom(crate::verification_errors::INVALID_CHANNEL)
1318            );
1319
1320            // verify nonce
1321            // nonce should be either be next or in future.
1322            ensure!(
1323                xdm.nonce >= next_nonce,
1324                InvalidTransaction::Custom(crate::verification_errors::INVALID_NONCE)
1325            );
1326
1327            // if the message is from domain, verify domain confirmation proof
1328            let state_root = if let Some(domain_proof) = xdm.proof.domain_proof().clone()
1329                && let Some(domain_id) = xdm.src_chain_id.maybe_domain_chain()
1330            {
1331                let confirmed_domain_block_storage_key =
1332                    T::StorageKeys::confirmed_domain_block_storage_key(domain_id)
1333                        .ok_or(UnknownTransaction::CannotLookup)?;
1334
1335                *StorageProofVerifier::<T::Hashing>::get_decoded_value::<
1336                    sp_domains::execution_receipt::ExecutionReceipt<
1337                        BlockNumberFor<T>,
1338                        T::Hash,
1339                        BlockNumberFor<T>,
1340                        T::Hash,
1341                        BalanceOf<T>,
1342                    >,
1343                >(
1344                    &consensus_state_root,
1345                    domain_proof,
1346                    StorageKey(confirmed_domain_block_storage_key),
1347                )
1348                .map_err(|err| {
1349                    log::error!(
1350                        "Failed to verify storage proof for confirmed Domain block: {err:?}",
1351                    );
1352                    TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1353                })?
1354                .final_state_root()
1355            } else {
1356                consensus_state_root
1357            };
1358
1359            // verify and decode the message
1360            let msg =
1361                StorageProofVerifier::<T::Hashing>::get_decoded_value::<Message<BalanceOf<T>>>(
1362                    &state_root,
1363                    xdm.proof.message_proof(),
1364                    storage_key,
1365                )
1366                .map_err(|err| {
1367                    log::error!("Failed to verify storage proof for message: {err:?}");
1368                    TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1369                })?;
1370
1371            Ok(msg)
1372        }
1373
1374        pub fn outbox_storage_key(message_key: MessageKey) -> Vec<u8> {
1375            Outbox::<T>::hashed_key_for(message_key)
1376        }
1377
1378        pub fn inbox_response_storage_key(message_key: MessageKey) -> Vec<u8> {
1379            InboxResponses::<T>::hashed_key_for(message_key)
1380        }
1381
1382        pub fn channel_storage_key(chain_id: ChainId, channel_id: ChannelId) -> Vec<u8> {
1383            Channels::<T>::hashed_key_for(chain_id, channel_id)
1384        }
1385
1386        pub fn domain_chains_allowlist_update(
1387            domain_id: DomainId,
1388        ) -> Option<DomainAllowlistUpdates> {
1389            DomainChainAllowlistUpdate::<T>::get(domain_id).filter(|updates| !updates.is_empty())
1390        }
1391
1392        pub fn domain_allow_list_update_storage_key(domain_id: DomainId) -> Vec<u8> {
1393            DomainChainAllowlistUpdate::<T>::hashed_key_for(domain_id)
1394        }
1395
1396        pub fn updated_channels() -> BTreeSet<(ChainId, ChannelId)> {
1397            UpdatedChannels::<T>::get()
1398        }
1399
1400        pub fn open_channels() -> BTreeSet<(ChainId, ChannelId)> {
1401            Channels::<T>::iter_keys().collect()
1402        }
1403
1404        pub fn channels_and_states() -> Vec<(ChainId, ChannelId, ChannelStateWithNonce)> {
1405            let keys: Vec<(ChainId, ChannelId)> = Channels::<T>::iter_keys().collect();
1406            keys.into_iter()
1407                .filter_map(|(chain_id, channel_id)| {
1408                    Channels::<T>::get(chain_id, channel_id).map(|channel| {
1409                        let state = channel.state;
1410                        let state_with_nonce = match state {
1411                            ChannelState::Initiated => ChannelStateWithNonce::Initiated,
1412                            ChannelState::Open => ChannelStateWithNonce::Open,
1413                            ChannelState::Closed => ChannelStateWithNonce::Closed {
1414                                next_outbox_nonce: channel.next_outbox_nonce,
1415                                next_inbox_nonce: channel.next_inbox_nonce,
1416                            },
1417                        };
1418
1419                        (chain_id, channel_id, state_with_nonce)
1420                    })
1421                })
1422                .collect()
1423        }
1424
1425        pub fn channel_nonce(chain_id: ChainId, channel_id: ChannelId) -> Option<ChannelNonce> {
1426            Channels::<T>::get(chain_id, channel_id).map(|channel| {
1427                let last_inbox_nonce = channel.next_inbox_nonce.checked_sub(U256::one());
1428                ChannelNonce {
1429                    relay_msg_nonce: last_inbox_nonce,
1430                    relay_response_msg_nonce: channel.latest_response_received_message_nonce,
1431                }
1432            })
1433        }
1434
1435        pub fn store_inbox_fee(
1436            src_chain_id: ChainId,
1437            message_id: MessageId,
1438            inbox_fees: BalanceOf<T>,
1439        ) -> DispatchResult {
1440            InboxFeesOnHold::<T>::mutate(|inbox_fees_on_hold| {
1441                *inbox_fees_on_hold = inbox_fees_on_hold
1442                    .checked_add(&inbox_fees)
1443                    .ok_or(Error::<T>::BalanceOverflow)?;
1444
1445                // If the `imbalance` is dropped without consuming it will reduce the total issuance by
1446                // the same amount as we issued here, thus we need to manually `mem::forget` it.
1447                let imbalance = T::Currency::issue(inbox_fees);
1448                core::mem::forget(imbalance);
1449
1450                Ok::<(), Error<T>>(())
1451            })?;
1452
1453            InboxFee::<T>::insert((src_chain_id, message_id), inbox_fees);
1454
1455            // Note `dst_chain_fee` as transfer in
1456            if !T::NoteChainTransfer::note_transfer_in(inbox_fees, src_chain_id) {
1457                return Err(Error::<T>::FailedToNoteTransferIn.into());
1458            }
1459
1460            Ok(())
1461        }
1462
1463        pub fn store_outbox_fee(
1464            dst_chain_id: ChainId,
1465            message_id: MessageId,
1466            outbox_fees: BalanceOf<T>,
1467            inbox_fees: BalanceOf<T>,
1468        ) -> DispatchResult {
1469            OutboxFeesOnHold::<T>::mutate(|outbox_fees_on_hold| {
1470                *outbox_fees_on_hold = outbox_fees_on_hold
1471                    .checked_add(&outbox_fees)
1472                    .ok_or(Error::<T>::BalanceOverflow)?;
1473
1474                // If the `imbalance` is dropped without consuming it will reduce the total issuance by
1475                // the same amount as we issued here, thus we need to manually `mem::forget` it.
1476                let imbalance = T::Currency::issue(outbox_fees);
1477                core::mem::forget(imbalance);
1478
1479                Ok::<(), Error<T>>(())
1480            })?;
1481
1482            OutboxFee::<T>::insert((dst_chain_id, message_id), outbox_fees);
1483
1484            // Note `dst_chain_fee` as transfer out
1485            if !T::NoteChainTransfer::note_transfer_out(inbox_fees, dst_chain_id) {
1486                return Err(Error::<T>::FailedToNoteTransferOut.into());
1487            }
1488
1489            Ok(())
1490        }
1491    }
1492}
1493
1494impl<T> Pallet<T>
1495where
1496    T: Config + CreateUnsigned<Call<T>>,
1497{
1498    pub fn outbox_message_unsigned(
1499        msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1500    ) -> Option<T::Extrinsic> {
1501        let call = Call::relay_message { msg };
1502        Some(T::create_unsigned(call.into()))
1503    }
1504
1505    pub fn inbox_response_message_unsigned(
1506        msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1507    ) -> Option<T::Extrinsic> {
1508        let call = Call::relay_message_response { msg };
1509        Some(T::create_unsigned(call.into()))
1510    }
1511
1512    /// Returns the first outbox message nonce that should be relayed to the dst_chain.
1513    pub fn first_outbox_message_nonce_to_relay(
1514        dst_chain_id: ChainId,
1515        channel_id: ChannelId,
1516        from_nonce: Nonce,
1517    ) -> Option<Nonce> {
1518        Self::first_relay_message(
1519            dst_chain_id,
1520            channel_id,
1521            from_nonce,
1522            Outbox::<T>::contains_key,
1523        )
1524    }
1525
1526    /// Returns the first inbox response message nonce that should be relayed to the dst_chain.
1527    pub fn first_inbox_message_response_nonce_to_relay(
1528        dst_chain_id: ChainId,
1529        channel_id: ChannelId,
1530        from_nonce: Nonce,
1531    ) -> Option<Nonce> {
1532        Self::first_relay_message(
1533            dst_chain_id,
1534            channel_id,
1535            from_nonce,
1536            InboxResponses::<T>::contains_key,
1537        )
1538    }
1539
1540    fn first_relay_message<Check>(
1541        dst_chain_id: ChainId,
1542        channel_id: ChannelId,
1543        from_nonce: Nonce,
1544        check: Check,
1545    ) -> Option<Nonce>
1546    where
1547        Check: Fn((ChainId, ChannelId, Nonce)) -> bool,
1548    {
1549        let mut nonce = from_nonce;
1550        let to_nonce = from_nonce.saturating_add(MAX_FUTURE_ALLOWED_NONCES.into());
1551        while nonce <= to_nonce {
1552            if check((dst_chain_id, channel_id, nonce)) {
1553                return Some(nonce);
1554            }
1555
1556            nonce = nonce.saturating_add(Nonce::one())
1557        }
1558
1559        None
1560    }
1561}
1562
1563impl<T: Config> sp_domains::DomainBundleSubmitted for Pallet<T> {
1564    fn domain_bundle_submitted(domain_id: DomainId) {
1565        // NOTE: clear the updates leave an empty value but does not delete the value for the
1566        // domain completely because in the invalid extrinsic root fraud proof the prover need
1567        // to generate a proof-of-empty-value for the domain.
1568        DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_updates| {
1569            if let Some(updates) = maybe_updates {
1570                updates.clear();
1571            }
1572        });
1573    }
1574}
1575
1576impl<T: Config> sp_domains::OnDomainInstantiated for Pallet<T> {
1577    fn on_domain_instantiated(domain_id: DomainId) {
1578        DomainChainAllowlistUpdate::<T>::insert(domain_id, DomainAllowlistUpdates::default());
1579    }
1580}