pallet_domains/
extensions.rs

1//! Extensions for unsigned general extrinsics
2
3use crate::pallet::Call as DomainsCall;
4use crate::{Config, FraudProofFor, OpaqueBundleOf, Origin, Pallet as Domains, SingletonReceiptOf};
5use frame_support::pallet_prelude::{PhantomData, TypeInfo};
6use frame_system::pallet_prelude::RuntimeCallFor;
7use parity_scale_codec::{Decode, Encode};
8use scale_info::prelude::fmt;
9use sp_domains_fraud_proof::InvalidTransactionCode;
10use sp_runtime::impl_tx_ext_default;
11use sp_runtime::traits::{
12    AsSystemOriginSigner, DispatchInfoOf, DispatchOriginOf, Dispatchable, Get, Implication,
13    TransactionExtension, ValidateResult,
14};
15use sp_runtime::transaction_validity::{
16    InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity,
17    ValidTransaction,
18};
19
20/// Trait to convert Runtime call to possible Domains call.
21pub trait MaybeDomainsCall<Runtime>
22where
23    Runtime: Config,
24{
25    fn maybe_domains_call(&self) -> Option<&DomainsCall<Runtime>>;
26}
27
28/// Extensions for pallet-domains unsigned extrinsics.
29#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
30pub struct DomainsExtension<Runtime>(PhantomData<Runtime>);
31
32impl<Runtime> DomainsExtension<Runtime> {
33    pub fn new() -> Self {
34        Self(PhantomData)
35    }
36}
37
38impl<Runtime> Default for DomainsExtension<Runtime> {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl<T: Config> fmt::Debug for DomainsExtension<T> {
45    #[cfg(feature = "std")]
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(f, "DomainsExtension",)
48    }
49
50    #[cfg(not(feature = "std"))]
51    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
52        Ok(())
53    }
54}
55
56impl<Runtime> DomainsExtension<Runtime>
57where
58    Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync,
59{
60    fn do_validate_submit_bundle(
61        opaque_bundle: &OpaqueBundleOf<Runtime>,
62        source: TransactionSource,
63    ) -> TransactionValidity {
64        let pre_dispatch = TransactionSource::InBlock == source;
65        if pre_dispatch {
66            Domains::<Runtime>::validate_submit_bundle(opaque_bundle, true)
67                .map_err(|_| InvalidTransaction::Call.into())
68                .map(|_| ValidTransaction::default())
69        } else {
70            let domain_id = opaque_bundle.domain_id();
71            let operator_id = opaque_bundle.operator_id();
72            let slot_number = opaque_bundle.slot_number();
73
74            if let Err(e) = Domains::<Runtime>::validate_submit_bundle(opaque_bundle, false) {
75                Domains::<Runtime>::log_bundle_error(&e, domain_id, operator_id);
76                return e.into();
77            }
78
79            ValidTransaction::with_tag_prefix("SubspaceSubmitBundle")
80                // Bundle have a bit higher priority than normal extrinsic but must less than
81                // fraud proof
82                .priority(1)
83                .longevity(
84                    Runtime::ConfirmationDepthK::get()
85                        .try_into()
86                        .unwrap_or_else(|_| {
87                            panic!("Block number always fits in TransactionLongevity; qed")
88                        }),
89                )
90                .and_provides((operator_id, slot_number))
91                .propagate(true)
92                .build()
93        }
94    }
95
96    fn do_validate_fraud_proof(
97        fraud_proof: &FraudProofFor<Runtime>,
98        source: TransactionSource,
99    ) -> TransactionValidity {
100        let pre_dispatch = TransactionSource::InBlock == source;
101        if pre_dispatch {
102            Domains::<Runtime>::validate_fraud_proof(fraud_proof)
103                .map(|_| ())
104                .map_err(|_| InvalidTransaction::Call.into())
105                .map(|_| ValidTransaction::default())
106        } else {
107            let (tag, priority) = match Domains::<Runtime>::validate_fraud_proof(fraud_proof) {
108                Err(e) => {
109                    log::warn!(
110                        target: "runtime::domains",
111                        "Bad fraud proof {fraud_proof:?}, error: {e:?}",
112                    );
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 validate(
179        &self,
180        origin: DispatchOriginOf<RuntimeCallFor<Runtime>>,
181        call: &RuntimeCallFor<Runtime>,
182        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
183        _len: usize,
184        _self_implicit: Self::Implicit,
185        _inherited_implication: &impl Implication,
186        source: TransactionSource,
187    ) -> ValidateResult<Self::Val, RuntimeCallFor<Runtime>> {
188        // we only care about unsigned calls
189        if origin.as_system_origin_signer().is_some() {
190            return Ok((ValidTransaction::default(), (), origin));
191        };
192
193        let domains_call = match call.maybe_domains_call() {
194            Some(domains_call) => domains_call,
195            None => return Ok((ValidTransaction::default(), (), origin)),
196        };
197
198        let validity = match domains_call {
199            DomainsCall::submit_bundle { opaque_bundle } => {
200                Self::do_validate_submit_bundle(opaque_bundle, source)?
201            }
202            DomainsCall::submit_fraud_proof { fraud_proof } => {
203                Self::do_validate_fraud_proof(fraud_proof, source)?
204            }
205            DomainsCall::submit_receipt { singleton_receipt } => {
206                Self::do_validate_singleton_receipt(singleton_receipt, source)?
207            }
208            _ => return Err(InvalidTransaction::Call.into()),
209        };
210
211        Ok((validity, (), Origin::ValidatedUnsigned.into()))
212    }
213
214    impl_tx_ext_default!(RuntimeCallFor<Runtime>; prepare);
215
216    // TODO: need benchmarking for this extension.
217    impl_tx_ext_default!(RuntimeCallFor<Runtime>; weight);
218}