pallet_messenger/
extensions.rs

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