pallet_messenger/
extensions.rs

1//! Extensions for unsigned general extrinsics
2
3#[cfg(feature = "runtime-benchmarks")]
4pub mod benchmarking_between_domains;
5#[cfg(feature = "runtime-benchmarks")]
6pub mod benchmarking_from_consensus;
7#[cfg(feature = "runtime-benchmarks")]
8pub mod benchmarking_from_domains;
9pub mod weights;
10mod weights_from_consensus;
11mod weights_from_domains;
12
13pub use crate::extensions::weights::{FromConsensusWeightInfo, FromDomainWeightInfo};
14pub use crate::extensions::weights_from_consensus::WeightInfo as WeightsFromConsensus;
15pub use crate::extensions::weights_from_domains::WeightInfo as WeightsFromDomains;
16use crate::pallet::Call as MessengerCall;
17use crate::{
18    Call, Config, ExtensionWeightInfo, Origin, Pallet as Messenger, ValidatedRelayMessage,
19    XDM_TRANSACTION_LONGEVITY,
20};
21use core::cmp::Ordering;
22use frame_support::RuntimeDebugNoBound;
23use frame_support::pallet_prelude::{PhantomData, TypeInfo, Weight};
24use frame_system::pallet_prelude::RuntimeCallFor;
25use parity_scale_codec::{Decode, Encode};
26use scale_info::prelude::fmt;
27use sp_messenger::MAX_FUTURE_ALLOWED_NONCES;
28use sp_messenger::messages::{Message, Nonce, Proof};
29use sp_runtime::DispatchResult;
30use sp_runtime::traits::{
31    AsSystemOriginSigner, DispatchInfoOf, DispatchOriginOf, Dispatchable, Implication,
32    PostDispatchInfoOf, TransactionExtension, ValidateResult,
33};
34use sp_runtime::transaction_validity::{
35    InvalidTransaction, TransactionSource, TransactionValidityError, ValidTransaction,
36    ValidTransactionBuilder,
37};
38use sp_subspace_mmr::MmrProofVerifier;
39
40/// Trait to convert Runtime call to possible Messenger call.
41pub trait MaybeMessengerCall<Runtime>
42where
43    Runtime: Config,
44{
45    fn maybe_messenger_call(&self) -> Option<&MessengerCall<Runtime>>;
46}
47
48/// Data passed from validate to prepare.
49#[derive(RuntimeDebugNoBound)]
50pub enum Val<T: Config + fmt::Debug> {
51    /// No validation data
52    None,
53    /// Validated data
54    ValidatedRelayMessage(ValidatedRelayMessage<T>),
55}
56
57/// Data passed from prepare to post_dispatch.
58#[derive(RuntimeDebugNoBound)]
59pub enum Pre {
60    Refund(Weight),
61}
62
63/// Extensions for pallet-messenger unsigned extrinsics.
64#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
65pub struct MessengerExtension<Runtime>(PhantomData<Runtime>);
66
67impl<Runtime> MessengerExtension<Runtime> {
68    pub fn new() -> Self {
69        Self(PhantomData)
70    }
71}
72
73impl<Runtime> Default for MessengerExtension<Runtime> {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79impl<T: Config> fmt::Debug for MessengerExtension<T> {
80    #[cfg(feature = "std")]
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "MessengerExtension",)
83    }
84
85    #[cfg(not(feature = "std"))]
86    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
87        Ok(())
88    }
89}
90
91impl<Runtime> MessengerExtension<Runtime>
92where
93    Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync,
94{
95    fn check_future_nonce_and_add_requires(
96        mut valid_tx_builder: ValidTransactionBuilder,
97        validated_relay_message: &ValidatedRelayMessage<Runtime>,
98    ) -> Result<ValidTransactionBuilder, TransactionValidityError> {
99        let Message {
100            dst_chain_id,
101            channel_id,
102            nonce: msg_nonce,
103            ..
104        } = &validated_relay_message.message;
105
106        let next_nonce = validated_relay_message.next_nonce;
107        // Only add the requires tag if the msg nonce is in future
108        if *msg_nonce > next_nonce {
109            let max_future_nonce = next_nonce.saturating_add(MAX_FUTURE_ALLOWED_NONCES.into());
110            if *msg_nonce > max_future_nonce {
111                return Err(InvalidTransaction::Custom(
112                    crate::verification_errors::IN_FUTURE_NONCE,
113                )
114                .into());
115            }
116
117            valid_tx_builder =
118                valid_tx_builder.and_requires((dst_chain_id, channel_id, msg_nonce - Nonce::one()));
119        };
120
121        Ok(valid_tx_builder)
122    }
123
124    fn do_validate(
125        call: &MessengerCall<Runtime>,
126    ) -> Result<(ValidTransaction, ValidatedRelayMessage<Runtime>), TransactionValidityError> {
127        match call {
128            Call::relay_message { msg: xdm } => {
129                let consensus_state_root =
130                    Runtime::MmrProofVerifier::verify_proof_and_extract_leaf(
131                        xdm.proof.consensus_mmr_proof(),
132                    )
133                    .ok_or(InvalidTransaction::BadProof)?
134                    .state_root();
135
136                let validated_message =
137                    Messenger::<Runtime>::validate_relay_message(xdm, consensus_state_root)?;
138
139                let Message {
140                    dst_chain_id,
141                    channel_id,
142                    nonce: msg_nonce,
143                    ..
144                } = &validated_message.message;
145
146                let valid_tx_builder = Self::check_future_nonce_and_add_requires(
147                    ValidTransaction::with_tag_prefix("MessengerInbox"),
148                    &validated_message,
149                )?;
150
151                let validity = valid_tx_builder
152                    // XDM have a bit higher priority than normal extrinsic but must less than
153                    // fraud proof
154                    .priority(1)
155                    .longevity(XDM_TRANSACTION_LONGEVITY)
156                    .and_provides((dst_chain_id, channel_id, msg_nonce))
157                    .propagate(true)
158                    .build()?;
159
160                Ok((validity, validated_message))
161            }
162            Call::relay_message_response { msg: xdm } => {
163                let consensus_state_root =
164                    Runtime::MmrProofVerifier::verify_proof_and_extract_leaf(
165                        xdm.proof.consensus_mmr_proof(),
166                    )
167                    .ok_or(InvalidTransaction::BadProof)?
168                    .state_root();
169
170                let validated_message = Messenger::<Runtime>::validate_relay_message_response(
171                    xdm,
172                    consensus_state_root,
173                )?;
174
175                let Message {
176                    dst_chain_id,
177                    channel_id,
178                    nonce: msg_nonce,
179                    ..
180                } = &validated_message.message;
181
182                let valid_tx_builder = Self::check_future_nonce_and_add_requires(
183                    ValidTransaction::with_tag_prefix("MessengerOutboxResponse"),
184                    &validated_message,
185                )?;
186
187                let validity = valid_tx_builder
188                    // XDM have a bit higher priority than normal extrinsic but must less than
189                    // fraud proof
190                    .priority(1)
191                    .longevity(XDM_TRANSACTION_LONGEVITY)
192                    .and_provides((dst_chain_id, channel_id, msg_nonce))
193                    .propagate(true)
194                    .build()?;
195
196                Ok((validity, validated_message))
197            }
198            _ => Err(InvalidTransaction::Call.into()),
199        }
200    }
201
202    fn do_prepare(
203        call: &MessengerCall<Runtime>,
204        val: ValidatedRelayMessage<Runtime>,
205    ) -> Result<Pre, TransactionValidityError> {
206        let ValidatedRelayMessage {
207            message,
208            should_init_channel,
209            next_nonce,
210        } = val;
211
212        // Reject in future message
213        if message.nonce.cmp(&next_nonce) == Ordering::Greater {
214            return Err(InvalidTransaction::Future.into());
215        }
216
217        let pre = match call {
218            Call::relay_message { msg } => {
219                Messenger::<Runtime>::pre_dispatch_relay_message(message, should_init_channel)?;
220                if should_init_channel {
221                    // if this is a channel init,
222                    // there is no further refund of weight
223                    Pre::Refund(Weight::zero())
224                } else {
225                    match msg.proof {
226                        Proof::Consensus { .. } => Pre::Refund(Self::refund_weight_for_consensus()),
227                        Proof::Domain { .. } => Pre::Refund(Self::refund_weight_for_domains()),
228                    }
229                }
230            }
231            Call::relay_message_response { .. } => {
232                Messenger::<Runtime>::pre_dispatch_relay_message_response(message)?;
233                // no refund for relay response message.
234                Pre::Refund(Weight::zero())
235            }
236            _ => return Err(InvalidTransaction::Call.into()),
237        };
238
239        Ok(pre)
240    }
241
242    fn do_calculate_weight(call: &RuntimeCallFor<Runtime>) -> Weight
243    where
244        RuntimeCallFor<Runtime>: MaybeMessengerCall<Runtime>,
245        Runtime: Config,
246    {
247        let messenger_call = match call.maybe_messenger_call() {
248            Some(messenger_call) => messenger_call,
249            None => return Weight::zero(),
250        };
251
252        let (dst_chain_id, verification_weight) = match messenger_call {
253            Call::relay_message { msg } => (
254                msg.dst_chain_id,
255                match msg.proof {
256                    Proof::Consensus { .. } => {
257                        Runtime::ExtensionWeightInfo::from_consensus_relay_message().max(
258                            Runtime::ExtensionWeightInfo::from_consensus_relay_message_channel_open(
259                            ),
260                        )
261                    }
262                    Proof::Domain { .. } => {
263                        Runtime::ExtensionWeightInfo::from_domains_relay_message_channel_open()
264                            .max(Runtime::ExtensionWeightInfo::from_domains_relay_message())
265                    }
266                },
267            ),
268            Call::relay_message_response { msg } => (
269                msg.dst_chain_id,
270                match msg.proof {
271                    Proof::Consensus { .. } => {
272                        Runtime::ExtensionWeightInfo::from_consensus_relay_message_response()
273                    }
274                    Proof::Domain { .. } => {
275                        Runtime::ExtensionWeightInfo::from_domains_relay_message_response()
276                    }
277                },
278            ),
279            _ => return Weight::zero(),
280        };
281
282        let mmr_proof_weight = if dst_chain_id.is_consensus_chain() {
283            Runtime::ExtensionWeightInfo::mmr_proof_verification_on_consensus()
284        } else {
285            Runtime::ExtensionWeightInfo::mmr_proof_verification_on_domain()
286        };
287
288        mmr_proof_weight.saturating_add(verification_weight)
289    }
290
291    fn refund_weight_for_consensus() -> Weight {
292        let min = Runtime::ExtensionWeightInfo::from_consensus_relay_message_channel_open()
293            .min(Runtime::ExtensionWeightInfo::from_consensus_relay_message());
294        let max = Runtime::ExtensionWeightInfo::from_consensus_relay_message_channel_open()
295            .max(Runtime::ExtensionWeightInfo::from_consensus_relay_message());
296        max.saturating_sub(min)
297    }
298
299    fn refund_weight_for_domains() -> Weight {
300        let min = Runtime::ExtensionWeightInfo::from_domains_relay_message_channel_open()
301            .min(Runtime::ExtensionWeightInfo::from_domains_relay_message());
302        let max = Runtime::ExtensionWeightInfo::from_domains_relay_message_channel_open()
303            .max(Runtime::ExtensionWeightInfo::from_domains_relay_message());
304        max.saturating_sub(min)
305    }
306}
307
308impl<Runtime> TransactionExtension<RuntimeCallFor<Runtime>> for MessengerExtension<Runtime>
309where
310    Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync,
311    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
312        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + From<Origin> + Clone,
313    RuntimeCallFor<Runtime>: MaybeMessengerCall<Runtime>,
314{
315    const IDENTIFIER: &'static str = "MessengerExtension";
316    type Implicit = ();
317    type Val = Val<Runtime>;
318    type Pre = Pre;
319
320    fn weight(&self, call: &RuntimeCallFor<Runtime>) -> Weight {
321        Self::do_calculate_weight(call)
322    }
323
324    fn validate(
325        &self,
326        origin: DispatchOriginOf<RuntimeCallFor<Runtime>>,
327        call: &RuntimeCallFor<Runtime>,
328        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
329        _len: usize,
330        _self_implicit: Self::Implicit,
331        _inherited_implication: &impl Implication,
332        _source: TransactionSource,
333    ) -> ValidateResult<Self::Val, RuntimeCallFor<Runtime>> {
334        // we only care about unsigned calls
335        if origin.as_system_origin_signer().is_some() {
336            return Ok((ValidTransaction::default(), Val::None, origin));
337        };
338
339        let messenger_call = match call.maybe_messenger_call() {
340            Some(messenger_call) => messenger_call,
341            None => return Ok((ValidTransaction::default(), Val::None, origin)),
342        };
343
344        let (validity, validated_relay_message) = Self::do_validate(messenger_call)?;
345        Ok((
346            validity,
347            Val::ValidatedRelayMessage(validated_relay_message),
348            Origin::ValidatedUnsigned.into(),
349        ))
350    }
351
352    fn prepare(
353        self,
354        val: Self::Val,
355        _origin: &DispatchOriginOf<RuntimeCallFor<Runtime>>,
356        call: &RuntimeCallFor<Runtime>,
357        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
358        _len: usize,
359    ) -> Result<Self::Pre, TransactionValidityError> {
360        match (call.maybe_messenger_call(), val) {
361            // prepare if this is a messenger call and has been validated
362            (Some(messenger_call), Val::ValidatedRelayMessage(validated_relay_message)) => {
363                Self::do_prepare(messenger_call, validated_relay_message)
364            }
365            // return Ok for the rest of the call types and nothing to refund here as
366            // non XDM calls will have zero weight from this extension.
367            (_, _) => Ok(Pre::Refund(Weight::zero())),
368        }
369    }
370
371    fn post_dispatch_details(
372        pre: Self::Pre,
373        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
374        _post_info: &PostDispatchInfoOf<RuntimeCallFor<Runtime>>,
375        _len: usize,
376        _result: &DispatchResult,
377    ) -> Result<Weight, TransactionValidityError> {
378        let Pre::Refund(weight) = pre;
379        Ok(weight)
380    }
381}
382
383/// Extensions for pallet-messenger unsigned extrinsics with trusted MMR verification.
384#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
385pub struct MessengerTrustedMmrExtension<Runtime>(PhantomData<Runtime>);
386
387impl<Runtime> MessengerTrustedMmrExtension<Runtime> {
388    pub fn new() -> Self {
389        Self(PhantomData)
390    }
391}
392
393impl<Runtime> Default for MessengerTrustedMmrExtension<Runtime> {
394    fn default() -> Self {
395        Self::new()
396    }
397}
398
399impl<T: Config> fmt::Debug for MessengerTrustedMmrExtension<T> {
400    #[cfg(feature = "std")]
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        write!(f, "MessengerTrustedMmrExtension",)
403    }
404
405    #[cfg(not(feature = "std"))]
406    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
407        Ok(())
408    }
409}
410
411impl<Runtime> MessengerTrustedMmrExtension<Runtime>
412where
413    Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync,
414{
415    fn do_validate(
416        call: &MessengerCall<Runtime>,
417    ) -> Result<(ValidTransaction, ValidatedRelayMessage<Runtime>), TransactionValidityError> {
418        match call {
419            Call::relay_message { msg: xdm } => {
420                let consensus_state_root =
421                    Runtime::MmrProofVerifier::extract_leaf_without_verifying(
422                        xdm.proof.consensus_mmr_proof(),
423                    )
424                    .ok_or(InvalidTransaction::BadProof)?
425                    .state_root();
426
427                let validated_relay_message =
428                    Messenger::<Runtime>::validate_relay_message(xdm, consensus_state_root)?;
429
430                Ok((ValidTransaction::default(), validated_relay_message))
431            }
432            Call::relay_message_response { msg: xdm } => {
433                let consensus_state_root =
434                    Runtime::MmrProofVerifier::extract_leaf_without_verifying(
435                        xdm.proof.consensus_mmr_proof(),
436                    )
437                    .ok_or(InvalidTransaction::BadProof)?
438                    .state_root();
439
440                let validated_relay_message =
441                    Messenger::<Runtime>::validate_relay_message_response(
442                        xdm,
443                        consensus_state_root,
444                    )?;
445
446                Ok((ValidTransaction::default(), validated_relay_message))
447            }
448            _ => Err(InvalidTransaction::Call.into()),
449        }
450    }
451}
452
453impl<Runtime> TransactionExtension<RuntimeCallFor<Runtime>>
454    for MessengerTrustedMmrExtension<Runtime>
455where
456    Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync,
457    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
458        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + From<Origin> + Clone,
459    RuntimeCallFor<Runtime>: MaybeMessengerCall<Runtime>,
460{
461    const IDENTIFIER: &'static str = "MessengerTrustedMmrExtension";
462    type Implicit = ();
463    type Val = Val<Runtime>;
464    type Pre = Pre;
465
466    fn weight(&self, call: &RuntimeCallFor<Runtime>) -> Weight {
467        MessengerExtension::<Runtime>::do_calculate_weight(call)
468    }
469
470    fn validate(
471        &self,
472        origin: DispatchOriginOf<RuntimeCallFor<Runtime>>,
473        call: &RuntimeCallFor<Runtime>,
474        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
475        _len: usize,
476        _self_implicit: Self::Implicit,
477        _inherited_implication: &impl Implication,
478        _source: TransactionSource,
479    ) -> ValidateResult<Self::Val, RuntimeCallFor<Runtime>> {
480        // we only care about unsigned calls
481        if origin.as_system_origin_signer().is_some() {
482            return Ok((ValidTransaction::default(), Val::None, origin));
483        };
484
485        let messenger_call = match call.maybe_messenger_call() {
486            Some(messenger_call) => messenger_call,
487            None => return Ok((ValidTransaction::default(), Val::None, origin)),
488        };
489
490        let (validity, validated_relay_message) = Self::do_validate(messenger_call)?;
491        Ok((
492            validity,
493            Val::ValidatedRelayMessage(validated_relay_message),
494            Origin::ValidatedUnsigned.into(),
495        ))
496    }
497
498    fn prepare(
499        self,
500        val: Self::Val,
501        _origin: &DispatchOriginOf<RuntimeCallFor<Runtime>>,
502        call: &RuntimeCallFor<Runtime>,
503        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
504        _len: usize,
505    ) -> Result<Self::Pre, TransactionValidityError> {
506        match (call.maybe_messenger_call(), val) {
507            // prepare if this is a messenger call and has been validated
508            (Some(messenger_call), Val::ValidatedRelayMessage(validated_relay_message)) => {
509                MessengerExtension::<Runtime>::do_prepare(messenger_call, validated_relay_message)
510            }
511            // return Ok for the rest of the call types
512            (_, _) => Ok(Pre::Refund(Weight::zero())),
513        }
514    }
515
516    fn post_dispatch_details(
517        pre: Self::Pre,
518        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
519        _post_info: &PostDispatchInfoOf<RuntimeCallFor<Runtime>>,
520        _len: usize,
521        _result: &DispatchResult,
522    ) -> Result<Weight, TransactionValidityError> {
523        let Pre::Refund(weight) = pre;
524        Ok(weight)
525    }
526}