pallet_domains/
extensions.rs

1//! Extensions for unsigned general extrinsics
2
3use crate::pallet::Call as DomainsCall;
4use crate::weights::WeightInfo;
5use crate::{Config, FraudProofFor, OpaqueBundleOf, Origin, Pallet as Domains, SingletonReceiptOf};
6use frame_support::ensure;
7use frame_support::pallet_prelude::{PhantomData, TypeInfo};
8use frame_support::weights::Weight;
9use frame_system::pallet_prelude::RuntimeCallFor;
10use parity_scale_codec::{Decode, Encode};
11use scale_info::prelude::fmt;
12use sp_domains_fraud_proof::InvalidTransactionCode;
13use sp_domains_fraud_proof::weights::fraud_proof_verification_weights;
14use sp_runtime::impl_tx_ext_default;
15use sp_runtime::traits::{
16    AsSystemOriginSigner, DispatchInfoOf, DispatchOriginOf, Dispatchable, Get, Implication,
17    TransactionExtension, ValidateResult,
18};
19use sp_runtime::transaction_validity::{
20    InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity,
21    ValidTransaction,
22};
23
24/// Trait to convert Runtime call to possible Domains call.
25pub trait MaybeDomainsCall<Runtime>
26where
27    Runtime: Config,
28{
29    fn maybe_domains_call(&self) -> Option<&DomainsCall<Runtime>>;
30}
31
32/// Trait to check if the Domains are enabled on Consensus.
33pub trait DomainsCheck {
34    /// Check if the domains are enabled on Runtime.
35    fn is_domains_enabled() -> bool;
36}
37
38/// Extensions for pallet-domains unsigned extrinsics.
39#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
40pub struct DomainsExtension<Runtime>(PhantomData<Runtime>);
41
42impl<Runtime> DomainsExtension<Runtime> {
43    pub fn new() -> Self {
44        Self(PhantomData)
45    }
46}
47
48impl<Runtime> Default for DomainsExtension<Runtime> {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl<T: Config> fmt::Debug for DomainsExtension<T> {
55    #[cfg(feature = "std")]
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(f, "DomainsExtension",)
58    }
59
60    #[cfg(not(feature = "std"))]
61    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
62        Ok(())
63    }
64}
65
66impl<Runtime> DomainsExtension<Runtime>
67where
68    Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync,
69{
70    fn do_validate_submit_bundle(
71        opaque_bundle: &OpaqueBundleOf<Runtime>,
72        source: TransactionSource,
73    ) -> TransactionValidity {
74        let pre_dispatch = TransactionSource::InBlock == source;
75        if pre_dispatch {
76            Domains::<Runtime>::validate_submit_bundle(opaque_bundle, true)
77                .map_err(|_| InvalidTransaction::Call.into())
78                .map(|_| ValidTransaction::default())
79        } else {
80            let domain_id = opaque_bundle.domain_id();
81            let operator_id = opaque_bundle.operator_id();
82            let slot_number = opaque_bundle.slot_number();
83
84            if let Err(e) = Domains::<Runtime>::validate_submit_bundle(opaque_bundle, false) {
85                Domains::<Runtime>::log_bundle_error(&e, domain_id, operator_id);
86                return e.into();
87            }
88
89            ValidTransaction::with_tag_prefix("SubspaceSubmitBundle")
90                // Bundle have a bit higher priority than normal extrinsic but must less than
91                // fraud proof
92                .priority(1)
93                .longevity(
94                    Runtime::ConfirmationDepthK::get()
95                        .try_into()
96                        .unwrap_or_else(|_| {
97                            panic!("Block number always fits in TransactionLongevity; qed")
98                        }),
99                )
100                .and_provides((operator_id, slot_number))
101                .propagate(true)
102                .build()
103        }
104    }
105
106    fn do_validate_fraud_proof(
107        fraud_proof: &FraudProofFor<Runtime>,
108        source: TransactionSource,
109    ) -> TransactionValidity {
110        let pre_dispatch = TransactionSource::InBlock == source;
111        if pre_dispatch {
112            Domains::<Runtime>::validate_fraud_proof(fraud_proof)
113                .map(|_| ())
114                .map_err(|_| InvalidTransaction::Call.into())
115                .map(|_| ValidTransaction::default())
116        } else {
117            let (tag, priority) = match Domains::<Runtime>::validate_fraud_proof(fraud_proof) {
118                Err(e) => {
119                    log::warn!("Bad fraud proof {fraud_proof:?}, error: {e:?}",);
120                    return InvalidTransactionCode::FraudProof.into();
121                }
122                Ok(tp) => tp,
123            };
124
125            ValidTransaction::with_tag_prefix("SubspaceSubmitFraudProof")
126                .priority(priority)
127                .and_provides(tag)
128                .longevity(TransactionLongevity::MAX)
129                // We need this extrinsic to be propagated to the farmer nodes.
130                .propagate(true)
131                .build()
132        }
133    }
134
135    fn do_validate_singleton_receipt(
136        singleton_receipt: &SingletonReceiptOf<Runtime>,
137        source: TransactionSource,
138    ) -> TransactionValidity {
139        let pre_dispatch = TransactionSource::InBlock == source;
140        if pre_dispatch {
141            Domains::<Runtime>::validate_singleton_receipt(singleton_receipt, true)
142                .map_err(|_| InvalidTransaction::Call.into())
143                .map(|_| ValidTransaction::default())
144        } else {
145            let domain_id = singleton_receipt.domain_id();
146            let operator_id = singleton_receipt.operator_id();
147            let slot_number = singleton_receipt.slot_number();
148
149            if let Err(e) = Domains::<Runtime>::validate_singleton_receipt(singleton_receipt, false)
150            {
151                Domains::<Runtime>::log_bundle_error(&e, domain_id, operator_id);
152                return e.into();
153            }
154
155            ValidTransaction::with_tag_prefix("SubspaceSubmitReceipt")
156                // Receipt have a bit higher priority than normal extrinsic but must less than
157                // fraud proof
158                .priority(1)
159                .longevity(
160                    Runtime::ConfirmationDepthK::get()
161                        .try_into()
162                        .unwrap_or_else(|_| {
163                            panic!("Block number always fits in TransactionLongevity; qed")
164                        }),
165                )
166                .and_provides((operator_id, slot_number))
167                .propagate(true)
168                .build()
169        }
170    }
171}
172
173impl<Runtime> TransactionExtension<RuntimeCallFor<Runtime>> for DomainsExtension<Runtime>
174where
175    Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync + DomainsCheck,
176    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
177        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + From<Origin> + Clone,
178    RuntimeCallFor<Runtime>: MaybeDomainsCall<Runtime>,
179{
180    const IDENTIFIER: &'static str = "DomainsExtension";
181    type Implicit = ();
182    type Val = ();
183    type Pre = ();
184
185    fn weight(&self, call: &Runtime::RuntimeCall) -> Weight {
186        // This extension only apply to the following 3 calls thus only return weight for them.
187        let maybe_weight = match call.maybe_domains_call() {
188            Some(DomainsCall::submit_bundle { .. }) => {
189                Some(<Runtime as Config>::WeightInfo::validate_submit_bundle())
190            }
191            Some(DomainsCall::submit_fraud_proof { fraud_proof }) => Some(
192                <Runtime as Config>::WeightInfo::fraud_proof_pre_check()
193                    .saturating_add(fraud_proof_verification_weights::<_, _, _, _>(fraud_proof)),
194            ),
195            Some(DomainsCall::submit_receipt { .. }) => {
196                Some(<Runtime as Config>::WeightInfo::validate_singleton_receipt())
197            }
198            _ => None,
199        };
200
201        // There is one additional runtime read to check if the domains are enabled
202        maybe_weight
203            .and_then(|weight| weight.checked_add(&Runtime::DbWeight::get().reads(1)))
204            .unwrap_or(Runtime::DbWeight::get().reads(1))
205    }
206
207    fn validate(
208        &self,
209        origin: DispatchOriginOf<RuntimeCallFor<Runtime>>,
210        call: &RuntimeCallFor<Runtime>,
211        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
212        _len: usize,
213        _self_implicit: Self::Implicit,
214        _inherited_implication: &impl Implication,
215        source: TransactionSource,
216    ) -> ValidateResult<Self::Val, RuntimeCallFor<Runtime>> {
217        // we only care about unsigned calls
218        if origin.as_system_origin_signer().is_some() {
219            return Ok((ValidTransaction::default(), (), origin));
220        };
221
222        let domains_call = match call.maybe_domains_call() {
223            Some(domains_call) => domains_call,
224            None => return Ok((ValidTransaction::default(), (), origin)),
225        };
226
227        // ensure domains are enabled
228        ensure!(Runtime::is_domains_enabled(), InvalidTransaction::Call);
229
230        let validity = match domains_call {
231            DomainsCall::submit_bundle { opaque_bundle } => {
232                Self::do_validate_submit_bundle(opaque_bundle, source)?
233            }
234            DomainsCall::submit_fraud_proof { fraud_proof } => {
235                Self::do_validate_fraud_proof(fraud_proof, source)?
236            }
237            DomainsCall::submit_receipt { singleton_receipt } => {
238                Self::do_validate_singleton_receipt(singleton_receipt, source)?
239            }
240            _ => return Err(InvalidTransaction::Call.into()),
241        };
242
243        Ok((validity, (), Origin::ValidatedUnsigned.into()))
244    }
245
246    impl_tx_ext_default!(RuntimeCallFor<Runtime>; prepare);
247}