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