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