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