1#![cfg_attr(not(feature = "std"), no_std)]
19#![forbid(unsafe_code)]
20#![warn(rust_2018_idioms)]
21#![feature(let_chains, variant_count, if_let_guard)]
22
23#[cfg(feature = "runtime-benchmarks")]
24mod benchmarking;
25pub mod extensions;
26mod fees;
27mod messages;
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, Encode};
43use scale_info::TypeInfo;
44use sp_core::U256;
45use sp_domains::{DomainAllowlistUpdates, DomainId};
46use sp_messenger::messages::{
47 ChainId, Channel, ChannelId, ChannelState, CrossDomainMessage, FeeModel, Message, MessageId,
48 Nonce,
49};
50use sp_runtime::traits::Hash;
51use sp_runtime::DispatchError;
52use subspace_runtime_primitives::CreateUnsigned;
53
54const XDM_TRANSACTION_LONGEVITY: u64 = 10;
57
58pub(crate) mod verification_errors {
59 pub(crate) const INVALID_NONCE: u8 = 201;
63 pub(crate) const NONCE_OVERFLOW: u8 = 202;
64 pub(crate) const INVALID_CHANNEL: u8 = 203;
66 pub(crate) const IN_FUTURE_NONCE: u8 = 204;
67 pub(crate) const NEXT_NONCE_UPDATE: u8 = 205;
69}
70
71#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
72pub enum OutboxMessageResult {
73 Ok,
75 Err(DispatchError),
77}
78
79#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
81pub enum RawOrigin {
82 ValidatedUnsigned,
83}
84
85pub struct EnsureMessengerOrigin;
87impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureMessengerOrigin {
88 type Success = ();
89
90 fn try_origin(o: O) -> Result<Self::Success, O> {
91 o.into().map(|o| match o {
92 RawOrigin::ValidatedUnsigned => (),
93 })
94 }
95
96 #[cfg(feature = "runtime-benchmarks")]
97 fn try_successful_origin() -> Result<O, ()> {
98 Ok(O::from(RawOrigin::ValidatedUnsigned))
99 }
100}
101
102pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>::Output;
103pub(crate) type BalanceOf<T> =
104 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
105pub(crate) type FungibleHoldId<T> =
106 <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
107
108#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
110pub enum ChainAllowlistUpdate {
111 Add(ChainId),
112 Remove(ChainId),
113}
114
115impl ChainAllowlistUpdate {
116 fn chain_id(&self) -> ChainId {
117 match self {
118 ChainAllowlistUpdate::Add(chain_id) => *chain_id,
119 ChainAllowlistUpdate::Remove(chain_id) => *chain_id,
120 }
121 }
122}
123
124#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
126pub enum MessageVersion {
127 V0,
128 V1,
129}
130
131#[derive(Debug, Encode, Decode, TypeInfo)]
132pub struct ValidatedRelayMessage<T: Config> {
133 pub message: Message<BalanceOf<T>>,
134 pub should_init_channel: bool,
135 pub next_nonce: Nonce,
136}
137
138#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
140pub(crate) enum CloseChannelBy<AccountId> {
141 Owner(AccountId),
142 Sudo,
143}
144
145pub trait HoldIdentifier<T: Config> {
147 fn messenger_channel() -> FungibleHoldId<T>;
148}
149
150const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
152
153#[frame_support::pallet]
154mod pallet {
155 use crate::weights::WeightInfo;
156 use crate::{
157 BalanceOf, ChainAllowlistUpdate, Channel, ChannelId, ChannelState, CloseChannelBy,
158 FeeModel, HoldIdentifier, Nonce, OutboxMessageResult, RawOrigin, StateRootOf,
159 ValidatedRelayMessage, STORAGE_VERSION, U256,
160 };
161 #[cfg(not(feature = "std"))]
162 use alloc::boxed::Box;
163 #[cfg(not(feature = "std"))]
164 use alloc::collections::BTreeSet;
165 #[cfg(not(feature = "std"))]
166 use alloc::vec::Vec;
167 use core::cmp::Ordering;
168 use frame_support::ensure;
169 use frame_support::pallet_prelude::*;
170 use frame_support::traits::fungible::{Balanced, Inspect, InspectHold, Mutate, MutateHold};
171 use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
172 use frame_support::weights::WeightToFee;
173 use frame_system::pallet_prelude::*;
174 use sp_core::storage::StorageKey;
175 use sp_domains::proof_provider_and_verifier::{StorageProofVerifier, VerificationError};
176 use sp_domains::{DomainAllowlistUpdates, DomainId, DomainOwner};
177 use sp_messenger::endpoint::{
178 Endpoint, EndpointHandler, EndpointRequest, EndpointRequestWithCollectedFee, Sender,
179 };
180 use sp_messenger::messages::{
181 ChainId, ChannelOpenParams, ChannelOpenParamsV1, ConvertedPayload, CrossDomainMessage,
182 Message, MessageId, MessageKey, MessageWeightTag, Payload, PayloadV1,
183 ProtocolMessageRequest, RequestResponse, VersionedPayload,
184 };
185 use sp_messenger::{
186 ChannelNonce, DomainRegistration, InherentError, InherentType, NoteChainTransfer,
187 OnXDMRewards, StorageKeys, INHERENT_IDENTIFIER,
188 };
189 use sp_runtime::traits::Zero;
190 use sp_runtime::{ArithmeticError, Perbill, Saturating};
191 use sp_subspace_mmr::MmrProofVerifier;
192 #[cfg(feature = "std")]
193 use std::collections::BTreeSet;
194
195 #[pallet::config]
196 pub trait Config: frame_system::Config {
197 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
198 type SelfChainId: Get<ChainId>;
200 fn get_endpoint_handler(endpoint: &Endpoint)
202 -> Option<Box<dyn EndpointHandler<MessageId>>>;
203 type Currency: Mutate<Self::AccountId>
205 + InspectHold<Self::AccountId>
206 + MutateHold<Self::AccountId>
207 + Balanced<Self::AccountId>;
208 type WeightInfo: WeightInfo;
210 type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
212 type AdjustedWeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
215 #[pallet::constant]
218 type FeeMultiplier: Get<u32>;
219 type OnXDMRewards: OnXDMRewards<BalanceOf<Self>>;
221 type MmrHash: Parameter + Member + Default + Clone;
223 type MmrProofVerifier: MmrProofVerifier<
225 Self::MmrHash,
226 BlockNumberFor<Self>,
227 StateRootOf<Self>,
228 >;
229 type StorageKeys: StorageKeys;
231 type DomainOwner: DomainOwner<Self::AccountId>;
233 type HoldIdentifier: HoldIdentifier<Self>;
235 #[pallet::constant]
237 type ChannelReserveFee: Get<BalanceOf<Self>>;
238 #[pallet::constant]
241 type ChannelInitReservePortion: Get<Perbill>;
242 type DomainRegistration: DomainRegistration;
244 type ChannelFeeModel: Get<FeeModel<BalanceOf<Self>>>;
246 #[pallet::constant]
248 type MaxOutgoingMessages: Get<u32>;
249 type MessengerOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
251 #[pallet::constant]
253 type MessageVersion: Get<crate::MessageVersion>;
254 type NoteChainTransfer: NoteChainTransfer<BalanceOf<Self>>;
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::storage]
387 pub(super) type InboxFeesOnHoldStartAt<T: Config> =
388 StorageMap<_, Identity, ChannelId, Nonce, OptionQuery>;
389 #[pallet::storage]
390 pub(super) type OutboxFeesOnHoldStartAt<T: Config> =
391 StorageMap<_, Identity, ChannelId, Nonce, OptionQuery>;
392
393 #[pallet::origin]
394 pub type Origin = RawOrigin;
395
396 #[pallet::event]
398 #[pallet::generate_deposit(pub (super) fn deposit_event)]
399 pub enum Event<T: Config> {
400 ChannelInitiated {
402 chain_id: ChainId,
404 channel_id: ChannelId,
406 },
407
408 ChannelClosed {
410 chain_id: ChainId,
412 channel_id: ChannelId,
414 },
415
416 ChannelOpen {
418 chain_id: ChainId,
420 channel_id: ChannelId,
422 },
423
424 OutboxMessage {
426 chain_id: ChainId,
427 channel_id: ChannelId,
428 nonce: Nonce,
429 },
430
431 OutboxMessageResponse {
433 chain_id: ChainId,
435 channel_id: ChannelId,
437 nonce: Nonce,
438 },
439
440 OutboxMessageResult {
442 chain_id: ChainId,
443 channel_id: ChannelId,
444 nonce: Nonce,
445 result: OutboxMessageResult,
446 },
447
448 InboxMessage {
450 chain_id: ChainId,
451 channel_id: ChannelId,
452 nonce: Nonce,
453 },
454
455 InboxMessageResponse {
457 chain_id: ChainId,
459 channel_id: ChannelId,
461 nonce: Nonce,
462 },
463 }
464
465 #[pallet::hooks]
466 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
467 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
468 UpdatedChannels::<T>::take();
469 T::DbWeight::get().reads_writes(0, 1)
470 }
471 }
472
473 #[pallet::validate_unsigned]
474 impl<T: Config> ValidateUnsigned for Pallet<T> {
475 type Call = Call<T>;
476
477 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
478 match call {
479 Call::update_domain_allowlist { .. } => Ok(()),
481 _ => Err(InvalidTransaction::Call.into()),
482 }
483 }
484
485 fn validate_unsigned(
487 _source: TransactionSource,
488 _call: &Self::Call,
489 ) -> TransactionValidity {
490 InvalidTransaction::Call.into()
491 }
492 }
493
494 #[pallet::error]
496 pub enum Error<T> {
497 InvalidChain,
499
500 MissingChannel,
502
503 InvalidChannelState,
505
506 NoOpenChannel,
508
509 NoMessageHandler,
511
512 OutboxFull,
514
515 InvalidMessagePayload,
517
518 InvalidMessageDestination,
520
521 MessageVerification(VerificationError),
523
524 MissingMessage,
526
527 WeightTagNotMatch,
530
531 BalanceOverflow,
533
534 BalanceUnderflow,
536
537 InvalidAllowedChain,
539
540 OperationNotAllowed,
542
543 NotDomainOwner,
545
546 ChainNotAllowed,
548
549 InsufficientBalance,
551
552 BalanceHold,
554
555 ChannelOwner,
557
558 BalanceUnlock,
560
561 InvalidChannelReserveFee,
563
564 InvalidMaxOutgoingMessages,
566
567 MessageCountOverflow,
569
570 MessageCountUnderflow,
572
573 MessageVersionMismatch,
575
576 FailedToNoteTransferIn,
578
579 FailedToNoteTransferOut,
581 }
582
583 #[pallet::call]
584 impl<T: Config> Pallet<T> {
585 #[pallet::call_index(0)]
589 #[pallet::weight(T::WeightInfo::initiate_channel())]
590 pub fn initiate_channel(origin: OriginFor<T>, dst_chain_id: ChainId) -> DispatchResult {
591 let owner = ensure_signed(origin)?;
592
593 let hold_id = T::HoldIdentifier::messenger_channel();
595 let amount = T::ChannelReserveFee::get();
596
597 ensure!(
599 T::Currency::reducible_balance(&owner, Preservation::Preserve, Fortitude::Polite)
600 >= amount,
601 Error::<T>::InsufficientBalance
602 );
603 T::Currency::hold(&hold_id, &owner, amount).map_err(|_| Error::<T>::BalanceHold)?;
604
605 let channel_open_params = ChannelOpenParams {
607 max_outgoing_messages: T::MaxOutgoingMessages::get(),
608 fee_model: T::ChannelFeeModel::get(),
609 };
610 let channel_id = Self::do_init_channel(
611 dst_chain_id,
612 channel_open_params,
613 Some(owner.clone()),
614 true,
615 amount,
616 )?;
617
618 let payload = match T::MessageVersion::get() {
619 crate::MessageVersion::V0 => {
620 VersionedPayload::V0(Payload::Protocol(RequestResponse::Request(
621 ProtocolMessageRequest::ChannelOpen(channel_open_params),
622 )))
623 }
624 crate::MessageVersion::V1 => {
625 VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
626 ProtocolMessageRequest::ChannelOpen(ChannelOpenParamsV1 {
627 max_outgoing_messages: channel_open_params.max_outgoing_messages,
628 }),
629 )))
630 }
631 };
632
633 Self::new_outbox_message(T::SelfChainId::get(), dst_chain_id, channel_id, payload)?;
635
636 Ok(())
637 }
638
639 #[pallet::call_index(1)]
642 #[pallet::weight(T::WeightInfo::close_channel())]
643 pub fn close_channel(
644 origin: OriginFor<T>,
645 chain_id: ChainId,
646 channel_id: ChannelId,
647 ) -> DispatchResult {
648 let close_channel_by = match ensure_signed_or_root(origin)? {
651 Some(owner) => CloseChannelBy::Owner(owner),
652 None => CloseChannelBy::Sudo,
653 };
654 Self::do_close_channel(chain_id, channel_id, close_channel_by)?;
655
656 let payload = match T::MessageVersion::get() {
657 crate::MessageVersion::V0 => VersionedPayload::V0(Payload::Protocol(
658 RequestResponse::Request(ProtocolMessageRequest::ChannelClose),
659 )),
660 crate::MessageVersion::V1 => VersionedPayload::V1(PayloadV1::Protocol(
661 RequestResponse::Request(ProtocolMessageRequest::ChannelClose),
662 )),
663 };
664
665 Self::new_outbox_message(T::SelfChainId::get(), chain_id, channel_id, payload)?;
666
667 Ok(())
668 }
669
670 #[pallet::call_index(2)]
672 #[pallet::weight(T::WeightInfo::relay_message().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag)))]
673 pub fn relay_message(
674 origin: OriginFor<T>,
675 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
676 ) -> DispatchResult {
677 T::MessengerOrigin::ensure_origin(origin)?;
678 let inbox_msg = Inbox::<T>::take().ok_or(Error::<T>::MissingMessage)?;
679 Self::process_inbox_messages(inbox_msg, msg.weight_tag)?;
680 Ok(())
681 }
682
683 #[pallet::call_index(3)]
685 #[pallet::weight(T::WeightInfo::relay_message_response().saturating_add(Pallet::< T >::message_weight(& msg.weight_tag)))]
686 pub fn relay_message_response(
687 origin: OriginFor<T>,
688 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
689 ) -> DispatchResult {
690 T::MessengerOrigin::ensure_origin(origin)?;
691 let outbox_resp_msg = OutboxResponses::<T>::take().ok_or(Error::<T>::MissingMessage)?;
692 Self::process_outbox_message_responses(outbox_resp_msg, msg.weight_tag)?;
693 Ok(())
694 }
695
696 #[pallet::call_index(4)]
698 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1))]
699 pub fn update_consensus_chain_allowlist(
700 origin: OriginFor<T>,
701 update: ChainAllowlistUpdate,
702 ) -> DispatchResult {
703 ensure_root(origin)?;
704 ensure!(
705 T::SelfChainId::get().is_consensus_chain(),
706 Error::<T>::OperationNotAllowed
707 );
708
709 ensure!(
710 update.chain_id() != T::SelfChainId::get(),
711 Error::<T>::InvalidAllowedChain
712 );
713
714 if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update {
715 ensure!(
716 T::DomainRegistration::is_domain_registered(domain_id),
717 Error::<T>::InvalidChain
718 );
719 }
720
721 ChainAllowlist::<T>::mutate(|list| match update {
722 ChainAllowlistUpdate::Add(chain_id) => list.insert(chain_id),
723 ChainAllowlistUpdate::Remove(chain_id) => list.remove(&chain_id),
724 });
725 Ok(())
726 }
727
728 #[pallet::call_index(5)]
730 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(1, 1))]
731 pub fn initiate_domain_update_chain_allowlist(
732 origin: OriginFor<T>,
733 domain_id: DomainId,
734 update: ChainAllowlistUpdate,
735 ) -> DispatchResult {
736 let domain_owner = ensure_signed(origin)?;
737 ensure!(
738 T::DomainOwner::is_domain_owner(domain_id, domain_owner),
739 Error::<T>::NotDomainOwner
740 );
741
742 ensure!(
743 T::SelfChainId::get().is_consensus_chain(),
744 Error::<T>::OperationNotAllowed
745 );
746
747 if let Some(dst_domain_id) = update.chain_id().maybe_domain_chain() {
748 ensure!(dst_domain_id != domain_id, Error::<T>::InvalidAllowedChain);
749 }
750
751 if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update {
752 ensure!(
753 T::DomainRegistration::is_domain_registered(domain_id),
754 Error::<T>::InvalidChain
755 );
756 }
757
758 DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_domain_updates| {
759 let mut domain_updates = maybe_domain_updates.take().unwrap_or_default();
760 match update {
761 ChainAllowlistUpdate::Add(chain_id) => {
762 domain_updates.remove_chains.remove(&chain_id);
763 domain_updates.allow_chains.insert(chain_id);
764 }
765 ChainAllowlistUpdate::Remove(chain_id) => {
766 domain_updates.allow_chains.remove(&chain_id);
767 domain_updates.remove_chains.insert(chain_id);
768 }
769 }
770
771 *maybe_domain_updates = Some(domain_updates)
772 });
773 Ok(())
774 }
775
776 #[pallet::call_index(6)]
778 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Mandatory))]
779 pub fn update_domain_allowlist(
780 origin: OriginFor<T>,
781 updates: DomainAllowlistUpdates,
782 ) -> DispatchResult {
783 ensure_none(origin)?;
784 ensure!(
785 !T::SelfChainId::get().is_consensus_chain(),
786 Error::<T>::OperationNotAllowed
787 );
788
789 let DomainAllowlistUpdates {
790 allow_chains,
791 remove_chains,
792 } = updates;
793
794 ChainAllowlist::<T>::mutate(|list| {
795 remove_chains.into_iter().for_each(|chain_id| {
797 list.remove(&chain_id);
798 });
799
800 allow_chains.into_iter().for_each(|chain_id| {
802 list.insert(chain_id);
803 });
804 });
805
806 Ok(())
807 }
808 }
809
810 #[pallet::inherent]
811 impl<T: Config> ProvideInherent for Pallet<T> {
812 type Call = Call<T>;
813 type Error = InherentError;
814 const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
815
816 fn create_inherent(data: &InherentData) -> Option<Self::Call> {
817 let inherent_data = data
818 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
819 .expect("Messenger inherent data not correctly encoded")
820 .expect("Messenger inherent data must be provided");
821
822 inherent_data
823 .maybe_updates
824 .map(|updates| Call::update_domain_allowlist { updates })
825 }
826
827 fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
828 let inherent_data = data
829 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
830 .expect("Messenger inherent data not correctly encoded")
831 .expect("Messenger inherent data must be provided");
832
833 Ok(if inherent_data.maybe_updates.is_none() {
834 None
835 } else {
836 Some(InherentError::MissingAllowlistUpdates)
837 })
838 }
839
840 fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
841 let inherent_data = data
842 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
843 .expect("Messenger inherent data not correctly encoded")
844 .expect("Messenger inherent data must be provided");
845
846 if let Some(provided_updates) = inherent_data.maybe_updates {
847 if let Call::update_domain_allowlist { updates } = call {
848 if updates != &provided_updates {
849 return Err(InherentError::IncorrectAllowlistUpdates);
850 }
851 }
852 } else {
853 return Err(InherentError::MissingAllowlistUpdates);
854 }
855
856 Ok(())
857 }
858
859 fn is_inherent(call: &Self::Call) -> bool {
860 matches!(call, Call::update_domain_allowlist { .. })
861 }
862 }
863
864 impl<T: Config> Sender<T::AccountId> for Pallet<T> {
865 type MessageId = MessageId;
866
867 fn send_message(
868 sender: &T::AccountId,
869 dst_chain_id: ChainId,
870 req: EndpointRequest,
871 ) -> Result<Self::MessageId, DispatchError> {
872 let allowed_chains = ChainAllowlist::<T>::get();
873 ensure!(
874 allowed_chains.contains(&dst_chain_id),
875 Error::<T>::ChainNotAllowed
876 );
877
878 let (channel_id, fee_model) =
879 Self::get_open_channel_for_chain(dst_chain_id).ok_or(Error::<T>::NoOpenChannel)?;
880
881 let src_endpoint = req.src_endpoint.clone();
882
883 let message_id = match T::MessageVersion::get() {
884 crate::MessageVersion::V0 => {
885 let nonce = Self::new_outbox_message(
886 T::SelfChainId::get(),
887 dst_chain_id,
888 channel_id,
889 VersionedPayload::V0(Payload::Endpoint(RequestResponse::Request(req))),
890 )?;
891
892 Self::collect_fees_for_message(
894 sender,
895 (dst_chain_id, (channel_id, nonce)),
896 &fee_model,
897 &src_endpoint,
898 )?;
899 (channel_id, nonce)
900 }
901 crate::MessageVersion::V1 => {
902 let collected_fee = Self::collect_fees_for_message_v1(sender, &src_endpoint)?;
904 let src_chain_fee = collected_fee.src_chain_fee;
905 let dst_chain_fee = collected_fee.dst_chain_fee;
906 let nonce = Self::new_outbox_message(
907 T::SelfChainId::get(),
908 dst_chain_id,
909 channel_id,
910 VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(
911 EndpointRequestWithCollectedFee { req, collected_fee },
912 ))),
913 )?;
914
915 let message_id = (channel_id, nonce);
917 Self::store_outbox_fee(dst_chain_id, message_id, src_chain_fee, dst_chain_fee)?;
918 message_id
919 }
920 };
921
922 Ok(message_id)
923 }
924
925 #[cfg(feature = "runtime-benchmarks")]
928 fn unchecked_open_channel(dst_chain_id: ChainId) -> Result<(), DispatchError> {
929 let fee_model = FeeModel {
930 relay_fee: Default::default(),
931 };
932 let init_params = ChannelOpenParams {
933 max_outgoing_messages: 100,
934 fee_model,
935 };
936 ChainAllowlist::<T>::mutate(|list| list.insert(dst_chain_id));
937 let channel_id =
938 Self::do_init_channel(dst_chain_id, init_params, None, true, Zero::zero())?;
939 Self::do_open_channel(dst_chain_id, channel_id)?;
940 Ok(())
941 }
942 }
943
944 impl<T: Config> Pallet<T> {
945 fn message_weight(weight_tag: &MessageWeightTag) -> Weight {
947 match weight_tag {
948 MessageWeightTag::ProtocolChannelOpen => T::WeightInfo::do_open_channel(),
949 MessageWeightTag::ProtocolChannelClose => T::WeightInfo::do_close_channel(),
950 MessageWeightTag::EndpointRequest(endpoint) => {
951 T::get_endpoint_handler(endpoint)
952 .map(|endpoint_handler| endpoint_handler.message_weight())
953 .unwrap_or(Weight::zero())
955 }
956 MessageWeightTag::EndpointResponse(endpoint) => {
957 T::get_endpoint_handler(endpoint)
958 .map(|endpoint_handler| endpoint_handler.message_response_weight())
959 .unwrap_or(Weight::zero())
961 }
962 MessageWeightTag::None => Weight::zero(),
963 }
964 }
965
966 pub fn get_open_channel_for_chain(
968 dst_chain_id: ChainId,
969 ) -> Option<(ChannelId, FeeModel<BalanceOf<T>>)> {
970 let mut next_channel_id = NextChannelId::<T>::get(dst_chain_id);
971
972 while let Some(channel_id) = next_channel_id.checked_sub(ChannelId::one()) {
975 let message_count = OutboxMessageCount::<T>::get((dst_chain_id, channel_id));
976 if let Some(channel) = Channels::<T>::get(dst_chain_id, channel_id) {
977 if channel.state == ChannelState::Open
978 && message_count < channel.max_outgoing_messages
979 {
980 return Some((channel_id, channel.fee));
981 }
982 }
983
984 next_channel_id = channel_id
985 }
986
987 None
988 }
989
990 pub(crate) fn do_open_channel(chain_id: ChainId, channel_id: ChannelId) -> DispatchResult {
992 Channels::<T>::try_mutate(chain_id, channel_id, |maybe_channel| -> DispatchResult {
993 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
994
995 ensure!(
996 channel.state == ChannelState::Initiated,
997 Error::<T>::InvalidChannelState
998 );
999
1000 channel.state = ChannelState::Open;
1001 Ok(())
1002 })?;
1003
1004 Self::deposit_event(Event::ChannelOpen {
1005 chain_id,
1006 channel_id,
1007 });
1008
1009 Ok(())
1010 }
1011
1012 pub(crate) fn do_close_channel(
1013 chain_id: ChainId,
1014 channel_id: ChannelId,
1015 close_channel_by: CloseChannelBy<T::AccountId>,
1016 ) -> DispatchResult {
1017 Channels::<T>::try_mutate(chain_id, channel_id, |maybe_channel| -> DispatchResult {
1018 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1019
1020 ensure!(
1021 channel.state != ChannelState::Closed,
1022 Error::<T>::InvalidChannelState
1023 );
1024
1025 if let CloseChannelBy::Owner(owner) = close_channel_by {
1026 ensure!(channel.maybe_owner == Some(owner), Error::<T>::ChannelOwner);
1027 }
1028
1029 if let Some(owner) = &channel.maybe_owner {
1030 let hold_id = T::HoldIdentifier::messenger_channel();
1031 let locked_amount = channel.channel_reserve_fee;
1032 let amount_to_release = {
1033 if channel.state == ChannelState::Open {
1034 locked_amount
1035 } else {
1036 let protocol_fee = T::ChannelInitReservePortion::get() * locked_amount;
1037 let release_amount = locked_amount.saturating_sub(protocol_fee);
1038 T::Currency::burn_held(
1039 &hold_id,
1040 owner,
1041 protocol_fee,
1042 Precision::Exact,
1043 Fortitude::Force,
1044 )?;
1045 T::OnXDMRewards::on_chain_protocol_fees(chain_id, protocol_fee);
1046 release_amount
1047 }
1048 };
1049 T::Currency::release(&hold_id, owner, amount_to_release, Precision::Exact)
1050 .map_err(|_| Error::<T>::BalanceUnlock)?;
1051 }
1052
1053 channel.state = ChannelState::Closed;
1054 Ok(())
1055 })?;
1056
1057 Self::deposit_event(Event::ChannelClosed {
1058 chain_id,
1059 channel_id,
1060 });
1061
1062 Ok(())
1063 }
1064
1065 pub(crate) fn do_init_channel(
1066 dst_chain_id: ChainId,
1067 init_params: ChannelOpenParams<BalanceOf<T>>,
1068 maybe_owner: Option<T::AccountId>,
1069 check_allowlist: bool,
1070 channel_reserve_fee: BalanceOf<T>,
1071 ) -> Result<ChannelId, DispatchError> {
1072 ensure!(
1073 T::SelfChainId::get() != dst_chain_id,
1074 Error::<T>::InvalidChain,
1075 );
1076
1077 ensure!(
1079 init_params.max_outgoing_messages >= 1u32,
1080 Error::<T>::InvalidMaxOutgoingMessages
1081 );
1082
1083 ensure!(
1086 maybe_owner.is_none() || !channel_reserve_fee.is_zero(),
1087 Error::<T>::InvalidChannelReserveFee,
1088 );
1089
1090 if check_allowlist {
1091 let chain_allowlist = ChainAllowlist::<T>::get();
1092 ensure!(
1093 chain_allowlist.contains(&dst_chain_id),
1094 Error::<T>::ChainNotAllowed
1095 );
1096 }
1097
1098 let channel_id = NextChannelId::<T>::get(dst_chain_id);
1099 let next_channel_id = channel_id
1100 .checked_add(U256::one())
1101 .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?;
1102
1103 Channels::<T>::insert(
1104 dst_chain_id,
1105 channel_id,
1106 Channel {
1107 channel_id,
1108 state: ChannelState::Initiated,
1109 next_inbox_nonce: Default::default(),
1110 next_outbox_nonce: Default::default(),
1111 latest_response_received_message_nonce: Default::default(),
1112 max_outgoing_messages: init_params.max_outgoing_messages,
1113 fee: init_params.fee_model,
1114 maybe_owner,
1115 channel_reserve_fee,
1116 },
1117 );
1118
1119 NextChannelId::<T>::insert(dst_chain_id, next_channel_id);
1120 Self::deposit_event(Event::ChannelInitiated {
1121 chain_id: dst_chain_id,
1122 channel_id,
1123 });
1124 Ok(channel_id)
1125 }
1126
1127 pub fn validate_relay_message(
1128 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1129 consensus_state_root: StateRootOf<T>,
1130 ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1131 let (next_nonce, maybe_channel) =
1132 match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1133 None => {
1134 log::debug!(
1137 "Initiating new channel: {:?} to chain: {:?}",
1138 xdm.channel_id,
1139 xdm.src_chain_id
1140 );
1141 (Nonce::zero(), None)
1142 }
1143 Some(channel) => {
1144 log::debug!(
1145 "Message to channel: {:?} from chain: {:?}",
1146 xdm.channel_id,
1147 xdm.src_chain_id
1148 );
1149 (channel.next_inbox_nonce, Some(channel))
1150 }
1151 };
1152
1153 let key = StorageKey(
1155 T::StorageKeys::outbox_storage_key(
1156 xdm.src_chain_id,
1157 (T::SelfChainId::get(), xdm.channel_id, xdm.nonce),
1158 )
1159 .ok_or(UnknownTransaction::CannotLookup)?,
1160 );
1161
1162 let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1164
1165 let ConvertedPayload { payload, is_v1: _ } = msg.payload.clone().into_payload_v0();
1166 let is_valid_call = match &payload {
1167 Payload::Protocol(RequestResponse::Request(req)) => match req {
1168 ProtocolMessageRequest::ChannelOpen(_) => maybe_channel.is_none(),
1170 ProtocolMessageRequest::ChannelClose => {
1172 if let Some(ref channel) = maybe_channel {
1173 !(channel.state == ChannelState::Closed)
1174 } else {
1175 false
1176 }
1177 }
1178 },
1179 Payload::Endpoint(RequestResponse::Request(_)) => {
1185 if let Some(ref channel) = maybe_channel {
1186 !(channel.state == ChannelState::Initiated)
1187 } else {
1188 false
1189 }
1190 }
1191 _ => false,
1193 };
1194
1195 if !is_valid_call {
1196 log::error!("Unexpected XDM message: {:?}", msg,);
1197 return Err(InvalidTransaction::Call.into());
1198 }
1199
1200 if msg.nonce.cmp(&next_nonce) == Ordering::Less {
1202 return Err(InvalidTransaction::Stale.into());
1203 }
1204
1205 let validated_relay_msg = ValidatedRelayMessage {
1206 message: msg,
1207 should_init_channel: maybe_channel.is_none(),
1208 next_nonce,
1209 };
1210
1211 Ok(validated_relay_msg)
1212 }
1213
1214 pub(crate) fn pre_dispatch_relay_message(
1215 msg: Message<BalanceOf<T>>,
1216 should_init_channel: bool,
1217 ) -> Result<(), TransactionValidityError> {
1218 if should_init_channel {
1219 let ConvertedPayload { payload, is_v1: _ } = msg.payload.clone().into_payload_v0();
1220 if let Payload::Protocol(RequestResponse::Request(
1221 ProtocolMessageRequest::ChannelOpen(params),
1222 )) = payload
1223 {
1224 Self::do_init_channel(msg.src_chain_id, params, None, false, Zero::zero())
1228 .map_err(|err| {
1229 log::error!(
1230 "Error initiating channel: {:?} with chain: {:?}: {:?}",
1231 msg.channel_id,
1232 msg.src_chain_id,
1233 err
1234 );
1235 InvalidTransaction::Call
1236 })?;
1237 } else {
1238 log::error!("Unexpected call instead of channel open request: {:?}", msg,);
1239 return Err(InvalidTransaction::Call.into());
1240 }
1241 }
1242
1243 let (dst_chain_id, channel_id, nonce) = (msg.src_chain_id, msg.channel_id, msg.nonce);
1244 Channels::<T>::mutate(
1247 dst_chain_id,
1248 channel_id,
1249 |maybe_channel| -> sp_runtime::DispatchResult {
1250 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1251 channel.next_inbox_nonce = nonce
1252 .checked_add(Nonce::one())
1253 .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?;
1254 Ok(())
1255 },
1256 )
1257 .map_err(|err| {
1258 log::error!(
1259 "Failed to increment the next relay message nonce for Chain[{:?}] with Channel[{:?}]: {:?}",
1260 dst_chain_id,
1261 channel_id,
1262 err,
1263 );
1264 InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1265 })?;
1266
1267 Self::deposit_event(Event::InboxMessage {
1268 chain_id: msg.src_chain_id,
1269 channel_id: msg.channel_id,
1270 nonce: msg.nonce,
1271 });
1272 Inbox::<T>::put(msg);
1273 Ok(())
1274 }
1275
1276 pub fn validate_relay_message_response(
1277 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1278 consensus_state_root: StateRootOf<T>,
1279 ) -> Result<ValidatedRelayMessage<T>, TransactionValidityError> {
1280 let next_nonce =
1282 match Channels::<T>::get(xdm.src_chain_id, xdm.channel_id) {
1283 None => {
1285 log::error!("Unexpected inbox message response: {:?}", xdm,);
1286 return Err(InvalidTransaction::Call.into());
1287 }
1288 Some(channel) => match channel.latest_response_received_message_nonce {
1289 None => Nonce::zero(),
1290 Some(last_nonce) => last_nonce.checked_add(Nonce::one()).ok_or(
1291 InvalidTransaction::Custom(crate::verification_errors::NONCE_OVERFLOW),
1292 )?,
1293 },
1294 };
1295
1296 let key = StorageKey(
1298 T::StorageKeys::inbox_responses_storage_key(
1299 xdm.src_chain_id,
1300 (T::SelfChainId::get(), xdm.channel_id, xdm.nonce),
1301 )
1302 .ok_or(UnknownTransaction::CannotLookup)?,
1303 );
1304
1305 let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?;
1307
1308 if msg.nonce.cmp(&next_nonce) == Ordering::Less {
1310 return Err(InvalidTransaction::Stale.into());
1311 }
1312
1313 let validated_relay_msg = ValidatedRelayMessage {
1314 message: msg,
1315 next_nonce,
1316 should_init_channel: false,
1318 };
1319
1320 Ok(validated_relay_msg)
1321 }
1322
1323 pub(crate) fn pre_dispatch_relay_message_response(
1324 msg: Message<BalanceOf<T>>,
1325 ) -> Result<(), TransactionValidityError> {
1326 let (dst_chain_id, channel_id, nonce) = (msg.src_chain_id, msg.channel_id, msg.nonce);
1329 Channels::<T>::mutate(
1330 dst_chain_id,
1331 channel_id,
1332 |maybe_channel| -> sp_runtime::DispatchResult {
1333 let channel = maybe_channel.as_mut().ok_or(Error::<T>::MissingChannel)?;
1334 channel.latest_response_received_message_nonce = Some(nonce);
1335 Ok(())
1336 },
1337 )
1338 .map_err(|err| {
1339 log::error!(
1340 "Failed to increment the next relay message response nonce for Chain[{:?}] with Channel[{:?}]: {:?}",
1341 dst_chain_id,
1342 channel_id,
1343 err,
1344 );
1345 InvalidTransaction::Custom(crate::verification_errors::NEXT_NONCE_UPDATE)
1346 })?;
1347
1348 Self::deposit_event(Event::OutboxMessageResponse {
1349 chain_id: msg.src_chain_id,
1350 channel_id: msg.channel_id,
1351 nonce: msg.nonce,
1352 });
1353
1354 OutboxResponses::<T>::put(msg);
1355 Ok(())
1356 }
1357
1358 pub(crate) fn do_verify_xdm(
1359 next_nonce: Nonce,
1360 storage_key: StorageKey,
1361 consensus_state_root: StateRootOf<T>,
1362 xdm: &CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1363 ) -> Result<Message<BalanceOf<T>>, TransactionValidityError> {
1364 let next_channel_id = NextChannelId::<T>::get(xdm.src_chain_id);
1366 ensure!(
1367 xdm.channel_id <= next_channel_id,
1368 InvalidTransaction::Custom(crate::verification_errors::INVALID_CHANNEL)
1369 );
1370
1371 ensure!(
1374 xdm.nonce >= next_nonce,
1375 InvalidTransaction::Custom(crate::verification_errors::INVALID_NONCE)
1376 );
1377
1378 let state_root = if let Some(domain_proof) = xdm.proof.domain_proof().clone()
1380 && let Some(domain_id) = xdm.src_chain_id.maybe_domain_chain()
1381 {
1382 let confirmed_domain_block_storage_key =
1383 T::StorageKeys::confirmed_domain_block_storage_key(domain_id)
1384 .ok_or(UnknownTransaction::CannotLookup)?;
1385
1386 StorageProofVerifier::<T::Hashing>::get_decoded_value::<
1387 sp_domains::ExecutionReceipt<
1388 BlockNumberFor<T>,
1389 T::Hash,
1390 BlockNumberFor<T>,
1391 T::Hash,
1392 BalanceOf<T>,
1393 >,
1394 >(
1395 &consensus_state_root,
1396 domain_proof,
1397 StorageKey(confirmed_domain_block_storage_key),
1398 )
1399 .map_err(|err| {
1400 log::error!(
1401 target: "runtime::messenger",
1402 "Failed to verify storage proof for confirmed Domain block: {:?}",
1403 err
1404 );
1405 TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1406 })?
1407 .final_state_root
1408 } else {
1409 consensus_state_root
1410 };
1411
1412 let msg =
1414 StorageProofVerifier::<T::Hashing>::get_decoded_value::<Message<BalanceOf<T>>>(
1415 &state_root,
1416 xdm.proof.message_proof(),
1417 storage_key,
1418 )
1419 .map_err(|err| {
1420 log::error!(
1421 target: "runtime::messenger",
1422 "Failed to verify storage proof for message: {:?}",
1423 err
1424 );
1425 TransactionValidityError::Invalid(InvalidTransaction::BadProof)
1426 })?;
1427
1428 Ok(msg)
1429 }
1430
1431 pub fn outbox_storage_key(message_key: MessageKey) -> Vec<u8> {
1432 Outbox::<T>::hashed_key_for(message_key)
1433 }
1434
1435 pub fn inbox_response_storage_key(message_key: MessageKey) -> Vec<u8> {
1436 InboxResponses::<T>::hashed_key_for(message_key)
1437 }
1438
1439 pub fn channel_storage_key(chain_id: ChainId, channel_id: ChannelId) -> Vec<u8> {
1440 Channels::<T>::hashed_key_for(chain_id, channel_id)
1441 }
1442
1443 pub fn domain_chains_allowlist_update(
1444 domain_id: DomainId,
1445 ) -> Option<DomainAllowlistUpdates> {
1446 DomainChainAllowlistUpdate::<T>::get(domain_id).filter(|updates| !updates.is_empty())
1447 }
1448
1449 pub fn domain_allow_list_update_storage_key(domain_id: DomainId) -> Vec<u8> {
1450 DomainChainAllowlistUpdate::<T>::hashed_key_for(domain_id)
1451 }
1452
1453 pub fn updated_channels() -> BTreeSet<(ChainId, ChannelId)> {
1454 UpdatedChannels::<T>::get()
1455 }
1456
1457 pub fn open_channels() -> BTreeSet<(ChainId, ChannelId)> {
1458 Channels::<T>::iter().fold(
1459 BTreeSet::new(),
1460 |mut acc, (dst_chain_id, channel_id, channel)| {
1461 if channel.state != ChannelState::Closed {
1462 acc.insert((dst_chain_id, channel_id));
1463 }
1464
1465 acc
1466 },
1467 )
1468 }
1469
1470 pub fn channel_nonce(chain_id: ChainId, channel_id: ChannelId) -> Option<ChannelNonce> {
1471 Channels::<T>::get(chain_id, channel_id).map(|channel| {
1472 let last_inbox_nonce = channel.next_inbox_nonce.checked_sub(U256::one());
1473 ChannelNonce {
1474 relay_msg_nonce: last_inbox_nonce,
1475 relay_response_msg_nonce: channel.latest_response_received_message_nonce,
1476 }
1477 })
1478 }
1479
1480 pub fn store_inbox_fee(
1481 src_chain_id: ChainId,
1482 message_id: MessageId,
1483 inbox_fees: BalanceOf<T>,
1484 ) -> DispatchResult {
1485 if !InboxFeesOnHoldStartAt::<T>::contains_key(message_id.0) {
1486 InboxFeesOnHoldStartAt::<T>::insert(message_id.0, message_id.1);
1487 }
1488 InboxFeesOnHold::<T>::mutate(|inbox_fees_on_hold| {
1489 *inbox_fees_on_hold = inbox_fees_on_hold
1490 .checked_add(&inbox_fees)
1491 .ok_or(Error::<T>::BalanceOverflow)?;
1492
1493 let imbalance = T::Currency::issue(inbox_fees);
1496 core::mem::forget(imbalance);
1497
1498 Ok::<(), Error<T>>(())
1499 })?;
1500
1501 InboxFee::<T>::insert((src_chain_id, message_id), inbox_fees);
1502
1503 if !T::NoteChainTransfer::note_transfer_in(inbox_fees, src_chain_id) {
1505 return Err(Error::<T>::FailedToNoteTransferIn.into());
1506 }
1507
1508 Ok(())
1509 }
1510
1511 pub fn store_outbox_fee(
1512 dst_chain_id: ChainId,
1513 message_id: MessageId,
1514 outbox_fees: BalanceOf<T>,
1515 inbox_fees: BalanceOf<T>,
1516 ) -> DispatchResult {
1517 if !OutboxFeesOnHoldStartAt::<T>::contains_key(message_id.0) {
1518 OutboxFeesOnHoldStartAt::<T>::insert(message_id.0, message_id.1);
1519 }
1520 OutboxFeesOnHold::<T>::mutate(|outbox_fees_on_hold| {
1521 *outbox_fees_on_hold = outbox_fees_on_hold
1522 .checked_add(&outbox_fees)
1523 .ok_or(Error::<T>::BalanceOverflow)?;
1524
1525 let imbalance = T::Currency::issue(outbox_fees);
1528 core::mem::forget(imbalance);
1529
1530 Ok::<(), Error<T>>(())
1531 })?;
1532
1533 OutboxFee::<T>::insert((dst_chain_id, message_id), outbox_fees);
1534
1535 if !T::NoteChainTransfer::note_transfer_out(inbox_fees, dst_chain_id) {
1537 return Err(Error::<T>::FailedToNoteTransferOut.into());
1538 }
1539
1540 Ok(())
1541 }
1542 }
1543}
1544
1545impl<T> Pallet<T>
1546where
1547 T: Config + CreateUnsigned<Call<T>>,
1548{
1549 pub fn outbox_message_unsigned(
1550 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1551 ) -> Option<T::Extrinsic> {
1552 let call = Call::relay_message { msg };
1553 Some(T::create_unsigned(call.into()))
1554 }
1555
1556 pub fn inbox_response_message_unsigned(
1557 msg: CrossDomainMessage<BlockNumberFor<T>, T::Hash, T::MmrHash>,
1558 ) -> Option<T::Extrinsic> {
1559 let call = Call::relay_message_response { msg };
1560 Some(T::create_unsigned(call.into()))
1561 }
1562
1563 pub fn should_relay_outbox_message(dst_chain_id: ChainId, msg_id: MessageId) -> bool {
1565 Outbox::<T>::contains_key((dst_chain_id, msg_id.0, msg_id.1))
1566 }
1567
1568 pub fn should_relay_inbox_message_response(dst_chain_id: ChainId, msg_id: MessageId) -> bool {
1570 InboxResponses::<T>::contains_key((dst_chain_id, msg_id.0, msg_id.1))
1571 }
1572}
1573
1574impl<T: Config> sp_domains::DomainBundleSubmitted for Pallet<T> {
1575 fn domain_bundle_submitted(domain_id: DomainId) {
1576 DomainChainAllowlistUpdate::<T>::mutate(domain_id, |maybe_updates| {
1580 if let Some(ref mut updates) = maybe_updates {
1581 updates.clear();
1582 }
1583 });
1584 }
1585}
1586
1587impl<T: Config> sp_domains::OnDomainInstantiated for Pallet<T> {
1588 fn on_domain_instantiated(domain_id: DomainId) {
1589 DomainChainAllowlistUpdate::<T>::insert(domain_id, DomainAllowlistUpdates::default());
1590 }
1591}