Skip to main content

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