pallet_domains/
extensions.rs

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