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