1#![cfg_attr(not(feature = "std"), no_std)]
19#![expect(clippy::clone_on_copy, reason = "Comes from Substrate")]
21#![forbid(unsafe_code)]
22#![warn(rust_2018_idioms)]
23#![cfg_attr(test, feature(variant_count))]
24
25#[cfg(feature = "runtime-benchmarks")]
26mod benchmarking;
27pub mod extensions;
28mod fees;
29mod messages;
30#[cfg(test)]
31mod mock;
32#[cfg(test)]
33mod tests;
34pub mod weights;
35
36#[cfg(not(feature = "std"))]
37extern crate alloc;
38
39use frame_support::__private::RuntimeDebug;
40use frame_support::pallet_prelude::{EnsureOrigin, MaxEncodedLen, StorageVersion};
41use frame_support::traits::fungible::{Inspect, InspectHold};
42use frame_system::pallet_prelude::BlockNumberFor;
43pub use pallet::*;
44use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
45use scale_info::TypeInfo;
46use sp_core::U256;
47use sp_domains::{DomainAllowlistUpdates, DomainId};
48use sp_messenger::MAX_FUTURE_ALLOWED_NONCES;
49use sp_messenger::messages::{
50 ChainId, Channel, ChannelId, ChannelState, CrossDomainMessage, Message, Nonce,
51};
52use sp_runtime::DispatchError;
53use sp_runtime::traits::Hash;
54use subspace_runtime_primitives::CreateUnsigned;
55pub use weights::WeightInfo;
56
57const XDM_TRANSACTION_LONGEVITY: u64 = 10;
60
61pub(crate) mod verification_errors {
63 pub(crate) const INVALID_NONCE: u8 = 201;
67 pub(crate) const XDM_NONCE_OVERFLOW: u8 = 202;
69 pub(crate) const INVALID_CHANNEL: u8 = 203;
71 pub(crate) const IN_FUTURE_NONCE: u8 = 204;
72 pub(crate) const NEXT_NONCE_UPDATE: u8 = 205;
74}
75
76#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy, DecodeWithMemTracking)]
77pub enum OutboxMessageResult {
78 Ok,
80 Err(DispatchError),
82}
83
84#[derive(
86 PartialEq,
87 Eq,
88 Clone,
89 Encode,
90 Decode,
91 RuntimeDebug,
92 TypeInfo,
93 MaxEncodedLen,
94 DecodeWithMemTracking,
95)]
96pub enum RawOrigin {
97 ValidatedUnsigned,
98}
99
100pub struct EnsureMessengerOrigin;
102impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureMessengerOrigin {
103 type Success = ();
104
105 fn try_origin(o: O) -> Result<Self::Success, O> {
106 o.into().map(|o| match o {
107 RawOrigin::ValidatedUnsigned => (),
108 })
109 }
110
111 #[cfg(feature = "runtime-benchmarks")]
112 fn try_successful_origin() -> Result<O, ()> {
113 Ok(O::from(RawOrigin::ValidatedUnsigned))
114 }
115}
116
117pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>::Output;
118pub(crate) type BalanceOf<T> =
119 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
120pub(crate) type FungibleHoldId<T> =
121 <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
122
123#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy, DecodeWithMemTracking)]
125pub enum ChainAllowlistUpdate {
126 Add(ChainId),
127 Remove(ChainId),
128}
129
130impl ChainAllowlistUpdate {
131 fn chain_id(&self) -> ChainId {
132 match self {
133 ChainAllowlistUpdate::Add(chain_id) => *chain_id,
134 ChainAllowlistUpdate::Remove(chain_id) => *chain_id,
135 }
136 }
137}
138
139#[derive(Debug, Encode, Decode, TypeInfo)]
140pub struct ValidatedRelayMessage<T: Config> {
141 pub message: Message<BalanceOf<T>>,
142 pub should_init_channel: bool,
143 pub next_nonce: Nonce,
144}
145
146#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
148pub(crate) enum CloseChannelBy<AccountId> {
149 Owner(AccountId),
150 Sudo,
151}
152
153pub trait HoldIdentifier<T: Config> {
155 fn messenger_channel() -> FungibleHoldId<T>;
156}
157
158const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
160
161#[frame_support::pallet]
162mod pallet {
163 pub use crate::extensions::weights::WeightInfo as ExtensionWeightInfo;
164 use crate::weights::WeightInfo;
165 use crate::{
166 BalanceOf, ChainAllowlistUpdate, Channel, ChannelId, ChannelState, CloseChannelBy,
167 HoldIdentifier, Nonce, OutboxMessageResult, RawOrigin, STORAGE_VERSION, StateRootOf, U256,
168 ValidatedRelayMessage,
169 };
170 #[cfg(not(feature = "std"))]
171 use alloc::boxed::Box;
172 #[cfg(not(feature = "std"))]
173 use alloc::collections::BTreeSet;
174 #[cfg(not(feature = "std"))]
175 use alloc::vec::Vec;
176 use core::cmp::Ordering;
177 use frame_support::ensure;
178 use frame_support::pallet_prelude::*;
179 use frame_support::storage::with_storage_layer;
180 use frame_support::traits::fungible::{Balanced, Inspect, InspectHold, Mutate, MutateHold};
181 use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
182 use frame_support::weights::WeightToFee;
183 use frame_system::pallet_prelude::*;
184 use sp_core::storage::StorageKey;
185 use sp_domains::proof_provider_and_verifier::{StorageProofVerifier, VerificationError};
186 use sp_domains::{DomainAllowlistUpdates, DomainId, DomainOwner};
187 use sp_messenger::endpoint::{
188 Endpoint, EndpointHandler, EndpointRequest, EndpointRequestWithCollectedFee, Sender,
189 };
190 use sp_messenger::messages::{
191 ChainId, ChannelOpenParamsV1, ChannelStateWithNonce, CrossDomainMessage, Message,
192 MessageId, MessageKey, MessageWeightTag, PayloadV1, ProtocolMessageRequest,
193 RequestResponse, VersionedPayload,
194 };
195 use sp_messenger::{
196 ChannelNonce, DomainRegistration, INHERENT_IDENTIFIER, InherentError, InherentType,
197 NoteChainTransfer, OnXDMRewards, StorageKeys,
198 };
199 use sp_runtime::traits::Zero;
200 use sp_runtime::{ArithmeticError, Perbill, Saturating};
201 use sp_subspace_mmr::MmrProofVerifier;
202 #[cfg(feature = "std")]
203 use std::collections::BTreeSet;
204
205 #[pallet::config]
206 pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
207 type SelfChainId: Get<ChainId>;
209 fn get_endpoint_handler(endpoint: &Endpoint)
211 -> Option<Box<dyn EndpointHandler<MessageId>>>;
212 type Currency: Mutate<Self::AccountId>
214 + InspectHold<Self::AccountId>
215 + MutateHold<Self::AccountId>
216 + Balanced<Self::AccountId>;
217 type WeightInfo: WeightInfo;
219 type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
221 type AdjustedWeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
224 #[pallet::constant]
227 type FeeMultiplier: Get<u32>;
228 type OnXDMRewards: OnXDMRewards<BalanceOf<Self>>;
230 type MmrHash: Parameter + Member + Default + Clone;
232 type MmrProofVerifier: MmrProofVerifier<Self::MmrHash, BlockNumberFor<Self>, StateRootOf<Self>>;
234 type StorageKeys: StorageKeys;
236 type DomainOwner: DomainOwner<Self::AccountId>;
238 type HoldIdentifier: HoldIdentifier<Self>;
240 #[pallet::constant]
242 type ChannelReserveFee: Get<BalanceOf<Self>>;
243 #[pallet::constant]
246 type ChannelInitReservePortion: Get<Perbill>;
247 type DomainRegistration: DomainRegistration;
249 #[pallet::constant]
251 type MaxOutgoingMessages: Get<u32>;
252 type MessengerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
254 type NoteChainTransfer: NoteChainTransfer<BalanceOf<Self>>;
256 type ExtensionWeightInfo: ExtensionWeightInfo;
258 }
259
260 #[pallet::pallet]
262 #[pallet::without_storage_info]
263 #[pallet::storage_version(STORAGE_VERSION)]
264 pub struct Pallet<T>(_);
265
266 #[pallet::storage]
268 #[pallet::getter(fn next_channel_id)]
269 pub(super) type NextChannelId<T: Config> =
270 StorageMap<_, Identity, ChainId, ChannelId, ValueQuery>;
271
272 #[pallet::storage]
275 #[pallet::getter(fn channels)]
276 pub(super) type Channels<T: Config> = StorageDoubleMap<
277 _,
278 Identity,
279 ChainId,
280 Identity,
281 ChannelId,
282 Channel<BalanceOf<T>, T::AccountId>,
283 OptionQuery,
284 >;
285
286 #[pallet::storage]
289 #[pallet::getter(fn inbox)]
290 pub(super) type Inbox<T: Config> = StorageValue<_, Message<BalanceOf<T>>, OptionQuery>;
291
292 #[pallet::storage]
296 #[pallet::getter(fn inbox_fees)]
297 pub(super) type InboxFee<T: Config> =
298 StorageMap<_, Identity, (ChainId, MessageId), BalanceOf<T>, OptionQuery>;
299
300 #[pallet::storage]
303 #[pallet::getter(fn outbox_fees)]
304 pub(super) type OutboxFee<T: Config> =
305 StorageMap<_, Identity, (ChainId, MessageId), BalanceOf<T>, OptionQuery>;
306
307 #[pallet::storage]
310 #[pallet::getter(fn inbox_responses)]
311 pub(super) type InboxResponses<T: Config> =
312 StorageMap<_, Identity, (ChainId, ChannelId, Nonce), Message<BalanceOf<T>>, OptionQuery>;
313
314 #[pallet::storage]
317 #[pallet::getter(fn outbox)]
318 pub(super) type Outbox<T: Config> =
319 StorageMap<_, Identity, (ChainId, ChannelId, Nonce), Message<BalanceOf<T>>, OptionQuery>;
320
321 #[pallet::storage]
323 #[pallet::getter(fn outbox_message_count)]
324 pub(super) type OutboxMessageCount<T: Config> =
325 StorageMap<_, Identity, (ChainId, ChannelId), u32, ValueQuery>;
326
327 #[pallet::storage]
330 #[pallet::getter(fn outbox_responses)]
331 pub(super) type OutboxResponses<T: Config> =
332 StorageValue<_, Message<BalanceOf<T>>, OptionQuery>;
333
334 #[pallet::storage]
336 #[pallet::getter(fn outbox_message_weight_tags)]
337 pub(super) type OutboxMessageWeightTags<T: Config> =
338 StorageMap<_, Identity, (ChainId, MessageId), MessageWeightTag>;
339
340 #[pallet::storage]
342 #[pallet::getter(fn inbox_response_message_weight_tags)]
343 pub(super) type InboxResponseMessageWeightTags<T: Config> =
344 StorageMap<_, Identity, (ChainId, MessageId), MessageWeightTag>;
345
346 #[pallet::storage]
348 #[pallet::getter(fn chain_allowlist)]
349 pub(super) type ChainAllowlist<T: Config> = StorageValue<_, BTreeSet<ChainId>, ValueQuery>;
350
351 #[pallet::storage]
355 #[pallet::getter(fn domain_chain_allowlist_updates)]
356 pub(super) type DomainChainAllowlistUpdate<T: Config> =
357 StorageMap<_, Identity, DomainId, DomainAllowlistUpdates, OptionQuery>;
358
359 #[pallet::storage]
362 pub(super) type UpdatedChannels<T: Config> =
363 StorageValue<_, BTreeSet<(ChainId, ChannelId)>, ValueQuery>;
364
365 #[pallet::storage]
372 pub(super) type InboxFeesOnHold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
373
374 #[pallet::storage]
381 pub(super) type OutboxFeesOnHold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
382
383 #[pallet::origin]
384 pub type Origin = RawOrigin;
385
386 #[pallet::event]
388 #[pallet::generate_deposit(pub (super) fn deposit_event)]
389 pub enum Event<T: Config> {
390 ChannelInitiated {
392 chain_id: ChainId,
394 channel_id: ChannelId,
396 },
397
398 ChannelClosed {
400 chain_id: ChainId,
402 channel_id: ChannelId,
404 },
405
406 ChannelOpen {
408 chain_id: ChainId,
410 channel_id: ChannelId,
412 },
413
414 OutboxMessage {
416 chain_id: ChainId,
417 channel_id: ChannelId,
418 nonce: Nonce,
419 },
420
421 OutboxMessageResponse {
423 chain_id: ChainId,
425 channel_id: ChannelId,
427 nonce: Nonce,
428 },
429
430 OutboxMessageResult {
432 chain_id: ChainId,
433 channel_id: ChannelId,
434 nonce: Nonce,
435 result: OutboxMessageResult,
436 },
437
438 InboxMessage {
440 chain_id: ChainId,
441 channel_id: ChannelId,
442 nonce: Nonce,
443 },
444
445 InboxMessageResponse {
447 chain_id: ChainId,
449 channel_id: ChannelId,
451 nonce: Nonce,
452 },
453 }
454
455 #[pallet::hooks]
456 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
457 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
458 UpdatedChannels::<T>::take();
459 T::DbWeight::get().reads_writes(0, 1)
460 }
461 }
462
463 #[pallet::validate_unsigned]
464 impl<T: Config> ValidateUnsigned for Pallet<T> {
465 type Call = Call<T>;
466
467 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
468 match call {
469 Call::update_domain_allowlist { .. } => Ok(()),
471 _ => Err(InvalidTransaction::Call.into()),
472 }
473 }
474
475 fn validate_unsigned(
477 _source: TransactionSource,
478 _call: &Self::Call,
479 ) -> TransactionValidity {
480 InvalidTransaction::Call.into()
481 }
482 }
483
484 #[pallet::error]
486 pub enum Error<T> {
487 InvalidChain,
489
490 MissingChannel,
492
493 InvalidChannelState,
495
496 NoOpenChannel,
498
499 NoMessageHandler,
501
502 OutboxFull,
504
505 InvalidMessagePayload,
507
508 InvalidMessageDestination,
510
511 MessageVerification(VerificationError),
513
514 MissingMessage,
516
517 WeightTagNotMatch,
520
521 BalanceOverflow,
523
524 BalanceUnderflow,
526
527 InvalidAllowedChain,
529
530 OperationNotAllowed,
532
533 NotDomainOwner,
535
536 ChainNotAllowed,
538
539 InsufficientBalance,
541
542 BalanceHold,
544
545 ChannelOwner,
547
548 BalanceUnlock,
550
551 InvalidChannelReserveFee,
553
554 InvalidMaxOutgoingMessages,
556
557 MessageCountOverflow,
559
560 MessageCountUnderflow,
562
563 FailedToNoteTransferIn,
565
566 FailedToNoteTransferOut,
568 }
569
570 #[pallet::call]
571 impl<T: Config> Pallet<T> {
572 #[pallet::call_index(0)]
576 #[pallet::weight(T::WeightInfo::initiate_channel())]
577 pub fn initiate_channel(origin: OriginFor<T>, dst_chain_id: ChainId) -> DispatchResult {
578 let owner = ensure_signed(origin)?;
579
580 let hold_id = T::HoldIdentifier::messenger_channel();
582 let amount = T::ChannelReserveFee::get();
583
584 ensure!(
586 T::Currency::reducible_balance(&owner, Preservation::Preserve, Fortitude::Polite)
587 >= amount,
588 Error::<T>::InsufficientBalance
589 );
590 T::Currency::hold(&hold_id, &owner, amount).map_err(|_| Error::<T>::BalanceHold)?;
591
592 let channel_open_params = ChannelOpenParamsV1 {
594 max_outgoing_messages: T::MaxOutgoingMessages::get(),
595 };
596 let channel_id = Self::do_init_channel(
597 dst_chain_id,
598 channel_open_params,
599 Some(owner.clone()),
600 true,
601 amount,
602 )?;
603
604 let payload = VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
605 ProtocolMessageRequest::ChannelOpen(ChannelOpenParamsV1 {
606 max_outgoing_messages: channel_open_params.max_outgoing_messages,
607 }),
608 )));
609
610 Self::new_outbox_message(T::SelfChainId::get(), dst_chain_id, channel_id, payload)?;
612
613 Ok(())
614 }
615
616 #[pallet::call_index(1)]
619 #[pallet::weight(T::WeightInfo::close_channel())]
620 pub fn close_channel(
621 origin: OriginFor<T>,
622 chain_id: ChainId,
623 channel_id: ChannelId,
624 ) -> DispatchResult {
625 let close_channel_by = match ensure_signed_or_root(origin)? {
628 Some(owner) => CloseChannelBy::Owner(owner),
629 None => CloseChannelBy::Sudo,
630 };
631 Self::do_close_channel(chain_id, channel_id, close_channel_by)?;
632
633 let payload = VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
634 ProtocolMessageRequest::ChannelClose,
635 )));
636
637 Self::new_outbox_message(T::SelfChainId::get(), chain_id, channel_id, payload)?;
638
639 Ok(())
640 }
641
642 #[pallet::call_index(2)]
644 #[pallet::weight(T::WeightInfo::relay_message().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag))
645 )]
646 pub fn relay_message(
647 origin: OriginFor<T>,
648 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
649 ) -> DispatchResult {
650 T::MessengerOrigin::ensure_origin(origin)?;
651 let inbox_msg = Inbox::<T>::take().ok_or(Error::<T>::MissingMessage)?;
652 Self::process_inbox_messages(inbox_msg, msg.weight_tag)?;
653 Ok(())
654 }
655
656 #[pallet::call_index(3)]
658 #[pallet::weight(T::WeightInfo::relay_message_response().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag))
659 )]
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[{dst_chain_id:?}] with Channel[{channel_id:?}]: {err:?}"
1215 );
1216 InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1217 })?;
1218
1219 Self::deposit_event(Event::InboxMessage {
1220 chain_id: msg.src_chain_id,
1221 channel_id: msg.channel_id,
1222 nonce: msg.nonce,
1223 });
1224 Inbox::<T>::put(msg);
1225 Ok(())
1226 }
1227
1228 pub fn validate_relay_message_response(
1229 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1230 consensus_state_root: StateRootOf<T>,
1231 ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1232 let next_nonce =
1234 match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1235 None => {
1237 log::error!("Unexpected inbox message response: {xdm:?}",);
1238 return Err(InvalidTransaction::Call.into());
1239 }
1240 Some(channel) => match channel.latest_response_received_message_nonce {
1241 None => Nonce::zero(),
1242 Some(last_nonce) => last_nonce.checked_add(Nonce::one()).ok_or(
1243 InvalidTransaction::Custom(
1244 crate::verification_errors::XDM_NONCE_OVERFLOW,
1245 ),
1246 )?,
1247 },
1248 };
1249
1250 let key = StorageKey(
1252 T::StorageKeys::inbox_responses_storage_key(
1253 xdm.src_chain_id,
1254 (T::SelfChainId::get(), xdm.channel_id, xdm.nonce),
1255 )
1256 .ok_or(UnknownTransaction::CannotLookup)?,
1257 );
1258
1259 let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1261
1262 if msg.nonce.cmp(&next_nonce) == Ordering::Less {
1264 return Err(InvalidTransaction::Stale.into());
1265 }
1266
1267 let validated_relay_msg = ValidatedRelayMessage {
1268 message: msg,
1269 next_nonce,
1270 should_init_channel: false,
1272 };
1273
1274 Ok(validated_relay_msg)
1275 }
1276
1277 pub(crate) fn pre_dispatch_relay_message_response(
1278 msg: Message<BalanceOf<T>>,
1279 ) -> Result<(), TransactionValidityError> {
1280 let (dst_chain_id, channel_id, nonce) = (msg.src_chain_id, msg.channel_id, msg.nonce);
1283 Channels::<T>::mutate(
1284 dst_chain_id,
1285 channel_id,
1286 |maybe_channel| -> sp_runtime::DispatchResult {
1287 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1288 channel.latest_response_received_message_nonce = Some(nonce);
1289 Ok(())
1290 },
1291 )
1292 .map_err(|err| { log::error!(
1293 "Failed to increment the next relay message response nonce for Chain[{dst_chain_id:?}] with Channel[{channel_id:?}]: {err:?}",
1294 );
1295 InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1296 })?;
1297
1298 Self::deposit_event(Event::OutboxMessageResponse {
1299 chain_id: msg.src_chain_id,
1300 channel_id: msg.channel_id,
1301 nonce: msg.nonce,
1302 });
1303
1304 OutboxResponses::<T>::put(msg);
1305 Ok(())
1306 }
1307
1308 pub(crate) fn do_verify_xdm(
1309 next_nonce: Nonce,
1310 storage_key: StorageKey,
1311 consensus_state_root: StateRootOf<T>,
1312 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1313 ) -> Result<Message<BalanceOf<T>>, TransactionValidityError> {
1314 let next_channel_id = NextChannelId::<T>::get(xdm.src_chain_id);
1316 ensure!(
1317 xdm.channel_id <= next_channel_id,
1318 InvalidTransaction::Custom(crate::verification_errors::INVALID_CHANNEL)
1319 );
1320
1321 ensure!(
1324 xdm.nonce >= next_nonce,
1325 InvalidTransaction::Custom(crate::verification_errors::INVALID_NONCE)
1326 );
1327
1328 let state_root = if let Some(domain_proof) = xdm.proof.domain_proof().clone()
1330 && let Some(domain_id) = xdm.src_chain_id.maybe_domain_chain()
1331 {
1332 let confirmed_domain_block_storage_key =
1333 T::StorageKeys::confirmed_domain_block_storage_key(domain_id)
1334 .ok_or(UnknownTransaction::CannotLookup)?;
1335
1336 *StorageProofVerifier::<T::Hashing>::get_decoded_value::<
1337 sp_domains::execution_receipt::ExecutionReceipt<
1338 BlockNumberFor<T>,
1339 T::Hash,
1340 BlockNumberFor<T>,
1341 T::Hash,
1342 BalanceOf<T>,
1343 >,
1344 >(
1345 &consensus_state_root,
1346 domain_proof,
1347 StorageKey(confirmed_domain_block_storage_key),
1348 )
1349 .map_err(|err| {
1350 log::error!(
1351 "Failed to verify storage proof for confirmed Domain block: {err:?}",
1352 );
1353 TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1354 })?
1355 .final_state_root()
1356 } else {
1357 consensus_state_root
1358 };
1359
1360 let msg =
1362 StorageProofVerifier::<T::Hashing>::get_decoded_value::<Message<BalanceOf<T>>>(
1363 &state_root,
1364 xdm.proof.message_proof(),
1365 storage_key,
1366 )
1367 .map_err(|err| {
1368 log::error!("Failed to verify storage proof for message: {err:?}");
1369 TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1370 })?;
1371
1372 Ok(msg)
1373 }
1374
1375 pub fn outbox_storage_key(message_key: MessageKey) -> Vec<u8> {
1376 Outbox::<T>::hashed_key_for(message_key)
1377 }
1378
1379 pub fn inbox_response_storage_key(message_key: MessageKey) -> Vec<u8> {
1380 InboxResponses::<T>::hashed_key_for(message_key)
1381 }
1382
1383 pub fn channel_storage_key(chain_id: ChainId, channel_id: ChannelId) -> Vec<u8> {
1384 Channels::<T>::hashed_key_for(chain_id, channel_id)
1385 }
1386
1387 pub fn domain_chains_allowlist_update(
1388 domain_id: DomainId,
1389 ) -> Option<DomainAllowlistUpdates> {
1390 DomainChainAllowlistUpdate::<T>::get(domain_id).filter(|updates| !updates.is_empty())
1391 }
1392
1393 pub fn domain_allow_list_update_storage_key(domain_id: DomainId) -> Vec<u8> {
1394 DomainChainAllowlistUpdate::<T>::hashed_key_for(domain_id)
1395 }
1396
1397 pub fn updated_channels() -> BTreeSet<(ChainId, ChannelId)> {
1398 UpdatedChannels::<T>::get()
1399 }
1400
1401 pub fn open_channels() -> BTreeSet<(ChainId, ChannelId)> {
1402 Channels::<T>::iter_keys().collect()
1403 }
1404
1405 pub fn channels_and_states() -> Vec<(ChainId, ChannelId, ChannelStateWithNonce)> {
1406 let keys: Vec<(ChainId, ChannelId)> = Channels::<T>::iter_keys().collect();
1407 keys.into_iter()
1408 .filter_map(|(chain_id, channel_id)| {
1409 Channels::<T>::get(chain_id, channel_id).map(|channel| {
1410 let state = channel.state;
1411 let state_with_nonce = match state {
1412 ChannelState::Initiated => ChannelStateWithNonce::Initiated,
1413 ChannelState::Open => ChannelStateWithNonce::Open,
1414 ChannelState::Closed => ChannelStateWithNonce::Closed {
1415 next_outbox_nonce: channel.next_outbox_nonce,
1416 next_inbox_nonce: channel.next_inbox_nonce,
1417 },
1418 };
1419
1420 (chain_id, channel_id, state_with_nonce)
1421 })
1422 })
1423 .collect()
1424 }
1425
1426 pub fn channel_nonce(chain_id: ChainId, channel_id: ChannelId) -> Option<ChannelNonce> {
1427 Channels::<T>::get(chain_id, channel_id).map(|channel| {
1428 let last_inbox_nonce = channel.next_inbox_nonce.checked_sub(U256::one());
1429 ChannelNonce {
1430 relay_msg_nonce: last_inbox_nonce,
1431 relay_response_msg_nonce: channel.latest_response_received_message_nonce,
1432 }
1433 })
1434 }
1435
1436 pub fn store_inbox_fee(
1437 src_chain_id: ChainId,
1438 message_id: MessageId,
1439 inbox_fees: BalanceOf<T>,
1440 ) -> DispatchResult {
1441 InboxFeesOnHold::<T>::mutate(|inbox_fees_on_hold| {
1442 *inbox_fees_on_hold = inbox_fees_on_hold
1443 .checked_add(&inbox_fees)
1444 .ok_or(Error::<T>::BalanceOverflow)?;
1445
1446 let imbalance = T::Currency::issue(inbox_fees);
1449 core::mem::forget(imbalance);
1450
1451 Ok::<(), Error<T>>(())
1452 })?;
1453
1454 InboxFee::<T>::insert((src_chain_id, message_id), inbox_fees);
1455
1456 if !T::NoteChainTransfer::note_transfer_in(inbox_fees, src_chain_id) {
1458 return Err(Error::<T>::FailedToNoteTransferIn.into());
1459 }
1460
1461 Ok(())
1462 }
1463
1464 pub fn store_outbox_fee(
1465 dst_chain_id: ChainId,
1466 message_id: MessageId,
1467 outbox_fees: BalanceOf<T>,
1468 inbox_fees: BalanceOf<T>,
1469 ) -> DispatchResult {
1470 OutboxFeesOnHold::<T>::mutate(|outbox_fees_on_hold| {
1471 *outbox_fees_on_hold = outbox_fees_on_hold
1472 .checked_add(&outbox_fees)
1473 .ok_or(Error::<T>::BalanceOverflow)?;
1474
1475 let imbalance = T::Currency::issue(outbox_fees);
1478 core::mem::forget(imbalance);
1479
1480 Ok::<(), Error<T>>(())
1481 })?;
1482
1483 OutboxFee::<T>::insert((dst_chain_id, message_id), outbox_fees);
1484
1485 if !T::NoteChainTransfer::note_transfer_out(inbox_fees, dst_chain_id) {
1487 return Err(Error::<T>::FailedToNoteTransferOut.into());
1488 }
1489
1490 Ok(())
1491 }
1492 }
1493}
1494
1495impl<T> Pallet<T>
1496where
1497 T: Config + CreateUnsigned<Call<T>>,
1498{
1499 pub fn outbox_message_unsigned(
1500 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1501 ) -> Option<T::Extrinsic> {
1502 let call = Call::relay_message { msg };
1503 Some(T::create_unsigned(call.into()))
1504 }
1505
1506 pub fn inbox_response_message_unsigned(
1507 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1508 ) -> Option<T::Extrinsic> {
1509 let call = Call::relay_message_response { msg };
1510 Some(T::create_unsigned(call.into()))
1511 }
1512
1513 pub fn first_outbox_message_nonce_to_relay(
1515 dst_chain_id: ChainId,
1516 channel_id: ChannelId,
1517 from_nonce: Nonce,
1518 ) -> Option<Nonce> {
1519 Self::first_relay_message(
1520 dst_chain_id,
1521 channel_id,
1522 from_nonce,
1523 Outbox::<T>::contains_key,
1524 )
1525 }
1526
1527 pub fn first_inbox_message_response_nonce_to_relay(
1529 dst_chain_id: ChainId,
1530 channel_id: ChannelId,
1531 from_nonce: Nonce,
1532 ) -> Option<Nonce> {
1533 Self::first_relay_message(
1534 dst_chain_id,
1535 channel_id,
1536 from_nonce,
1537 InboxResponses::<T>::contains_key,
1538 )
1539 }
1540
1541 fn first_relay_message<Check>(
1542 dst_chain_id: ChainId,
1543 channel_id: ChannelId,
1544 from_nonce: Nonce,
1545 check: Check,
1546 ) -> Option<Nonce>
1547 where
1548 Check: Fn((ChainId, ChannelId, Nonce)) -> bool,
1549 {
1550 let mut nonce = from_nonce;
1551 let to_nonce = from_nonce.saturating_add(MAX_FUTURE_ALLOWED_NONCES.into());
1552 while nonce <= to_nonce {
1553 if check((dst_chain_id, channel_id, nonce)) {
1554 return Some(nonce);
1555 }
1556
1557 nonce = nonce.saturating_add(Nonce::one())
1558 }
1559
1560 None
1561 }
1562}
1563
1564impl<T: Config> sp_domains::DomainBundleSubmitted for Pallet<T> {
1565 fn domain_bundle_submitted(domain_id: DomainId) {
1566 DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_updates| {
1570 if let Some(updates) = maybe_updates {
1571 updates.clear();
1572 }
1573 });
1574 }
1575}
1576
1577impl<T: Config> sp_domains::OnDomainInstantiated for Pallet<T> {
1578 fn on_domain_instantiated(domain_id: DomainId) {
1579 DomainChainAllowlistUpdate::<T>::insert(domain_id, DomainAllowlistUpdates::default());
1580 }
1581}