1use 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
26pub trait MaybeDomainsCall<Runtime>
28where
29 Runtime: Config,
30{
31 fn maybe_domains_call(&self) -> Option<&DomainsCall<Runtime>>;
32}
33
34pub trait DomainsCheck {
36 fn is_domains_enabled() -> bool;
38}
39
40#[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 .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 .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 .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 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 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 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!(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}