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;
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 {
205 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
206 type SelfChainId: Get<ChainId>;
208 fn get_endpoint_handler(endpoint: &Endpoint)
210 -> Option<Box<dyn EndpointHandler<MessageId>>>;
211 type Currency: Mutate<Self::AccountId>
213 + InspectHold<Self::AccountId>
214 + MutateHold<Self::AccountId>
215 + Balanced<Self::AccountId>;
216 type WeightInfo: WeightInfo;
218 type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
220 type AdjustedWeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
223 #[pallet::constant]
226 type FeeMultiplier: Get<u32>;
227 type OnXDMRewards: OnXDMRewards<BalanceOf<Self>>;
229 type MmrHash: Parameter + Member + Default + Clone;
231 type MmrProofVerifier: MmrProofVerifier<Self::MmrHash, BlockNumberFor<Self>, StateRootOf<Self>>;
233 type StorageKeys: StorageKeys;
235 type DomainOwner: DomainOwner<Self::AccountId>;
237 type HoldIdentifier: HoldIdentifier<Self>;
239 #[pallet::constant]
241 type ChannelReserveFee: Get<BalanceOf<Self>>;
242 #[pallet::constant]
245 type ChannelInitReservePortion: Get<Perbill>;
246 type DomainRegistration: DomainRegistration;
248 #[pallet::constant]
250 type MaxOutgoingMessages: Get<u32>;
251 type MessengerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
253 type NoteChainTransfer: NoteChainTransfer<BalanceOf<Self>>;
255 type ExtensionWeightInfo: ExtensionWeightInfo;
257 }
258
259 #[pallet::pallet]
261 #[pallet::without_storage_info]
262 #[pallet::storage_version(STORAGE_VERSION)]
263 pub struct Pallet<T>(_);
264
265 #[pallet::storage]
267 #[pallet::getter(fn next_channel_id)]
268 pub(super) type NextChannelId<T: Config> =
269 StorageMap<_, Identity, ChainId, ChannelId, ValueQuery>;
270
271 #[pallet::storage]
274 #[pallet::getter(fn channels)]
275 pub(super) type Channels<T: Config> = StorageDoubleMap<
276 _,
277 Identity,
278 ChainId,
279 Identity,
280 ChannelId,
281 Channel<BalanceOf<T>, T::AccountId>,
282 OptionQuery,
283 >;
284
285 #[pallet::storage]
288 #[pallet::getter(fn inbox)]
289 pub(super) type Inbox<T: Config> = StorageValue<_, Message<BalanceOf<T>>, OptionQuery>;
290
291 #[pallet::storage]
295 #[pallet::getter(fn inbox_fees)]
296 pub(super) type InboxFee<T: Config> =
297 StorageMap<_, Identity, (ChainId, MessageId), BalanceOf<T>, OptionQuery>;
298
299 #[pallet::storage]
302 #[pallet::getter(fn outbox_fees)]
303 pub(super) type OutboxFee<T: Config> =
304 StorageMap<_, Identity, (ChainId, MessageId), BalanceOf<T>, OptionQuery>;
305
306 #[pallet::storage]
309 #[pallet::getter(fn inbox_responses)]
310 pub(super) type InboxResponses<T: Config> =
311 StorageMap<_, Identity, (ChainId, ChannelId, Nonce), Message<BalanceOf<T>>, OptionQuery>;
312
313 #[pallet::storage]
316 #[pallet::getter(fn outbox)]
317 pub(super) type Outbox<T: Config> =
318 StorageMap<_, Identity, (ChainId, ChannelId, Nonce), Message<BalanceOf<T>>, OptionQuery>;
319
320 #[pallet::storage]
322 #[pallet::getter(fn outbox_message_count)]
323 pub(super) type OutboxMessageCount<T: Config> =
324 StorageMap<_, Identity, (ChainId, ChannelId), u32, ValueQuery>;
325
326 #[pallet::storage]
329 #[pallet::getter(fn outbox_responses)]
330 pub(super) type OutboxResponses<T: Config> =
331 StorageValue<_, Message<BalanceOf<T>>, OptionQuery>;
332
333 #[pallet::storage]
335 #[pallet::getter(fn outbox_message_weight_tags)]
336 pub(super) type OutboxMessageWeightTags<T: Config> =
337 StorageMap<_, Identity, (ChainId, MessageId), MessageWeightTag>;
338
339 #[pallet::storage]
341 #[pallet::getter(fn inbox_response_message_weight_tags)]
342 pub(super) type InboxResponseMessageWeightTags<T: Config> =
343 StorageMap<_, Identity, (ChainId, MessageId), MessageWeightTag>;
344
345 #[pallet::storage]
347 #[pallet::getter(fn chain_allowlist)]
348 pub(super) type ChainAllowlist<T: Config> = StorageValue<_, BTreeSet<ChainId>, ValueQuery>;
349
350 #[pallet::storage]
354 #[pallet::getter(fn domain_chain_allowlist_updates)]
355 pub(super) type DomainChainAllowlistUpdate<T: Config> =
356 StorageMap<_, Identity, DomainId, DomainAllowlistUpdates, OptionQuery>;
357
358 #[pallet::storage]
361 pub(super) type UpdatedChannels<T: Config> =
362 StorageValue<_, BTreeSet<(ChainId, ChannelId)>, ValueQuery>;
363
364 #[pallet::storage]
371 pub(super) type InboxFeesOnHold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
372
373 #[pallet::storage]
380 pub(super) type OutboxFeesOnHold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
381
382 #[pallet::origin]
383 pub type Origin = RawOrigin;
384
385 #[pallet::event]
387 #[pallet::generate_deposit(pub (super) fn deposit_event)]
388 pub enum Event<T: Config> {
389 ChannelInitiated {
391 chain_id: ChainId,
393 channel_id: ChannelId,
395 },
396
397 ChannelClosed {
399 chain_id: ChainId,
401 channel_id: ChannelId,
403 },
404
405 ChannelOpen {
407 chain_id: ChainId,
409 channel_id: ChannelId,
411 },
412
413 OutboxMessage {
415 chain_id: ChainId,
416 channel_id: ChannelId,
417 nonce: Nonce,
418 },
419
420 OutboxMessageResponse {
422 chain_id: ChainId,
424 channel_id: ChannelId,
426 nonce: Nonce,
427 },
428
429 OutboxMessageResult {
431 chain_id: ChainId,
432 channel_id: ChannelId,
433 nonce: Nonce,
434 result: OutboxMessageResult,
435 },
436
437 InboxMessage {
439 chain_id: ChainId,
440 channel_id: ChannelId,
441 nonce: Nonce,
442 },
443
444 InboxMessageResponse {
446 chain_id: ChainId,
448 channel_id: ChannelId,
450 nonce: Nonce,
451 },
452 }
453
454 #[pallet::hooks]
455 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
456 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
457 UpdatedChannels::<T>::take();
458 T::DbWeight::get().reads_writes(0, 1)
459 }
460 }
461
462 #[pallet::validate_unsigned]
463 impl<T: Config> ValidateUnsigned for Pallet<T> {
464 type Call = Call<T>;
465
466 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
467 match call {
468 Call::update_domain_allowlist { .. } => Ok(()),
470 _ => Err(InvalidTransaction::Call.into()),
471 }
472 }
473
474 fn validate_unsigned(
476 _source: TransactionSource,
477 _call: &Self::Call,
478 ) -> TransactionValidity {
479 InvalidTransaction::Call.into()
480 }
481 }
482
483 #[pallet::error]
485 pub enum Error<T> {
486 InvalidChain,
488
489 MissingChannel,
491
492 InvalidChannelState,
494
495 NoOpenChannel,
497
498 NoMessageHandler,
500
501 OutboxFull,
503
504 InvalidMessagePayload,
506
507 InvalidMessageDestination,
509
510 MessageVerification(VerificationError),
512
513 MissingMessage,
515
516 WeightTagNotMatch,
519
520 BalanceOverflow,
522
523 BalanceUnderflow,
525
526 InvalidAllowedChain,
528
529 OperationNotAllowed,
531
532 NotDomainOwner,
534
535 ChainNotAllowed,
537
538 InsufficientBalance,
540
541 BalanceHold,
543
544 ChannelOwner,
546
547 BalanceUnlock,
549
550 InvalidChannelReserveFee,
552
553 InvalidMaxOutgoingMessages,
555
556 MessageCountOverflow,
558
559 MessageCountUnderflow,
561
562 FailedToNoteTransferIn,
564
565 FailedToNoteTransferOut,
567 }
568
569 #[pallet::call]
570 impl<T: Config> Pallet<T> {
571 #[pallet::call_index(0)]
575 #[pallet::weight(T::WeightInfo::initiate_channel())]
576 pub fn initiate_channel(origin: OriginFor<T>, dst_chain_id: ChainId) -> DispatchResult {
577 let owner = ensure_signed(origin)?;
578
579 let hold_id = T::HoldIdentifier::messenger_channel();
581 let amount = T::ChannelReserveFee::get();
582
583 ensure!(
585 T::Currency::reducible_balance(&owner, Preservation::Preserve, Fortitude::Polite)
586 >= amount,
587 Error::<T>::InsufficientBalance
588 );
589 T::Currency::hold(&hold_id, &owner, amount).map_err(|_| Error::<T>::BalanceHold)?;
590
591 let channel_open_params = ChannelOpenParamsV1 {
593 max_outgoing_messages: T::MaxOutgoingMessages::get(),
594 };
595 let channel_id = Self::do_init_channel(
596 dst_chain_id,
597 channel_open_params,
598 Some(owner.clone()),
599 true,
600 amount,
601 )?;
602
603 let payload = VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
604 ProtocolMessageRequest::ChannelOpen(ChannelOpenParamsV1 {
605 max_outgoing_messages: channel_open_params.max_outgoing_messages,
606 }),
607 )));
608
609 Self::new_outbox_message(T::SelfChainId::get(), dst_chain_id, channel_id, payload)?;
611
612 Ok(())
613 }
614
615 #[pallet::call_index(1)]
618 #[pallet::weight(T::WeightInfo::close_channel())]
619 pub fn close_channel(
620 origin: OriginFor<T>,
621 chain_id: ChainId,
622 channel_id: ChannelId,
623 ) -> DispatchResult {
624 let close_channel_by = match ensure_signed_or_root(origin)? {
627 Some(owner) => CloseChannelBy::Owner(owner),
628 None => CloseChannelBy::Sudo,
629 };
630 Self::do_close_channel(chain_id, channel_id, close_channel_by)?;
631
632 let payload = VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
633 ProtocolMessageRequest::ChannelClose,
634 )));
635
636 Self::new_outbox_message(T::SelfChainId::get(), chain_id, channel_id, payload)?;
637
638 Ok(())
639 }
640
641 #[pallet::call_index(2)]
643 #[pallet::weight(T::WeightInfo::relay_message().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag))
644 )]
645 pub fn relay_message(
646 origin: OriginFor<T>,
647 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
648 ) -> DispatchResult {
649 T::MessengerOrigin::ensure_origin(origin)?;
650 let inbox_msg = Inbox::<T>::take().ok_or(Error::<T>::MissingMessage)?;
651 Self::process_inbox_messages(inbox_msg, msg.weight_tag)?;
652 Ok(())
653 }
654
655 #[pallet::call_index(3)]
657 #[pallet::weight(T::WeightInfo::relay_message_response().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag))
658 )]
659 pub fn relay_message_response(
660 origin: OriginFor<T>,
661 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
662 ) -> DispatchResult {
663 T::MessengerOrigin::ensure_origin(origin)?;
664 let outbox_resp_msg = OutboxResponses::<T>::take().ok_or(Error::<T>::MissingMessage)?;
665 Self::process_outbox_message_responses(outbox_resp_msg, msg.weight_tag)?;
666 Ok(())
667 }
668
669 #[pallet::call_index(4)]
671 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1))]
672 pub fn update_consensus_chain_allowlist(
673 origin: OriginFor<T>,
674 update: ChainAllowlistUpdate,
675 ) -> DispatchResult {
676 ensure_root(origin)?;
677 ensure!(
678 T::SelfChainId::get().is_consensus_chain(),
679 Error::<T>::OperationNotAllowed
680 );
681
682 ensure!(
683 update.chain_id() != T::SelfChainId::get(),
684 Error::<T>::InvalidAllowedChain
685 );
686
687 if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update {
688 ensure!(
689 T::DomainRegistration::is_domain_registered(domain_id),
690 Error::<T>::InvalidChain
691 );
692 }
693
694 ChainAllowlist::<T>::mutate(|list| match update {
695 ChainAllowlistUpdate::Add(chain_id) => list.insert(chain_id),
696 ChainAllowlistUpdate::Remove(chain_id) => list.remove(&chain_id),
697 });
698 Ok(())
699 }
700
701 #[pallet::call_index(5)]
703 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1))]
704 pub fn initiate_domain_update_chain_allowlist(
705 origin: OriginFor<T>,
706 domain_id: DomainId,
707 update: ChainAllowlistUpdate,
708 ) -> DispatchResult {
709 let domain_owner = ensure_signed(origin)?;
710 ensure!(
711 T::DomainOwner::is_domain_owner(domain_id, domain_owner),
712 Error::<T>::NotDomainOwner
713 );
714
715 ensure!(
716 T::SelfChainId::get().is_consensus_chain(),
717 Error::<T>::OperationNotAllowed
718 );
719
720 if let Some(dst_domain_id) = update.chain_id().maybe_domain_chain() {
721 ensure!(dst_domain_id != domain_id, Error::<T>::InvalidAllowedChain);
722 }
723
724 if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update {
725 ensure!(
726 T::DomainRegistration::is_domain_registered(domain_id),
727 Error::<T>::InvalidChain
728 );
729 }
730
731 DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_domain_updates| {
732 let mut domain_updates = maybe_domain_updates.take().unwrap_or_default();
733 match update {
734 ChainAllowlistUpdate::Add(chain_id) => {
735 domain_updates.remove_chains.remove(&chain_id);
736 domain_updates.allow_chains.insert(chain_id);
737 }
738 ChainAllowlistUpdate::Remove(chain_id) => {
739 domain_updates.allow_chains.remove(&chain_id);
740 domain_updates.remove_chains.insert(chain_id);
741 }
742 }
743
744 *maybe_domain_updates = Some(domain_updates)
745 });
746 Ok(())
747 }
748
749 #[pallet::call_index(6)]
751 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Mandatory))]
752 pub fn update_domain_allowlist(
753 origin: OriginFor<T>,
754 updates: DomainAllowlistUpdates,
755 ) -> DispatchResult {
756 ensure_none(origin)?;
757 ensure!(
758 !T::SelfChainId::get().is_consensus_chain(),
759 Error::<T>::OperationNotAllowed
760 );
761
762 let DomainAllowlistUpdates {
763 allow_chains,
764 remove_chains,
765 } = updates;
766
767 ChainAllowlist::<T>::mutate(|list| {
768 remove_chains.into_iter().for_each(|chain_id| {
770 list.remove(&chain_id);
771 });
772
773 allow_chains.into_iter().for_each(|chain_id| {
775 list.insert(chain_id);
776 });
777 });
778
779 Ok(())
780 }
781 }
782
783 #[pallet::inherent]
784 impl<T: Config> ProvideInherent for Pallet<T> {
785 type Call = Call<T>;
786 type Error = InherentError;
787 const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
788
789 fn create_inherent(data: &InherentData) -> Option<Self::Call> {
790 let inherent_data = data
791 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
792 .expect("Messenger inherent data not correctly encoded")
793 .expect("Messenger inherent data must be provided");
794
795 inherent_data
796 .maybe_updates
797 .map(|updates| Call::update_domain_allowlist { updates })
798 }
799
800 fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
801 let inherent_data = data
802 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
803 .expect("Messenger inherent data not correctly encoded")
804 .expect("Messenger inherent data must be provided");
805
806 Ok(if inherent_data.maybe_updates.is_none() {
807 None
808 } else {
809 Some(InherentError::MissingAllowlistUpdates)
810 })
811 }
812
813 fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
814 let inherent_data = data
815 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
816 .expect("Messenger inherent data not correctly encoded")
817 .expect("Messenger inherent data must be provided");
818
819 if let Some(provided_updates) = inherent_data.maybe_updates {
820 if let Call::update_domain_allowlist { updates } = call
821 && updates != &provided_updates
822 {
823 return Err(InherentError::IncorrectAllowlistUpdates);
824 }
825 } else {
826 return Err(InherentError::MissingAllowlistUpdates);
827 }
828
829 Ok(())
830 }
831
832 fn is_inherent(call: &Self::Call) -> bool {
833 matches!(call, Call::update_domain_allowlist { .. })
834 }
835 }
836
837 impl<T: Config> Sender<T::AccountId> for Pallet<T> {
838 type MessageId = MessageId;
839
840 fn send_message(
841 sender: &T::AccountId,
842 dst_chain_id: ChainId,
843 req: EndpointRequest,
844 ) -> Result<Self::MessageId, DispatchError> {
845 let allowed_chains = ChainAllowlist::<T>::get();
846 ensure!(
847 allowed_chains.contains(&dst_chain_id),
848 Error::<T>::ChainNotAllowed
849 );
850
851 let channel_id =
852 Self::get_open_channel_for_chain(dst_chain_id).ok_or(Error::<T>::NoOpenChannel)?;
853
854 let src_endpoint = req.src_endpoint.clone();
855
856 let message_id = {
857 let collected_fee = Self::collect_fees_for_message_v1(sender, &src_endpoint)?;
859 let src_chain_fee = collected_fee.src_chain_fee;
860 let dst_chain_fee = collected_fee.dst_chain_fee;
861 let nonce = Self::new_outbox_message(
862 T::SelfChainId::get(),
863 dst_chain_id,
864 channel_id,
865 VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(
866 EndpointRequestWithCollectedFee { req, collected_fee },
867 ))),
868 )?;
869
870 let message_id = (channel_id, nonce);
872 Self::store_outbox_fee(dst_chain_id, message_id, src_chain_fee, dst_chain_fee)?;
873 message_id
874 };
875
876 Ok(message_id)
877 }
878
879 #[cfg(feature = "runtime-benchmarks")]
882 fn unchecked_open_channel(dst_chain_id: ChainId) -> Result<(), DispatchError> {
883 let init_params = ChannelOpenParamsV1 {
884 max_outgoing_messages: 100,
885 };
886 ChainAllowlist::<T>::mutate(|list| list.insert(dst_chain_id));
887 let channel_id =
888 Self::do_init_channel(dst_chain_id, init_params, None, true, Zero::zero())?;
889 Self::do_open_channel(dst_chain_id, channel_id)?;
890 Ok(())
891 }
892 }
893
894 impl<T: Config> Pallet<T> {
895 fn message_weight(weight_tag: &MessageWeightTag) -> Weight {
897 match weight_tag {
898 MessageWeightTag::ProtocolChannelOpen => T::WeightInfo::do_open_channel(),
899 MessageWeightTag::ProtocolChannelClose => T::WeightInfo::do_close_channel(),
900 MessageWeightTag::EndpointRequest(endpoint) => {
901 T::get_endpoint_handler(endpoint)
902 .map(|endpoint_handler| endpoint_handler.message_weight())
903 .unwrap_or(Weight::zero())
905 }
906 MessageWeightTag::EndpointResponse(endpoint) => {
907 T::get_endpoint_handler(endpoint)
908 .map(|endpoint_handler| endpoint_handler.message_response_weight())
909 .unwrap_or(Weight::zero())
911 }
912 MessageWeightTag::None => Weight::zero(),
913 }
914 }
915
916 pub fn get_open_channel_for_chain(dst_chain_id: ChainId) -> Option<ChannelId> {
918 let mut next_channel_id = NextChannelId::<T>::get(dst_chain_id);
919
920 while let Some(channel_id) = next_channel_id.checked_sub(ChannelId::one()) {
923 let message_count = OutboxMessageCount::<T>::get((dst_chain_id, channel_id));
924 if let Some(channel) = Channels::<T>::get(dst_chain_id, channel_id)
925 && channel.state == ChannelState::Open
926 && message_count < channel.max_outgoing_messages
927 {
928 return Some(channel_id);
929 }
930
931 next_channel_id = channel_id
932 }
933
934 None
935 }
936
937 pub(crate) fn do_open_channel(chain_id: ChainId, channel_id: ChannelId) -> DispatchResult {
939 Channels::<T>::try_mutate(chain_id, channel_id, |maybe_channel| -> DispatchResult {
940 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
941
942 ensure!(
943 channel.state == ChannelState::Initiated,
944 Error::<T>::InvalidChannelState
945 );
946
947 channel.state = ChannelState::Open;
948 Ok(())
949 })?;
950
951 Self::deposit_event(Event::ChannelOpen {
952 chain_id,
953 channel_id,
954 });
955
956 Ok(())
957 }
958
959 pub(crate) fn do_close_channel(
960 chain_id: ChainId,
961 channel_id: ChannelId,
962 close_channel_by: CloseChannelBy<T::AccountId>,
963 ) -> DispatchResult {
964 Channels::<T>::try_mutate(chain_id, channel_id, |maybe_channel| -> DispatchResult {
965 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
966
967 ensure!(
968 channel.state != ChannelState::Closed,
969 Error::<T>::InvalidChannelState
970 );
971
972 if let CloseChannelBy::Owner(owner) = close_channel_by {
973 ensure!(channel.maybe_owner == Some(owner), Error::<T>::ChannelOwner);
974 }
975
976 if let Some(owner) = &channel.maybe_owner {
977 let hold_id = T::HoldIdentifier::messenger_channel();
978 let locked_amount = channel.channel_reserve_fee;
979 let (amount_to_release, maybe_amount_to_burn) = {
980 if channel.state == ChannelState::Open {
981 (locked_amount, None)
982 } else {
983 let protocol_fee = T::ChannelInitReservePortion::get() * locked_amount;
984 let release_amount = locked_amount.saturating_sub(protocol_fee);
985 (release_amount, Some(protocol_fee))
986 }
987 };
988
989 with_storage_layer(|| {
990 if let Some(protocol_fee) = maybe_amount_to_burn {
991 T::Currency::burn_held(
992 &hold_id,
993 owner,
994 protocol_fee,
995 Precision::Exact,
996 Fortitude::Force,
997 )?;
998 T::OnXDMRewards::on_chain_protocol_fees(chain_id, protocol_fee);
999 }
1000
1001 T::Currency::release(&hold_id, owner, amount_to_release, Precision::Exact)
1002 .map_err(|_| Error::<T>::BalanceUnlock)?;
1003
1004 Ok::<(), DispatchError>(())
1005 })?;
1006 }
1007
1008 channel.state = ChannelState::Closed;
1009 Ok(())
1010 })?;
1011
1012 Self::deposit_event(Event::ChannelClosed {
1013 chain_id,
1014 channel_id,
1015 });
1016
1017 Ok(())
1018 }
1019
1020 pub(crate) fn do_init_channel(
1021 dst_chain_id: ChainId,
1022 init_params: ChannelOpenParamsV1,
1023 maybe_owner: Option<T::AccountId>,
1024 check_allowlist: bool,
1025 channel_reserve_fee: BalanceOf<T>,
1026 ) -> Result<ChannelId, DispatchError> {
1027 ensure!(
1028 T::SelfChainId::get() != dst_chain_id,
1029 Error::<T>::InvalidChain,
1030 );
1031
1032 ensure!(
1034 init_params.max_outgoing_messages >= 1u32,
1035 Error::<T>::InvalidMaxOutgoingMessages
1036 );
1037
1038 ensure!(
1041 maybe_owner.is_none() || !channel_reserve_fee.is_zero(),
1042 Error::<T>::InvalidChannelReserveFee,
1043 );
1044
1045 if check_allowlist {
1046 let chain_allowlist = ChainAllowlist::<T>::get();
1047 ensure!(
1048 chain_allowlist.contains(&dst_chain_id),
1049 Error::<T>::ChainNotAllowed
1050 );
1051 }
1052
1053 let channel_id = NextChannelId::<T>::get(dst_chain_id);
1054 let next_channel_id = channel_id
1055 .checked_add(U256::one())
1056 .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?;
1057
1058 Channels::<T>::insert(
1059 dst_chain_id,
1060 channel_id,
1061 Channel {
1062 channel_id,
1063 state: ChannelState::Initiated,
1064 next_inbox_nonce: Default::default(),
1065 next_outbox_nonce: Default::default(),
1066 latest_response_received_message_nonce: Default::default(),
1067 max_outgoing_messages: init_params.max_outgoing_messages,
1068 maybe_owner,
1069 channel_reserve_fee,
1070 },
1071 );
1072
1073 NextChannelId::<T>::insert(dst_chain_id, next_channel_id);
1074 Self::deposit_event(Event::ChannelInitiated {
1075 chain_id: dst_chain_id,
1076 channel_id,
1077 });
1078 Ok(channel_id)
1079 }
1080
1081 pub fn validate_relay_message(
1082 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1083 consensus_state_root: StateRootOf<T>,
1084 ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1085 let (next_nonce, maybe_channel) =
1086 match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1087 None => {
1088 log::debug!(
1091 "Initiating new channel: {:?} to chain: {:?}",
1092 xdm.channel_id,
1093 xdm.src_chain_id
1094 );
1095 (Nonce::zero(), None)
1096 }
1097 Some(channel) => {
1098 log::debug!(
1099 "Message to channel: {:?} from chain: {:?}",
1100 xdm.channel_id,
1101 xdm.src_chain_id
1102 );
1103 (channel.next_inbox_nonce, Some(channel))
1104 }
1105 };
1106
1107 let key = StorageKey(
1109 T::StorageKeys::outbox_storage_key(
1110 xdm.src_chain_id,
1111 (T::SelfChainId::get(), xdm.channel_id, xdm.nonce),
1112 )
1113 .ok_or(UnknownTransaction::CannotLookup)?,
1114 );
1115
1116 let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1118
1119 let is_valid_call = match &msg.payload {
1120 VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(req))) => {
1121 match req {
1122 ProtocolMessageRequest::ChannelOpen(_) => maybe_channel.is_none(),
1124 ProtocolMessageRequest::ChannelClose => {
1126 if let Some(ref channel) = maybe_channel {
1127 !(channel.state == ChannelState::Closed)
1128 } else {
1129 false
1130 }
1131 }
1132 }
1133 }
1134 VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(_))) => {
1140 if let Some(ref channel) = maybe_channel {
1141 !(channel.state == ChannelState::Initiated)
1142 } else {
1143 false
1144 }
1145 }
1146 _ => false,
1148 };
1149
1150 if !is_valid_call {
1151 log::error!("Unexpected XDM message: {msg:?}",);
1152 return Err(InvalidTransaction::Call.into());
1153 }
1154
1155 if msg.nonce.cmp(&next_nonce) == Ordering::Less {
1157 return Err(InvalidTransaction::Stale.into());
1158 }
1159
1160 let validated_relay_msg = ValidatedRelayMessage {
1161 message: msg,
1162 should_init_channel: maybe_channel.is_none(),
1163 next_nonce,
1164 };
1165
1166 Ok(validated_relay_msg)
1167 }
1168
1169 pub(crate) fn pre_dispatch_relay_message(
1170 msg: Message<BalanceOf<T>>,
1171 should_init_channel: bool,
1172 ) -> Result<(), TransactionValidityError> {
1173 if should_init_channel {
1174 if let VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
1175 ProtocolMessageRequest::ChannelOpen(params),
1176 ))) = msg.payload
1177 {
1178 Self::do_init_channel(msg.src_chain_id, params, None, false, Zero::zero())
1182 .map_err(|err| {
1183 log::error!(
1184 "Error initiating channel: {:?} with chain: {:?}: {:?}",
1185 msg.channel_id,
1186 msg.src_chain_id,
1187 err
1188 );
1189 InvalidTransaction::Call
1190 })?;
1191 } else {
1192 log::error!("Unexpected call instead of channel open request: {msg:?}");
1193 return Err(InvalidTransaction::Call.into());
1194 }
1195 }
1196
1197 let (dst_chain_id, channel_id, nonce) = (msg.src_chain_id, msg.channel_id, msg.nonce);
1198 Channels::<T>::mutate(
1201 dst_chain_id,
1202 channel_id,
1203 |maybe_channel| -> sp_runtime::DispatchResult {
1204 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1205 channel.next_inbox_nonce = nonce
1206 .checked_add(Nonce::one())
1207 .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?;
1208 Ok(())
1209 },
1210 )
1211 .map_err(|err| {
1212 log::error!(
1213 "Failed to increment the next relay message nonce for Chain[{dst_chain_id:?}] with Channel[{channel_id:?}]: {err:?}"
1214 );
1215 InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1216 })?;
1217
1218 Self::deposit_event(Event::InboxMessage {
1219 chain_id: msg.src_chain_id,
1220 channel_id: msg.channel_id,
1221 nonce: msg.nonce,
1222 });
1223 Inbox::<T>::put(msg);
1224 Ok(())
1225 }
1226
1227 pub fn validate_relay_message_response(
1228 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1229 consensus_state_root: StateRootOf<T>,
1230 ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1231 let next_nonce =
1233 match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1234 None => {
1236 log::error!("Unexpected inbox message response: {xdm:?}",);
1237 return Err(InvalidTransaction::Call.into());
1238 }
1239 Some(channel) => match channel.latest_response_received_message_nonce {
1240 None => Nonce::zero(),
1241 Some(last_nonce) => last_nonce.checked_add(Nonce::one()).ok_or(
1242 InvalidTransaction::Custom(
1243 crate::verification_errors::XDM_NONCE_OVERFLOW,
1244 ),
1245 )?,
1246 },
1247 };
1248
1249 let key = StorageKey(
1251 T::StorageKeys::inbox_responses_storage_key(
1252 xdm.src_chain_id,
1253 (T::SelfChainId::get(), xdm.channel_id, xdm.nonce),
1254 )
1255 .ok_or(UnknownTransaction::CannotLookup)?,
1256 );
1257
1258 let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1260
1261 if msg.nonce.cmp(&next_nonce) == Ordering::Less {
1263 return Err(InvalidTransaction::Stale.into());
1264 }
1265
1266 let validated_relay_msg = ValidatedRelayMessage {
1267 message: msg,
1268 next_nonce,
1269 should_init_channel: false,
1271 };
1272
1273 Ok(validated_relay_msg)
1274 }
1275
1276 pub(crate) fn pre_dispatch_relay_message_response(
1277 msg: Message<BalanceOf<T>>,
1278 ) -> Result<(), TransactionValidityError> {
1279 let (dst_chain_id, channel_id, nonce) = (msg.src_chain_id, msg.channel_id, msg.nonce);
1282 Channels::<T>::mutate(
1283 dst_chain_id,
1284 channel_id,
1285 |maybe_channel| -> sp_runtime::DispatchResult {
1286 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1287 channel.latest_response_received_message_nonce = Some(nonce);
1288 Ok(())
1289 },
1290 )
1291 .map_err(|err| { log::error!(
1292 "Failed to increment the next relay message response nonce for Chain[{dst_chain_id:?}] with Channel[{channel_id:?}]: {err:?}",
1293 );
1294 InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1295 })?;
1296
1297 Self::deposit_event(Event::OutboxMessageResponse {
1298 chain_id: msg.src_chain_id,
1299 channel_id: msg.channel_id,
1300 nonce: msg.nonce,
1301 });
1302
1303 OutboxResponses::<T>::put(msg);
1304 Ok(())
1305 }
1306
1307 pub(crate) fn do_verify_xdm(
1308 next_nonce: Nonce,
1309 storage_key: StorageKey,
1310 consensus_state_root: StateRootOf<T>,
1311 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1312 ) -> Result<Message<BalanceOf<T>>, TransactionValidityError> {
1313 let next_channel_id = NextChannelId::<T>::get(xdm.src_chain_id);
1315 ensure!(
1316 xdm.channel_id <= next_channel_id,
1317 InvalidTransaction::Custom(crate::verification_errors::INVALID_CHANNEL)
1318 );
1319
1320 ensure!(
1323 xdm.nonce >= next_nonce,
1324 InvalidTransaction::Custom(crate::verification_errors::INVALID_NONCE)
1325 );
1326
1327 let state_root = if let Some(domain_proof) = xdm.proof.domain_proof().clone()
1329 && let Some(domain_id) = xdm.src_chain_id.maybe_domain_chain()
1330 {
1331 let confirmed_domain_block_storage_key =
1332 T::StorageKeys::confirmed_domain_block_storage_key(domain_id)
1333 .ok_or(UnknownTransaction::CannotLookup)?;
1334
1335 *StorageProofVerifier::<T::Hashing>::get_decoded_value::<
1336 sp_domains::execution_receipt::ExecutionReceipt<
1337 BlockNumberFor<T>,
1338 T::Hash,
1339 BlockNumberFor<T>,
1340 T::Hash,
1341 BalanceOf<T>,
1342 >,
1343 >(
1344 &consensus_state_root,
1345 domain_proof,
1346 StorageKey(confirmed_domain_block_storage_key),
1347 )
1348 .map_err(|err| {
1349 log::error!(
1350 "Failed to verify storage proof for confirmed Domain block: {err:?}",
1351 );
1352 TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1353 })?
1354 .final_state_root()
1355 } else {
1356 consensus_state_root
1357 };
1358
1359 let msg =
1361 StorageProofVerifier::<T::Hashing>::get_decoded_value::<Message<BalanceOf<T>>>(
1362 &state_root,
1363 xdm.proof.message_proof(),
1364 storage_key,
1365 )
1366 .map_err(|err| {
1367 log::error!("Failed to verify storage proof for message: {err:?}");
1368 TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1369 })?;
1370
1371 Ok(msg)
1372 }
1373
1374 pub fn outbox_storage_key(message_key: MessageKey) -> Vec<u8> {
1375 Outbox::<T>::hashed_key_for(message_key)
1376 }
1377
1378 pub fn inbox_response_storage_key(message_key: MessageKey) -> Vec<u8> {
1379 InboxResponses::<T>::hashed_key_for(message_key)
1380 }
1381
1382 pub fn channel_storage_key(chain_id: ChainId, channel_id: ChannelId) -> Vec<u8> {
1383 Channels::<T>::hashed_key_for(chain_id, channel_id)
1384 }
1385
1386 pub fn domain_chains_allowlist_update(
1387 domain_id: DomainId,
1388 ) -> Option<DomainAllowlistUpdates> {
1389 DomainChainAllowlistUpdate::<T>::get(domain_id).filter(|updates| !updates.is_empty())
1390 }
1391
1392 pub fn domain_allow_list_update_storage_key(domain_id: DomainId) -> Vec<u8> {
1393 DomainChainAllowlistUpdate::<T>::hashed_key_for(domain_id)
1394 }
1395
1396 pub fn updated_channels() -> BTreeSet<(ChainId, ChannelId)> {
1397 UpdatedChannels::<T>::get()
1398 }
1399
1400 pub fn open_channels() -> BTreeSet<(ChainId, ChannelId)> {
1401 Channels::<T>::iter_keys().collect()
1402 }
1403
1404 pub fn channels_and_states() -> Vec<(ChainId, ChannelId, ChannelStateWithNonce)> {
1405 let keys: Vec<(ChainId, ChannelId)> = Channels::<T>::iter_keys().collect();
1406 keys.into_iter()
1407 .filter_map(|(chain_id, channel_id)| {
1408 Channels::<T>::get(chain_id, channel_id).map(|channel| {
1409 let state = channel.state;
1410 let state_with_nonce = match state {
1411 ChannelState::Initiated => ChannelStateWithNonce::Initiated,
1412 ChannelState::Open => ChannelStateWithNonce::Open,
1413 ChannelState::Closed => ChannelStateWithNonce::Closed {
1414 next_outbox_nonce: channel.next_outbox_nonce,
1415 next_inbox_nonce: channel.next_inbox_nonce,
1416 },
1417 };
1418
1419 (chain_id, channel_id, state_with_nonce)
1420 })
1421 })
1422 .collect()
1423 }
1424
1425 pub fn channel_nonce(chain_id: ChainId, channel_id: ChannelId) -> Option<ChannelNonce> {
1426 Channels::<T>::get(chain_id, channel_id).map(|channel| {
1427 let last_inbox_nonce = channel.next_inbox_nonce.checked_sub(U256::one());
1428 ChannelNonce {
1429 relay_msg_nonce: last_inbox_nonce,
1430 relay_response_msg_nonce: channel.latest_response_received_message_nonce,
1431 }
1432 })
1433 }
1434
1435 pub fn store_inbox_fee(
1436 src_chain_id: ChainId,
1437 message_id: MessageId,
1438 inbox_fees: BalanceOf<T>,
1439 ) -> DispatchResult {
1440 InboxFeesOnHold::<T>::mutate(|inbox_fees_on_hold| {
1441 *inbox_fees_on_hold = inbox_fees_on_hold
1442 .checked_add(&inbox_fees)
1443 .ok_or(Error::<T>::BalanceOverflow)?;
1444
1445 let imbalance = T::Currency::issue(inbox_fees);
1448 core::mem::forget(imbalance);
1449
1450 Ok::<(), Error<T>>(())
1451 })?;
1452
1453 InboxFee::<T>::insert((src_chain_id, message_id), inbox_fees);
1454
1455 if !T::NoteChainTransfer::note_transfer_in(inbox_fees, src_chain_id) {
1457 return Err(Error::<T>::FailedToNoteTransferIn.into());
1458 }
1459
1460 Ok(())
1461 }
1462
1463 pub fn store_outbox_fee(
1464 dst_chain_id: ChainId,
1465 message_id: MessageId,
1466 outbox_fees: BalanceOf<T>,
1467 inbox_fees: BalanceOf<T>,
1468 ) -> DispatchResult {
1469 OutboxFeesOnHold::<T>::mutate(|outbox_fees_on_hold| {
1470 *outbox_fees_on_hold = outbox_fees_on_hold
1471 .checked_add(&outbox_fees)
1472 .ok_or(Error::<T>::BalanceOverflow)?;
1473
1474 let imbalance = T::Currency::issue(outbox_fees);
1477 core::mem::forget(imbalance);
1478
1479 Ok::<(), Error<T>>(())
1480 })?;
1481
1482 OutboxFee::<T>::insert((dst_chain_id, message_id), outbox_fees);
1483
1484 if !T::NoteChainTransfer::note_transfer_out(inbox_fees, dst_chain_id) {
1486 return Err(Error::<T>::FailedToNoteTransferOut.into());
1487 }
1488
1489 Ok(())
1490 }
1491 }
1492}
1493
1494impl<T> Pallet<T>
1495where
1496 T: Config + CreateUnsigned<Call<T>>,
1497{
1498 pub fn outbox_message_unsigned(
1499 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1500 ) -> Option<T::Extrinsic> {
1501 let call = Call::relay_message { msg };
1502 Some(T::create_unsigned(call.into()))
1503 }
1504
1505 pub fn inbox_response_message_unsigned(
1506 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1507 ) -> Option<T::Extrinsic> {
1508 let call = Call::relay_message_response { msg };
1509 Some(T::create_unsigned(call.into()))
1510 }
1511
1512 pub fn first_outbox_message_nonce_to_relay(
1514 dst_chain_id: ChainId,
1515 channel_id: ChannelId,
1516 from_nonce: Nonce,
1517 ) -> Option<Nonce> {
1518 Self::first_relay_message(
1519 dst_chain_id,
1520 channel_id,
1521 from_nonce,
1522 Outbox::<T>::contains_key,
1523 )
1524 }
1525
1526 pub fn first_inbox_message_response_nonce_to_relay(
1528 dst_chain_id: ChainId,
1529 channel_id: ChannelId,
1530 from_nonce: Nonce,
1531 ) -> Option<Nonce> {
1532 Self::first_relay_message(
1533 dst_chain_id,
1534 channel_id,
1535 from_nonce,
1536 InboxResponses::<T>::contains_key,
1537 )
1538 }
1539
1540 fn first_relay_message<Check>(
1541 dst_chain_id: ChainId,
1542 channel_id: ChannelId,
1543 from_nonce: Nonce,
1544 check: Check,
1545 ) -> Option<Nonce>
1546 where
1547 Check: Fn((ChainId, ChannelId, Nonce)) -> bool,
1548 {
1549 let mut nonce = from_nonce;
1550 let to_nonce = from_nonce.saturating_add(MAX_FUTURE_ALLOWED_NONCES.into());
1551 while nonce <= to_nonce {
1552 if check((dst_chain_id, channel_id, nonce)) {
1553 return Some(nonce);
1554 }
1555
1556 nonce = nonce.saturating_add(Nonce::one())
1557 }
1558
1559 None
1560 }
1561}
1562
1563impl<T: Config> sp_domains::DomainBundleSubmitted for Pallet<T> {
1564 fn domain_bundle_submitted(domain_id: DomainId) {
1565 DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_updates| {
1569 if let Some(updates) = maybe_updates {
1570 updates.clear();
1571 }
1572 });
1573 }
1574}
1575
1576impl<T: Config> sp_domains::OnDomainInstantiated for Pallet<T> {
1577 fn on_domain_instantiated(domain_id: DomainId) {
1578 DomainChainAllowlistUpdate::<T>::insert(domain_id, DomainAllowlistUpdates::default());
1579 }
1580}