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