1#![cfg_attr(not(feature = "std"), no_std)]
19#![forbid(unsafe_code)]
20#![warn(rust_2018_idioms)]
21#![feature(variant_count, if_let_guard)]
22
23#[cfg(feature = "runtime-benchmarks")]
24mod benchmarking;
25pub mod extensions;
26mod fees;
27mod messages;
28pub mod migrations;
29#[cfg(test)]
30mod mock;
31#[cfg(test)]
32mod tests;
33pub mod weights;
34
35#[cfg(not(feature = "std"))]
36extern crate alloc;
37
38use frame_support::__private::RuntimeDebug;
39use frame_support::pallet_prelude::{EnsureOrigin, MaxEncodedLen, StorageVersion};
40use frame_support::traits::fungible::{Inspect, InspectHold};
41use frame_system::pallet_prelude::BlockNumberFor;
42pub use pallet::*;
43use parity_scale_codec::{Decode, Encode};
44use scale_info::TypeInfo;
45use sp_core::U256;
46use sp_domains::{DomainAllowlistUpdates, DomainId};
47use sp_messenger::MAX_FUTURE_ALLOWED_NONCES;
48use sp_messenger::messages::{
49 ChainId, Channel, ChannelId, ChannelState, CrossDomainMessage, Message, Nonce,
50};
51use sp_runtime::DispatchError;
52use sp_runtime::traits::Hash;
53use subspace_runtime_primitives::CreateUnsigned;
54pub use weights::WeightInfo;
55
56const XDM_TRANSACTION_LONGEVITY: u64 = 10;
59
60pub(crate) mod verification_errors {
62 pub(crate) const INVALID_NONCE: u8 = 201;
66 pub(crate) const XDM_NONCE_OVERFLOW: u8 = 202;
68 pub(crate) const INVALID_CHANNEL: u8 = 203;
70 pub(crate) const IN_FUTURE_NONCE: u8 = 204;
71 pub(crate) const NEXT_NONCE_UPDATE: u8 = 205;
73}
74
75#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
76pub enum OutboxMessageResult {
77 Ok,
79 Err(DispatchError),
81}
82
83#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
85pub enum RawOrigin {
86 ValidatedUnsigned,
87}
88
89pub struct EnsureMessengerOrigin;
91impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureMessengerOrigin {
92 type Success = ();
93
94 fn try_origin(o: O) -> Result<Self::Success, O> {
95 o.into().map(|o| match o {
96 RawOrigin::ValidatedUnsigned => (),
97 })
98 }
99
100 #[cfg(feature = "runtime-benchmarks")]
101 fn try_successful_origin() -> Result<O, ()> {
102 Ok(O::from(RawOrigin::ValidatedUnsigned))
103 }
104}
105
106pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>::Output;
107pub(crate) type BalanceOf<T> =
108 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
109pub(crate) type FungibleHoldId<T> =
110 <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
111
112#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
114pub enum ChainAllowlistUpdate {
115 Add(ChainId),
116 Remove(ChainId),
117}
118
119impl ChainAllowlistUpdate {
120 fn chain_id(&self) -> ChainId {
121 match self {
122 ChainAllowlistUpdate::Add(chain_id) => *chain_id,
123 ChainAllowlistUpdate::Remove(chain_id) => *chain_id,
124 }
125 }
126}
127
128#[derive(Debug, Encode, Decode, TypeInfo)]
129pub struct ValidatedRelayMessage<T: Config> {
130 pub message: Message<BalanceOf<T>>,
131 pub should_init_channel: bool,
132 pub next_nonce: Nonce,
133}
134
135#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
137pub(crate) enum CloseChannelBy<AccountId> {
138 Owner(AccountId),
139 Sudo,
140}
141
142pub trait HoldIdentifier<T: Config> {
144 fn messenger_channel() -> FungibleHoldId<T>;
145}
146
147const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
149
150#[frame_support::pallet]
151mod pallet {
152 pub use crate::extensions::weights::WeightInfo as ExtensionWeightInfo;
153 use crate::weights::WeightInfo;
154 use crate::{
155 BalanceOf, ChainAllowlistUpdate, Channel, ChannelId, ChannelState, CloseChannelBy,
156 HoldIdentifier, Nonce, OutboxMessageResult, RawOrigin, STORAGE_VERSION, StateRootOf, U256,
157 ValidatedRelayMessage,
158 };
159 #[cfg(not(feature = "std"))]
160 use alloc::boxed::Box;
161 #[cfg(not(feature = "std"))]
162 use alloc::collections::BTreeSet;
163 #[cfg(not(feature = "std"))]
164 use alloc::vec::Vec;
165 use core::cmp::Ordering;
166 use frame_support::ensure;
167 use frame_support::pallet_prelude::*;
168 use frame_support::storage::with_storage_layer;
169 use frame_support::traits::fungible::{Balanced, Inspect, InspectHold, Mutate, MutateHold};
170 use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
171 use frame_support::weights::WeightToFee;
172 use frame_system::pallet_prelude::*;
173 use sp_core::storage::StorageKey;
174 use sp_domains::proof_provider_and_verifier::{StorageProofVerifier, VerificationError};
175 use sp_domains::{DomainAllowlistUpdates, DomainId, DomainOwner};
176 use sp_messenger::endpoint::{
177 Endpoint, EndpointHandler, EndpointRequest, EndpointRequestWithCollectedFee, Sender,
178 };
179 use sp_messenger::messages::{
180 ChainId, ChannelOpenParamsV1, ChannelStateWithNonce, CrossDomainMessage, Message,
181 MessageId, MessageKey, MessageWeightTag, PayloadV1, ProtocolMessageRequest,
182 RequestResponse, VersionedPayload,
183 };
184 use sp_messenger::{
185 ChannelNonce, DomainRegistration, INHERENT_IDENTIFIER, InherentError, InherentType,
186 NoteChainTransfer, OnXDMRewards, StorageKeys,
187 };
188 use sp_runtime::traits::Zero;
189 use sp_runtime::{ArithmeticError, Perbill, Saturating};
190 use sp_subspace_mmr::MmrProofVerifier;
191 #[cfg(feature = "std")]
192 use std::collections::BTreeSet;
193
194 #[pallet::config]
195 pub trait Config: frame_system::Config {
196 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
197 type SelfChainId: Get<ChainId>;
199 fn get_endpoint_handler(endpoint: &Endpoint)
201 -> Option<Box<dyn EndpointHandler<MessageId>>>;
202 type Currency: Mutate<Self::AccountId>
204 + InspectHold<Self::AccountId>
205 + MutateHold<Self::AccountId>
206 + Balanced<Self::AccountId>;
207 type WeightInfo: WeightInfo;
209 type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
211 type AdjustedWeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
214 #[pallet::constant]
217 type FeeMultiplier: Get<u32>;
218 type OnXDMRewards: OnXDMRewards<BalanceOf<Self>>;
220 type MmrHash: Parameter + Member + Default + Clone;
222 type MmrProofVerifier: MmrProofVerifier<Self::MmrHash, BlockNumberFor<Self>, StateRootOf<Self>>;
224 type StorageKeys: StorageKeys;
226 type DomainOwner: DomainOwner<Self::AccountId>;
228 type HoldIdentifier: HoldIdentifier<Self>;
230 #[pallet::constant]
232 type ChannelReserveFee: Get<BalanceOf<Self>>;
233 #[pallet::constant]
236 type ChannelInitReservePortion: Get<Perbill>;
237 type DomainRegistration: DomainRegistration;
239 #[pallet::constant]
241 type MaxOutgoingMessages: Get<u32>;
242 type MessengerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
244 type NoteChainTransfer: NoteChainTransfer<BalanceOf<Self>>;
246 type ExtensionWeightInfo: ExtensionWeightInfo;
248 }
249
250 #[pallet::pallet]
252 #[pallet::without_storage_info]
253 #[pallet::storage_version(STORAGE_VERSION)]
254 pub struct Pallet<T>(_);
255
256 #[pallet::storage]
258 #[pallet::getter(fn next_channel_id)]
259 pub(super) type NextChannelId<T: Config> =
260 StorageMap<_, Identity, ChainId, ChannelId, ValueQuery>;
261
262 #[pallet::storage]
265 #[pallet::getter(fn channels)]
266 pub(super) type Channels<T: Config> = StorageDoubleMap<
267 _,
268 Identity,
269 ChainId,
270 Identity,
271 ChannelId,
272 Channel<BalanceOf<T>, T::AccountId>,
273 OptionQuery,
274 >;
275
276 #[pallet::storage]
279 #[pallet::getter(fn inbox)]
280 pub(super) type Inbox<T: Config> = StorageValue<_, Message<BalanceOf<T>>, OptionQuery>;
281
282 #[pallet::storage]
286 #[pallet::getter(fn inbox_fees)]
287 pub(super) type InboxFee<T: Config> =
288 StorageMap<_, Identity, (ChainId, MessageId), BalanceOf<T>, OptionQuery>;
289
290 #[pallet::storage]
293 #[pallet::getter(fn outbox_fees)]
294 pub(super) type OutboxFee<T: Config> =
295 StorageMap<_, Identity, (ChainId, MessageId), BalanceOf<T>, OptionQuery>;
296
297 #[pallet::storage]
300 #[pallet::getter(fn inbox_responses)]
301 pub(super) type InboxResponses<T: Config> =
302 StorageMap<_, Identity, (ChainId, ChannelId, Nonce), Message<BalanceOf<T>>, OptionQuery>;
303
304 #[pallet::storage]
307 #[pallet::getter(fn outbox)]
308 pub(super) type Outbox<T: Config> =
309 StorageMap<_, Identity, (ChainId, ChannelId, Nonce), Message<BalanceOf<T>>, OptionQuery>;
310
311 #[pallet::storage]
313 #[pallet::getter(fn outbox_message_count)]
314 pub(super) type OutboxMessageCount<T: Config> =
315 StorageMap<_, Identity, (ChainId, ChannelId), u32, ValueQuery>;
316
317 #[pallet::storage]
320 #[pallet::getter(fn outbox_responses)]
321 pub(super) type OutboxResponses<T: Config> =
322 StorageValue<_, Message<BalanceOf<T>>, OptionQuery>;
323
324 #[pallet::storage]
326 #[pallet::getter(fn outbox_message_weight_tags)]
327 pub(super) type OutboxMessageWeightTags<T: Config> =
328 StorageMap<_, Identity, (ChainId, MessageId), MessageWeightTag>;
329
330 #[pallet::storage]
332 #[pallet::getter(fn inbox_response_message_weight_tags)]
333 pub(super) type InboxResponseMessageWeightTags<T: Config> =
334 StorageMap<_, Identity, (ChainId, MessageId), MessageWeightTag>;
335
336 #[pallet::storage]
338 #[pallet::getter(fn chain_allowlist)]
339 pub(super) type ChainAllowlist<T: Config> = StorageValue<_, BTreeSet<ChainId>, ValueQuery>;
340
341 #[pallet::storage]
345 #[pallet::getter(fn domain_chain_allowlist_updates)]
346 pub(super) type DomainChainAllowlistUpdate<T: Config> =
347 StorageMap<_, Identity, DomainId, DomainAllowlistUpdates, OptionQuery>;
348
349 #[pallet::storage]
352 pub(super) type UpdatedChannels<T: Config> =
353 StorageValue<_, BTreeSet<(ChainId, ChannelId)>, ValueQuery>;
354
355 #[pallet::storage]
362 pub(super) type InboxFeesOnHold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
363
364 #[pallet::storage]
371 pub(super) type OutboxFeesOnHold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
372
373 #[pallet::storage]
379 pub(super) type InboxFeesOnHoldStartAt<T: Config> =
380 StorageMap<_, Identity, ChannelId, Nonce, OptionQuery>;
381 #[pallet::storage]
382 pub(super) type OutboxFeesOnHoldStartAt<T: Config> =
383 StorageMap<_, Identity, ChannelId, Nonce, OptionQuery>;
384
385 #[pallet::origin]
386 pub type Origin = RawOrigin;
387
388 #[pallet::event]
390 #[pallet::generate_deposit(pub (super) fn deposit_event)]
391 pub enum Event<T: Config> {
392 ChannelInitiated {
394 chain_id: ChainId,
396 channel_id: ChannelId,
398 },
399
400 ChannelClosed {
402 chain_id: ChainId,
404 channel_id: ChannelId,
406 },
407
408 ChannelOpen {
410 chain_id: ChainId,
412 channel_id: ChannelId,
414 },
415
416 OutboxMessage {
418 chain_id: ChainId,
419 channel_id: ChannelId,
420 nonce: Nonce,
421 },
422
423 OutboxMessageResponse {
425 chain_id: ChainId,
427 channel_id: ChannelId,
429 nonce: Nonce,
430 },
431
432 OutboxMessageResult {
434 chain_id: ChainId,
435 channel_id: ChannelId,
436 nonce: Nonce,
437 result: OutboxMessageResult,
438 },
439
440 InboxMessage {
442 chain_id: ChainId,
443 channel_id: ChannelId,
444 nonce: Nonce,
445 },
446
447 InboxMessageResponse {
449 chain_id: ChainId,
451 channel_id: ChannelId,
453 nonce: Nonce,
454 },
455 }
456
457 #[pallet::hooks]
458 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
459 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
460 UpdatedChannels::<T>::take();
461 T::DbWeight::get().reads_writes(0, 1)
462 }
463 }
464
465 #[pallet::validate_unsigned]
466 impl<T: Config> ValidateUnsigned for Pallet<T> {
467 type Call = Call<T>;
468
469 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
470 match call {
471 Call::update_domain_allowlist { .. } => Ok(()),
473 _ => Err(InvalidTransaction::Call.into()),
474 }
475 }
476
477 fn validate_unsigned(
479 _source: TransactionSource,
480 _call: &Self::Call,
481 ) -> TransactionValidity {
482 InvalidTransaction::Call.into()
483 }
484 }
485
486 #[pallet::error]
488 pub enum Error<T> {
489 InvalidChain,
491
492 MissingChannel,
494
495 InvalidChannelState,
497
498 NoOpenChannel,
500
501 NoMessageHandler,
503
504 OutboxFull,
506
507 InvalidMessagePayload,
509
510 InvalidMessageDestination,
512
513 MessageVerification(VerificationError),
515
516 MissingMessage,
518
519 WeightTagNotMatch,
522
523 BalanceOverflow,
525
526 BalanceUnderflow,
528
529 InvalidAllowedChain,
531
532 OperationNotAllowed,
534
535 NotDomainOwner,
537
538 ChainNotAllowed,
540
541 InsufficientBalance,
543
544 BalanceHold,
546
547 ChannelOwner,
549
550 BalanceUnlock,
552
553 InvalidChannelReserveFee,
555
556 InvalidMaxOutgoingMessages,
558
559 MessageCountOverflow,
561
562 MessageCountUnderflow,
564
565 FailedToNoteTransferIn,
567
568 FailedToNoteTransferOut,
570 }
571
572 #[pallet::call]
573 impl<T: Config> Pallet<T> {
574 #[pallet::call_index(0)]
578 #[pallet::weight(T::WeightInfo::initiate_channel())]
579 pub fn initiate_channel(origin: OriginFor<T>, dst_chain_id: ChainId) -> DispatchResult {
580 let owner = ensure_signed(origin)?;
581
582 let hold_id = T::HoldIdentifier::messenger_channel();
584 let amount = T::ChannelReserveFee::get();
585
586 ensure!(
588 T::Currency::reducible_balance(&owner, Preservation::Preserve, Fortitude::Polite)
589 >= amount,
590 Error::<T>::InsufficientBalance
591 );
592 T::Currency::hold(&hold_id, &owner, amount).map_err(|_| Error::<T>::BalanceHold)?;
593
594 let channel_open_params = ChannelOpenParamsV1 {
596 max_outgoing_messages: T::MaxOutgoingMessages::get(),
597 };
598 let channel_id = Self::do_init_channel(
599 dst_chain_id,
600 channel_open_params,
601 Some(owner.clone()),
602 true,
603 amount,
604 )?;
605
606 let payload = VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
607 ProtocolMessageRequest::ChannelOpen(ChannelOpenParamsV1 {
608 max_outgoing_messages: channel_open_params.max_outgoing_messages,
609 }),
610 )));
611
612 Self::new_outbox_message(T::SelfChainId::get(), dst_chain_id, channel_id, payload)?;
614
615 Ok(())
616 }
617
618 #[pallet::call_index(1)]
621 #[pallet::weight(T::WeightInfo::close_channel())]
622 pub fn close_channel(
623 origin: OriginFor<T>,
624 chain_id: ChainId,
625 channel_id: ChannelId,
626 ) -> DispatchResult {
627 let close_channel_by = match ensure_signed_or_root(origin)? {
630 Some(owner) => CloseChannelBy::Owner(owner),
631 None => CloseChannelBy::Sudo,
632 };
633 Self::do_close_channel(chain_id, channel_id, close_channel_by)?;
634
635 let payload = VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
636 ProtocolMessageRequest::ChannelClose,
637 )));
638
639 Self::new_outbox_message(T::SelfChainId::get(), chain_id, channel_id, payload)?;
640
641 Ok(())
642 }
643
644 #[pallet::call_index(2)]
646 #[pallet::weight(T::WeightInfo::relay_message().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag)))]
647 pub fn relay_message(
648 origin: OriginFor<T>,
649 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
650 ) -> DispatchResult {
651 T::MessengerOrigin::ensure_origin(origin)?;
652 let inbox_msg = Inbox::<T>::take().ok_or(Error::<T>::MissingMessage)?;
653 Self::process_inbox_messages(inbox_msg, msg.weight_tag)?;
654 Ok(())
655 }
656
657 #[pallet::call_index(3)]
659 #[pallet::weight(T::WeightInfo::relay_message_response().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag)))]
660 pub fn relay_message_response(
661 origin: OriginFor<T>,
662 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
663 ) -> DispatchResult {
664 T::MessengerOrigin::ensure_origin(origin)?;
665 let outbox_resp_msg = OutboxResponses::<T>::take().ok_or(Error::<T>::MissingMessage)?;
666 Self::process_outbox_message_responses(outbox_resp_msg, msg.weight_tag)?;
667 Ok(())
668 }
669
670 #[pallet::call_index(4)]
672 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1))]
673 pub fn update_consensus_chain_allowlist(
674 origin: OriginFor<T>,
675 update: ChainAllowlistUpdate,
676 ) -> DispatchResult {
677 ensure_root(origin)?;
678 ensure!(
679 T::SelfChainId::get().is_consensus_chain(),
680 Error::<T>::OperationNotAllowed
681 );
682
683 ensure!(
684 update.chain_id() != T::SelfChainId::get(),
685 Error::<T>::InvalidAllowedChain
686 );
687
688 if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update {
689 ensure!(
690 T::DomainRegistration::is_domain_registered(domain_id),
691 Error::<T>::InvalidChain
692 );
693 }
694
695 ChainAllowlist::<T>::mutate(|list| match update {
696 ChainAllowlistUpdate::Add(chain_id) => list.insert(chain_id),
697 ChainAllowlistUpdate::Remove(chain_id) => list.remove(&chain_id),
698 });
699 Ok(())
700 }
701
702 #[pallet::call_index(5)]
704 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1))]
705 pub fn initiate_domain_update_chain_allowlist(
706 origin: OriginFor<T>,
707 domain_id: DomainId,
708 update: ChainAllowlistUpdate,
709 ) -> DispatchResult {
710 let domain_owner = ensure_signed(origin)?;
711 ensure!(
712 T::DomainOwner::is_domain_owner(domain_id, domain_owner),
713 Error::<T>::NotDomainOwner
714 );
715
716 ensure!(
717 T::SelfChainId::get().is_consensus_chain(),
718 Error::<T>::OperationNotAllowed
719 );
720
721 if let Some(dst_domain_id) = update.chain_id().maybe_domain_chain() {
722 ensure!(dst_domain_id != domain_id, Error::<T>::InvalidAllowedChain);
723 }
724
725 if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update {
726 ensure!(
727 T::DomainRegistration::is_domain_registered(domain_id),
728 Error::<T>::InvalidChain
729 );
730 }
731
732 DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_domain_updates| {
733 let mut domain_updates = maybe_domain_updates.take().unwrap_or_default();
734 match update {
735 ChainAllowlistUpdate::Add(chain_id) => {
736 domain_updates.remove_chains.remove(&chain_id);
737 domain_updates.allow_chains.insert(chain_id);
738 }
739 ChainAllowlistUpdate::Remove(chain_id) => {
740 domain_updates.allow_chains.remove(&chain_id);
741 domain_updates.remove_chains.insert(chain_id);
742 }
743 }
744
745 *maybe_domain_updates = Some(domain_updates)
746 });
747 Ok(())
748 }
749
750 #[pallet::call_index(6)]
752 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Mandatory))]
753 pub fn update_domain_allowlist(
754 origin: OriginFor<T>,
755 updates: DomainAllowlistUpdates,
756 ) -> DispatchResult {
757 ensure_none(origin)?;
758 ensure!(
759 !T::SelfChainId::get().is_consensus_chain(),
760 Error::<T>::OperationNotAllowed
761 );
762
763 let DomainAllowlistUpdates {
764 allow_chains,
765 remove_chains,
766 } = updates;
767
768 ChainAllowlist::<T>::mutate(|list| {
769 remove_chains.into_iter().for_each(|chain_id| {
771 list.remove(&chain_id);
772 });
773
774 allow_chains.into_iter().for_each(|chain_id| {
776 list.insert(chain_id);
777 });
778 });
779
780 Ok(())
781 }
782 }
783
784 #[pallet::inherent]
785 impl<T: Config> ProvideInherent for Pallet<T> {
786 type Call = Call<T>;
787 type Error = InherentError;
788 const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
789
790 fn create_inherent(data: &InherentData) -> Option<Self::Call> {
791 let inherent_data = data
792 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
793 .expect("Messenger inherent data not correctly encoded")
794 .expect("Messenger inherent data must be provided");
795
796 inherent_data
797 .maybe_updates
798 .map(|updates| Call::update_domain_allowlist { updates })
799 }
800
801 fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
802 let inherent_data = data
803 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
804 .expect("Messenger inherent data not correctly encoded")
805 .expect("Messenger inherent data must be provided");
806
807 Ok(if inherent_data.maybe_updates.is_none() {
808 None
809 } else {
810 Some(InherentError::MissingAllowlistUpdates)
811 })
812 }
813
814 fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
815 let inherent_data = data
816 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
817 .expect("Messenger inherent data not correctly encoded")
818 .expect("Messenger inherent data must be provided");
819
820 if let Some(provided_updates) = inherent_data.maybe_updates {
821 if let Call::update_domain_allowlist { updates } = call
822 && updates != &provided_updates
823 {
824 return Err(InherentError::IncorrectAllowlistUpdates);
825 }
826 } else {
827 return Err(InherentError::MissingAllowlistUpdates);
828 }
829
830 Ok(())
831 }
832
833 fn is_inherent(call: &Self::Call) -> bool {
834 matches!(call, Call::update_domain_allowlist { .. })
835 }
836 }
837
838 impl<T: Config> Sender<T::AccountId> for Pallet<T> {
839 type MessageId = MessageId;
840
841 fn send_message(
842 sender: &T::AccountId,
843 dst_chain_id: ChainId,
844 req: EndpointRequest,
845 ) -> Result<Self::MessageId, DispatchError> {
846 let allowed_chains = ChainAllowlist::<T>::get();
847 ensure!(
848 allowed_chains.contains(&dst_chain_id),
849 Error::<T>::ChainNotAllowed
850 );
851
852 let channel_id =
853 Self::get_open_channel_for_chain(dst_chain_id).ok_or(Error::<T>::NoOpenChannel)?;
854
855 let src_endpoint = req.src_endpoint.clone();
856
857 let message_id = {
858 let collected_fee = Self::collect_fees_for_message_v1(sender, &src_endpoint)?;
860 let src_chain_fee = collected_fee.src_chain_fee;
861 let dst_chain_fee = collected_fee.dst_chain_fee;
862 let nonce = Self::new_outbox_message(
863 T::SelfChainId::get(),
864 dst_chain_id,
865 channel_id,
866 VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(
867 EndpointRequestWithCollectedFee { req, collected_fee },
868 ))),
869 )?;
870
871 let message_id = (channel_id, nonce);
873 Self::store_outbox_fee(dst_chain_id, message_id, src_chain_fee, dst_chain_fee)?;
874 message_id
875 };
876
877 Ok(message_id)
878 }
879
880 #[cfg(feature = "runtime-benchmarks")]
883 fn unchecked_open_channel(dst_chain_id: ChainId) -> Result<(), DispatchError> {
884 let init_params = ChannelOpenParamsV1 {
885 max_outgoing_messages: 100,
886 };
887 ChainAllowlist::<T>::mutate(|list| list.insert(dst_chain_id));
888 let channel_id =
889 Self::do_init_channel(dst_chain_id, init_params, None, true, Zero::zero())?;
890 Self::do_open_channel(dst_chain_id, channel_id)?;
891 Ok(())
892 }
893 }
894
895 impl<T: Config> Pallet<T> {
896 fn message_weight(weight_tag: &MessageWeightTag) -> Weight {
898 match weight_tag {
899 MessageWeightTag::ProtocolChannelOpen => T::WeightInfo::do_open_channel(),
900 MessageWeightTag::ProtocolChannelClose => T::WeightInfo::do_close_channel(),
901 MessageWeightTag::EndpointRequest(endpoint) => {
902 T::get_endpoint_handler(endpoint)
903 .map(|endpoint_handler| endpoint_handler.message_weight())
904 .unwrap_or(Weight::zero())
906 }
907 MessageWeightTag::EndpointResponse(endpoint) => {
908 T::get_endpoint_handler(endpoint)
909 .map(|endpoint_handler| endpoint_handler.message_response_weight())
910 .unwrap_or(Weight::zero())
912 }
913 MessageWeightTag::None => Weight::zero(),
914 }
915 }
916
917 pub fn get_open_channel_for_chain(dst_chain_id: ChainId) -> Option<ChannelId> {
919 let mut next_channel_id = NextChannelId::<T>::get(dst_chain_id);
920
921 while let Some(channel_id) = next_channel_id.checked_sub(ChannelId::one()) {
924 let message_count = OutboxMessageCount::<T>::get((dst_chain_id, channel_id));
925 if let Some(channel) = Channels::<T>::get(dst_chain_id, channel_id)
926 && channel.state == ChannelState::Open
927 && message_count < channel.max_outgoing_messages
928 {
929 return Some(channel_id);
930 }
931
932 next_channel_id = channel_id
933 }
934
935 None
936 }
937
938 pub(crate) fn do_open_channel(chain_id: ChainId, channel_id: ChannelId) -> DispatchResult {
940 Channels::<T>::try_mutate(chain_id, channel_id, |maybe_channel| -> DispatchResult {
941 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
942
943 ensure!(
944 channel.state == ChannelState::Initiated,
945 Error::<T>::InvalidChannelState
946 );
947
948 channel.state = ChannelState::Open;
949 Ok(())
950 })?;
951
952 Self::deposit_event(Event::ChannelOpen {
953 chain_id,
954 channel_id,
955 });
956
957 Ok(())
958 }
959
960 pub(crate) fn do_close_channel(
961 chain_id: ChainId,
962 channel_id: ChannelId,
963 close_channel_by: CloseChannelBy<T::AccountId>,
964 ) -> DispatchResult {
965 Channels::<T>::try_mutate(chain_id, channel_id, |maybe_channel| -> DispatchResult {
966 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
967
968 ensure!(
969 channel.state != ChannelState::Closed,
970 Error::<T>::InvalidChannelState
971 );
972
973 if let CloseChannelBy::Owner(owner) = close_channel_by {
974 ensure!(channel.maybe_owner == Some(owner), Error::<T>::ChannelOwner);
975 }
976
977 if let Some(owner) = &channel.maybe_owner {
978 let hold_id = T::HoldIdentifier::messenger_channel();
979 let locked_amount = channel.channel_reserve_fee;
980 let (amount_to_release, maybe_amount_to_burn) = {
981 if channel.state == ChannelState::Open {
982 (locked_amount, None)
983 } else {
984 let protocol_fee = T::ChannelInitReservePortion::get() * locked_amount;
985 let release_amount = locked_amount.saturating_sub(protocol_fee);
986 (release_amount, Some(protocol_fee))
987 }
988 };
989
990 with_storage_layer(|| {
991 if let Some(protocol_fee) = maybe_amount_to_burn {
992 T::Currency::burn_held(
993 &hold_id,
994 owner,
995 protocol_fee,
996 Precision::Exact,
997 Fortitude::Force,
998 )?;
999 T::OnXDMRewards::on_chain_protocol_fees(chain_id, protocol_fee);
1000 }
1001
1002 T::Currency::release(&hold_id, owner, amount_to_release, Precision::Exact)
1003 .map_err(|_| Error::<T>::BalanceUnlock)?;
1004
1005 Ok::<(), DispatchError>(())
1006 })?;
1007 }
1008
1009 channel.state = ChannelState::Closed;
1010 Ok(())
1011 })?;
1012
1013 Self::deposit_event(Event::ChannelClosed {
1014 chain_id,
1015 channel_id,
1016 });
1017
1018 Ok(())
1019 }
1020
1021 pub(crate) fn do_init_channel(
1022 dst_chain_id: ChainId,
1023 init_params: ChannelOpenParamsV1,
1024 maybe_owner: Option<T::AccountId>,
1025 check_allowlist: bool,
1026 channel_reserve_fee: BalanceOf<T>,
1027 ) -> Result<ChannelId, DispatchError> {
1028 ensure!(
1029 T::SelfChainId::get() != dst_chain_id,
1030 Error::<T>::InvalidChain,
1031 );
1032
1033 ensure!(
1035 init_params.max_outgoing_messages >= 1u32,
1036 Error::<T>::InvalidMaxOutgoingMessages
1037 );
1038
1039 ensure!(
1042 maybe_owner.is_none() || !channel_reserve_fee.is_zero(),
1043 Error::<T>::InvalidChannelReserveFee,
1044 );
1045
1046 if check_allowlist {
1047 let chain_allowlist = ChainAllowlist::<T>::get();
1048 ensure!(
1049 chain_allowlist.contains(&dst_chain_id),
1050 Error::<T>::ChainNotAllowed
1051 );
1052 }
1053
1054 let channel_id = NextChannelId::<T>::get(dst_chain_id);
1055 let next_channel_id = channel_id
1056 .checked_add(U256::one())
1057 .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?;
1058
1059 Channels::<T>::insert(
1060 dst_chain_id,
1061 channel_id,
1062 Channel {
1063 channel_id,
1064 state: ChannelState::Initiated,
1065 next_inbox_nonce: Default::default(),
1066 next_outbox_nonce: Default::default(),
1067 latest_response_received_message_nonce: Default::default(),
1068 max_outgoing_messages: init_params.max_outgoing_messages,
1069 maybe_owner,
1070 channel_reserve_fee,
1071 },
1072 );
1073
1074 NextChannelId::<T>::insert(dst_chain_id, next_channel_id);
1075 Self::deposit_event(Event::ChannelInitiated {
1076 chain_id: dst_chain_id,
1077 channel_id,
1078 });
1079 Ok(channel_id)
1080 }
1081
1082 pub fn validate_relay_message(
1083 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1084 consensus_state_root: StateRootOf<T>,
1085 ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1086 let (next_nonce, maybe_channel) =
1087 match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1088 None => {
1089 log::debug!(
1092 "Initiating new channel: {:?} to chain: {:?}",
1093 xdm.channel_id,
1094 xdm.src_chain_id
1095 );
1096 (Nonce::zero(), None)
1097 }
1098 Some(channel) => {
1099 log::debug!(
1100 "Message to channel: {:?} from chain: {:?}",
1101 xdm.channel_id,
1102 xdm.src_chain_id
1103 );
1104 (channel.next_inbox_nonce, Some(channel))
1105 }
1106 };
1107
1108 let key = StorageKey(
1110 T::StorageKeys::outbox_storage_key(
1111 xdm.src_chain_id,
1112 (T::SelfChainId::get(), xdm.channel_id, xdm.nonce),
1113 )
1114 .ok_or(UnknownTransaction::CannotLookup)?,
1115 );
1116
1117 let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1119
1120 let is_valid_call = match &msg.payload {
1121 VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(req))) => {
1122 match req {
1123 ProtocolMessageRequest::ChannelOpen(_) => maybe_channel.is_none(),
1125 ProtocolMessageRequest::ChannelClose => {
1127 if let Some(ref channel) = maybe_channel {
1128 !(channel.state == ChannelState::Closed)
1129 } else {
1130 false
1131 }
1132 }
1133 }
1134 }
1135 VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(_))) => {
1141 if let Some(ref channel) = maybe_channel {
1142 !(channel.state == ChannelState::Initiated)
1143 } else {
1144 false
1145 }
1146 }
1147 _ => false,
1149 };
1150
1151 if !is_valid_call {
1152 log::error!("Unexpected XDM message: {:?}", msg,);
1153 return Err(InvalidTransaction::Call.into());
1154 }
1155
1156 if msg.nonce.cmp(&next_nonce) == Ordering::Less {
1158 return Err(InvalidTransaction::Stale.into());
1159 }
1160
1161 let validated_relay_msg = ValidatedRelayMessage {
1162 message: msg,
1163 should_init_channel: maybe_channel.is_none(),
1164 next_nonce,
1165 };
1166
1167 Ok(validated_relay_msg)
1168 }
1169
1170 pub(crate) fn pre_dispatch_relay_message(
1171 msg: Message<BalanceOf<T>>,
1172 should_init_channel: bool,
1173 ) -> Result<(), TransactionValidityError> {
1174 if should_init_channel {
1175 if let VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
1176 ProtocolMessageRequest::ChannelOpen(params),
1177 ))) = msg.payload
1178 {
1179 Self::do_init_channel(msg.src_chain_id, params, None, false, Zero::zero())
1183 .map_err(|err| {
1184 log::error!(
1185 "Error initiating channel: {:?} with chain: {:?}: {:?}",
1186 msg.channel_id,
1187 msg.src_chain_id,
1188 err
1189 );
1190 InvalidTransaction::Call
1191 })?;
1192 } else {
1193 log::error!("Unexpected call instead of channel open request: {:?}", msg,);
1194 return Err(InvalidTransaction::Call.into());
1195 }
1196 }
1197
1198 let (dst_chain_id, channel_id, nonce) = (msg.src_chain_id, msg.channel_id, msg.nonce);
1199 Channels::<T>::mutate(
1202 dst_chain_id,
1203 channel_id,
1204 |maybe_channel| -> sp_runtime::DispatchResult {
1205 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1206 channel.next_inbox_nonce = nonce
1207 .checked_add(Nonce::one())
1208 .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?;
1209 Ok(())
1210 },
1211 )
1212 .map_err(|err| {
1213 log::error!(
1214 "Failed to increment the next relay message nonce for Chain[{:?}] with Channel[{:?}]: {:?}",
1215 dst_chain_id,
1216 channel_id,
1217 err,
1218 );
1219 InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1220 })?;
1221
1222 Self::deposit_event(Event::InboxMessage {
1223 chain_id: msg.src_chain_id,
1224 channel_id: msg.channel_id,
1225 nonce: msg.nonce,
1226 });
1227 Inbox::<T>::put(msg);
1228 Ok(())
1229 }
1230
1231 pub fn validate_relay_message_response(
1232 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1233 consensus_state_root: StateRootOf<T>,
1234 ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1235 let next_nonce =
1237 match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1238 None => {
1240 log::error!("Unexpected inbox message response: {:?}", xdm,);
1241 return Err(InvalidTransaction::Call.into());
1242 }
1243 Some(channel) => match channel.latest_response_received_message_nonce {
1244 None => Nonce::zero(),
1245 Some(last_nonce) => last_nonce.checked_add(Nonce::one()).ok_or(
1246 InvalidTransaction::Custom(
1247 crate::verification_errors::XDM_NONCE_OVERFLOW,
1248 ),
1249 )?,
1250 },
1251 };
1252
1253 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 let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1264
1265 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 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 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 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 ensure!(
1331 xdm.nonce >= next_nonce,
1332 InvalidTransaction::Custom(crate::verification_errors::INVALID_NONCE)
1333 );
1334
1335 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 "Failed to verify storage proof for confirmed Domain block: {:?}",
1359 err
1360 );
1361 TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1362 })?
1363 .final_state_root
1364 } else {
1365 consensus_state_root
1366 };
1367
1368 let msg =
1370 StorageProofVerifier::<T::Hashing>::get_decoded_value::<Message<BalanceOf<T>>>(
1371 &state_root,
1372 xdm.proof.message_proof(),
1373 storage_key,
1374 )
1375 .map_err(|err| {
1376 log::error!("Failed to verify storage proof for message: {:?}", err);
1377 TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1378 })?;
1379
1380 Ok(msg)
1381 }
1382
1383 pub fn outbox_storage_key(message_key: MessageKey) -> Vec<u8> {
1384 Outbox::<T>::hashed_key_for(message_key)
1385 }
1386
1387 pub fn inbox_response_storage_key(message_key: MessageKey) -> Vec<u8> {
1388 InboxResponses::<T>::hashed_key_for(message_key)
1389 }
1390
1391 pub fn channel_storage_key(chain_id: ChainId, channel_id: ChannelId) -> Vec<u8> {
1392 Channels::<T>::hashed_key_for(chain_id, channel_id)
1393 }
1394
1395 pub fn domain_chains_allowlist_update(
1396 domain_id: DomainId,
1397 ) -> Option<DomainAllowlistUpdates> {
1398 DomainChainAllowlistUpdate::<T>::get(domain_id).filter(|updates| !updates.is_empty())
1399 }
1400
1401 pub fn domain_allow_list_update_storage_key(domain_id: DomainId) -> Vec<u8> {
1402 DomainChainAllowlistUpdate::<T>::hashed_key_for(domain_id)
1403 }
1404
1405 pub fn updated_channels() -> BTreeSet<(ChainId, ChannelId)> {
1406 UpdatedChannels::<T>::get()
1407 }
1408
1409 pub fn open_channels() -> BTreeSet<(ChainId, ChannelId)> {
1410 Channels::<T>::iter_keys().collect()
1411 }
1412
1413 pub fn channels_and_states() -> Vec<(ChainId, ChannelId, ChannelStateWithNonce)> {
1414 crate::migrations::get_channels_and_states::<T>()
1415 }
1416
1417 pub fn channel_nonce(chain_id: ChainId, channel_id: ChannelId) -> Option<ChannelNonce> {
1418 crate::migrations::get_channel::<T>(chain_id, channel_id).map(|channel| {
1419 let last_inbox_nonce = channel.next_inbox_nonce.checked_sub(U256::one());
1420 ChannelNonce {
1421 relay_msg_nonce: last_inbox_nonce,
1422 relay_response_msg_nonce: channel.latest_response_received_message_nonce,
1423 }
1424 })
1425 }
1426
1427 pub fn store_inbox_fee(
1428 src_chain_id: ChainId,
1429 message_id: MessageId,
1430 inbox_fees: BalanceOf<T>,
1431 ) -> DispatchResult {
1432 if !InboxFeesOnHoldStartAt::<T>::contains_key(message_id.0) {
1433 InboxFeesOnHoldStartAt::<T>::insert(message_id.0, message_id.1);
1434 }
1435 InboxFeesOnHold::<T>::mutate(|inbox_fees_on_hold| {
1436 *inbox_fees_on_hold = inbox_fees_on_hold
1437 .checked_add(&inbox_fees)
1438 .ok_or(Error::<T>::BalanceOverflow)?;
1439
1440 let imbalance = T::Currency::issue(inbox_fees);
1443 core::mem::forget(imbalance);
1444
1445 Ok::<(), Error<T>>(())
1446 })?;
1447
1448 InboxFee::<T>::insert((src_chain_id, message_id), inbox_fees);
1449
1450 if !T::NoteChainTransfer::note_transfer_in(inbox_fees, src_chain_id) {
1452 return Err(Error::<T>::FailedToNoteTransferIn.into());
1453 }
1454
1455 Ok(())
1456 }
1457
1458 pub fn store_outbox_fee(
1459 dst_chain_id: ChainId,
1460 message_id: MessageId,
1461 outbox_fees: BalanceOf<T>,
1462 inbox_fees: BalanceOf<T>,
1463 ) -> DispatchResult {
1464 if !OutboxFeesOnHoldStartAt::<T>::contains_key(message_id.0) {
1465 OutboxFeesOnHoldStartAt::<T>::insert(message_id.0, message_id.1);
1466 }
1467 OutboxFeesOnHold::<T>::mutate(|outbox_fees_on_hold| {
1468 *outbox_fees_on_hold = outbox_fees_on_hold
1469 .checked_add(&outbox_fees)
1470 .ok_or(Error::<T>::BalanceOverflow)?;
1471
1472 let imbalance = T::Currency::issue(outbox_fees);
1475 core::mem::forget(imbalance);
1476
1477 Ok::<(), Error<T>>(())
1478 })?;
1479
1480 OutboxFee::<T>::insert((dst_chain_id, message_id), outbox_fees);
1481
1482 if !T::NoteChainTransfer::note_transfer_out(inbox_fees, dst_chain_id) {
1484 return Err(Error::<T>::FailedToNoteTransferOut.into());
1485 }
1486
1487 Ok(())
1488 }
1489 }
1490}
1491
1492impl<T> Pallet<T>
1493where
1494 T: Config + CreateUnsigned<Call<T>>,
1495{
1496 pub fn outbox_message_unsigned(
1497 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1498 ) -> Option<T::Extrinsic> {
1499 let call = Call::relay_message { msg };
1500 Some(T::create_unsigned(call.into()))
1501 }
1502
1503 pub fn inbox_response_message_unsigned(
1504 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1505 ) -> Option<T::Extrinsic> {
1506 let call = Call::relay_message_response { msg };
1507 Some(T::create_unsigned(call.into()))
1508 }
1509
1510 pub fn first_outbox_message_nonce_to_relay(
1512 dst_chain_id: ChainId,
1513 channel_id: ChannelId,
1514 from_nonce: Nonce,
1515 ) -> Option<Nonce> {
1516 Self::first_relay_message(
1517 dst_chain_id,
1518 channel_id,
1519 from_nonce,
1520 Outbox::<T>::contains_key,
1521 )
1522 }
1523
1524 pub fn first_inbox_message_response_nonce_to_relay(
1526 dst_chain_id: ChainId,
1527 channel_id: ChannelId,
1528 from_nonce: Nonce,
1529 ) -> Option<Nonce> {
1530 Self::first_relay_message(
1531 dst_chain_id,
1532 channel_id,
1533 from_nonce,
1534 InboxResponses::<T>::contains_key,
1535 )
1536 }
1537
1538 fn first_relay_message<Check>(
1539 dst_chain_id: ChainId,
1540 channel_id: ChannelId,
1541 from_nonce: Nonce,
1542 check: Check,
1543 ) -> Option<Nonce>
1544 where
1545 Check: Fn((ChainId, ChannelId, Nonce)) -> bool,
1546 {
1547 let mut nonce = from_nonce;
1548 let to_nonce = from_nonce.saturating_add(MAX_FUTURE_ALLOWED_NONCES.into());
1549 while nonce <= to_nonce {
1550 if check((dst_chain_id, channel_id, nonce)) {
1551 return Some(nonce);
1552 }
1553
1554 nonce = nonce.saturating_add(Nonce::one())
1555 }
1556
1557 None
1558 }
1559}
1560
1561impl<T: Config> sp_domains::DomainBundleSubmitted for Pallet<T> {
1562 fn domain_bundle_submitted(domain_id: DomainId) {
1563 DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_updates| {
1567 if let Some(updates) = maybe_updates {
1568 updates.clear();
1569 }
1570 });
1571 }
1572}
1573
1574impl<T: Config> sp_domains::OnDomainInstantiated for Pallet<T> {
1575 fn on_domain_instantiated(domain_id: DomainId) {
1576 DomainChainAllowlistUpdate::<T>::insert(domain_id, DomainAllowlistUpdates::default());
1577 }
1578}